Learn how to create your own custom input component in react.
This component will behave as different input types including textarea. We will pass different props to change the behavior of the component.
Following is the list of props we will require in our component.
name: PropTypes.string.isRequired, type: PropTypes.oneOf([ "text", "number", "password", "date", "email", "tel", "url", "search" ]).isRequired, label: PropTypes.string, placeholder: PropTypes.string, readOnly: PropTypes.bool, autoFocus: PropTypes.bool, required: PropTypes.bool, maxLength: PropTypes.number, pattern: PropTypes.string, onChange: PropTypes.func, onBlur: PropTypes.func, onFocus: PropTypes.func, onKeyDown: PropTypes.func, /* Will be applied to container */ className: PropTypes.string, /* Will be applied to underlying input/textarea tag */ inputClassName: PropTypes.string, /* Will be applied to label */ labelClassName: PropTypes.string, /* Renders a textarea if true */ multi: PropTypes.bool, /* Value */ value: PropTypes.string, error: PropTypes.bool, helperText: PropTypes.string
I guess all the props are self explanatory. You can add extra HTML input types as per your requirement.
Our component will be composed of three parts
- Label:- which will attached label of the input.
- Input:- input area.
- HelperText:- Which will show any messages like warning, info, or error.
So let us begin creating our custom input component in react.
We will be using two extra packages.
prop-types
:- To validate the props.classnames
:- Which will help to use CSS classes as javascript objects. The only thing we will need to makeclassnames
work is rename the CSS file toindex.module.css
First, import all the required files. As we are not going to maintain any state, so we will be creating a functional component.
import React from "react"; import PropTypes from "prop-types"; import cx from "classnames"; import styles from "./index.module.css"; const input = (props, ref) => { //other codes will go here } //Validate the props input.propTypes = { name: PropTypes.string.isRequired, type: PropTypes.oneOf([ "text", "number", "password", "date", "email", "tel", "url", "search" ]).isRequired, label: PropTypes.string, placeholder: PropTypes.string, readOnly: PropTypes.bool, autoFocus: PropTypes.bool, required: PropTypes.bool, maxLength: PropTypes.number, pattern: PropTypes.string, onChange: PropTypes.func, onBlur: PropTypes.func, onFocus: PropTypes.func, onKeyDown: PropTypes.func, /* Will be applied to container */ className: PropTypes.string, /* Will be applied to underlying input/textarea tag */ inputClassName: PropTypes.string, /* Will be applied to label */ labelClassName: PropTypes.string, /* Renders a textarea if true */ multi: PropTypes.bool, /* Value */ value: PropTypes.string, error: PropTypes.bool, helperText: PropTypes.string }; //Set the default props input.defaultProps = { className: "", inputClassName: "", labelClassName: "", type: "text", label: "", placeholder: "", readOnly: false, multi: false }; export default React.forwardRef((props, ref) => input(props, ref));
We are forwarding ref from the parent and passing it to our component to have extra control.
Now lets create the classes for each different part of our component and return it.
const { className, inputClassName, labelClassName, type, label, placeholder, readOnly, multi, maxLength, autoFocus, value, error, helperText } = props; const _className = cx(styles.container, className); const _inputClassName = cx( { [styles.input]: !multi, [styles.textarea]: multi, [styles.readonly]: readOnly, [styles.hasError]: error }, inputClassName ); const _labelClassName = cx(styles.label, labelClassName, { [styles.error]: error }); const _helperTextClassName = cx(styles.helperText, { [styles.error]: error }); let _props = { autoFocus, placeholder, value, readOnly, maxLength, className: _inputClassName, onChange: handleOnChange, onFocus: handleFocus, onBlur: handleOnBlur, onKeyDown: handleKeyDown }; return ( <div className={_className}> {label ? <label className={_labelClassName}>{label}</label> : null} {multi ? ( <textarea {..._props} ref={ref}></textarea> ) : ( <input {..._props} type={type} ref={ref} /> )} {helperText && helperText.length ? ( <span className={_helperTextClassName}>{helperText}</span> ) : null} </div> );
If multi
is set to true
then we will render the textarea
irrespective of the input type passed else we will render the requested input type.
Will pass all other props as it is to the element as it will assign the appropriates props accordingly and neglect others.
Now handle different events on the input component and return them to the parent.
const handleOnBlur = event => { const { value } = event.target; const { onBlur, name } = props; onBlur && onBlur({ value, name, event }); }; const handleOnChange = event => { const { value } = event.target; const { onChange, name } = props; onChange && onChange({ value, name, event }); }; const handleFocus = event => { const { name, onFocus, value } = props; // To fix the issue with cursor at beginning if (value) { event.target.value = ""; event.target.value = value; } onFocus && onFocus({ event, name, value }); }; const handleKeyDown = event => { const { name, onKeyDown } = props; const { value } = event.target; onKeyDown && onKeyDown({ value, name, event }); };
Complete code of input component in react
import React from "react"; import PropTypes from "prop-types"; import cx from "classnames"; import styles from "./index.module.css"; const input = (props, ref) => { const handleOnBlur = event => { const { value } = event.target; const { onBlur, name } = props; onBlur && onBlur({ value, name, event }); }; const handleOnChange = event => { const { value } = event.target; const { onChange, name } = props; onChange && onChange({ value, name, event }); }; const handleFocus = event => { const { name, onFocus, value } = props; // To fix the issue with cursor at beginning if (value) { event.target.value = ""; event.target.value = value; } onFocus && onFocus({ event, name, value }); }; const handleKeyDown = event => { const { name, onKeyDown } = props; const { value } = event.target; onKeyDown && onKeyDown({ value, name, event }); }; const { className, inputClassName, labelClassName, type, label, placeholder, readOnly, multi, maxLength, autoFocus, value, error, helperText } = props; const _className = cx(styles.container, className); const _inputClassName = cx( { [styles.input]: !multi, [styles.textarea]: multi, [styles.readonly]: readOnly, [styles.hasError]: error }, inputClassName ); const _labelClassName = cx(styles.label, labelClassName, { [styles.error]: error }); const _helperTextClassName = cx(styles.helperText, { [styles.error]: error }); let _props = { autoFocus, placeholder, value, readOnly, maxLength, className: _inputClassName, onChange: handleOnChange, onFocus: handleFocus, onBlur: handleOnBlur, onKeyDown: handleKeyDown }; return ( <div className={_className}> {label ? <label className={_labelClassName}>{label}</label> : null} {multi ? ( <textarea {..._props} ref={ref}></textarea> ) : ( <input {..._props} type={type} ref={ref} /> )} {helperText && helperText.length ? ( <span className={_helperTextClassName}>{helperText}</span> ) : null} </div> ); }; input.propTypes = { name: PropTypes.string.isRequired, type: PropTypes.oneOf([ "text", "number", "password", "date", "email", "tel", "url", "search" ]).isRequired, label: PropTypes.string, placeholder: PropTypes.string, readOnly: PropTypes.bool, autoFocus: PropTypes.bool, required: PropTypes.bool, maxLength: PropTypes.number, pattern: PropTypes.string, onChange: PropTypes.func, onBlur: PropTypes.func, onFocus: PropTypes.func, onKeyDown: PropTypes.func, /* Will be applied to container */ className: PropTypes.string, /* Will be applied to underlying input/textarea tag */ inputClassName: PropTypes.string, /* Will be applied to label */ labelClassName: PropTypes.string, /* Renders a textarea if true */ multi: PropTypes.bool, /* Value */ value: PropTypes.string, error: PropTypes.bool, helperText: PropTypes.string }; input.defaultProps = { className: "", inputClassName: "", labelClassName: "", type: "text", label: "", placeholder: "", readOnly: false, multi: false }; export default React.forwardRef((props, ref) => input(props, ref));
Now let start the styling of our component.
We will keep it extremely simple and allow user to pass different classes for the different part of the component to extend the styles.
//index.module.css .container { position: relative; } .label { display: block; font-weight: 600; font-size: 14px; line-height: 24px; margin-bottom: 4px; letter-spacing: -0.05px; color: #4c4c56; } .input, .textarea { padding: 0 10px; height: 40px; color: #4c4c56; resize: none; width: 100%; font-size: 14px; margin-bottom: 20px; border-radius: 3px; transition: 200ms ease all; border: 1px solid #607d8b; } .input:focus, .textarea:focus { box-shadow: rgba(67, 90, 111, 0.14) 0px 0px 2px inset, rgb(87, 154, 217) 0px 0px 0px 1px inset, rgba(16, 112, 202, 0.14) 0px 0px 0px 3px; outline: none; } .hasError { border-color: #eb5055; } .hasError:focus { border-color: #eb5055; } .readonly { box-shadow: rgba(67, 90, 111, 0.14) 0px 0px 0px 1px inset; background-color: rgb(245, 246, 247); } .readonly:focus { box-shadow: none; cursor: no-drop; } .textarea { line-height: 1.5em; height: 100px; padding: 10px; } .helperText { font-weight: 600; font-size: 14px; text-transform: capitalize; letter-spacing: 0.3px; display: block; position: relative; bottom: 5px; color: #7cb342; } .error { color: #eb5055; }
Render the element
ReactDOM.render( <div className="abc"> <Input type="text" placeholder="Enter Text" label="Input Box" helperText="I am a text type input box" name="typeText" /> <hr /> <Input type="email" placeholder="Enter Email Address" label="Email Box" helperText="I am an email type box" name="typeEmail" /> <hr /> <Input type="password" placeholder="Enter Password" label="Password Box" helperText="I am a password type box" name="typePassword" /> <hr /> <Input type="date" placeholder="Enter date" label="Date Box" helperText="I am a Date type input box" name="typeDate" /> <hr /> <Input type="tel" placeholder="Enter Telephone Number" label="Telephone Box" helperText="I am a Telephone type input box" name="typeTel" /> <hr /> </div>, document.getElementById("root") );