Building A Beautiful Text Input in React

Text inputs are the bread and butter of forms, but they’re often tricky to get right. In React, splitting commonly used elements out into components is an efficient way to maximise your re-use of code and minimise errors. Since text inputs are used for a wide variety of purposes, we’ll need to make the component flexible enough to handle a range of different variables in suitability for different environments. Our text input will:

  • Have a focussed and un-focussed state with a smart transition.
  • Accept a pre-defined value, placeholder and label.
  • Accept errors passed in from an external function.
  • Be ‘lockable’.
The finished article.

Without further ado, let’s set up the basis of a simple TextInput component. When coding React, I prefer to work by Airbnb’s style guide to keep my components clean and consistent.

Our text input will be wrapped in a div with class ‘field’; a wrapper to which we will now apply styling. Create a stylesheet and import it by the usual method, then insert the following style to set up the basic appearance of the text input:

I’ve set the height of my text input to be 56px out of preference, but all the sizes concerned can of course be adjusted with some tinkering. Setting the position of the ‘field’ div to relative allows us to perform some trickery with absolutely-positioned elements inside it, allowing for fancy transitions. The transition property ensures that every time a style value for the div changes (for example, on hover), it will be neatly transitioned across a duration of 0.3 seconds.

We now need to define the props for the component, which will do most of the heavy lifting for us. Importing and using the ‘prop-types’ package is recommended whenever you’re creating components, to ensure your props are always validated.

That’s quite a few props! Notice that most of them are optional (non-optional props are marked by an .isRequired suffix and don’t require a corresponding value in defaultProps). This is because we want the text input to be as flexible as possible, accounting for a wide range of use scenarios. At this point it would be good to add some initial state to our component, after defaultProps:

A little bit of logic creeps in here. When the ‘locked’ prop is set to true, we’d like the input only to become focussed when the ‘focussed’ prop is set to true. This is useful for a number of purposes, most commonly in forms where clicking an edit button focusses a certain field which otherwise cannot be focussed by the user. When the ‘locked’ prop is set to false, we’d like the user to be able to focus the field just by clicking or tabbing into it as normal.

To that end, the initial state for `focussed` value checks whether the component has been passed a true ‘locked’ prop and if so, whether the ‘focussed’ value has also been passed as true. If not, it sets the ‘focussed’ state to false. The ‘value’, ‘error’ and ‘label’ states all return the result of conditionals which check whether or not a value has been passed to them from props.

Now to the rendering of the component itself:

As it stands, the component will now render out a dumb input field. But changing the value will have no impact, because the value is being passed in from the component’s state. In order to address this, we need to add a function for the input’s onChange function:

And on the input:

The onChange function is now detecting the value of the input every time a change is made and updating the value in state to reflect it. At the same time, it’s clearing any errors which have been passed in as props. This is because users generally expect a form field to be cleared of errors after it’s edited; at least until the form is submitted again.

At this point the component is becoming quite functional, but not friendly on the eyes. The following CSS add styles for the input element, as well as styling classes for focussed and locked states:

Of course, in order for these new styles to take effect we need to expose the ‘focussed’ and ‘locked’ classes within the component. The following declaration dynamically generates the class for the wrapper div, adding the classes ‘focussed’ and ‘locked’ where appropriate:

This appears quite complicated, but in reality it’s fairly simple. The div is always given the class ‘field’. Next, the operation (locked ? focussed : focussed || value) is performed, which checks if the input has been passed a ‘locked’ prop. If it has, it then returns the boolean value of the component’s ‘focussed’ state. If the input hasn’t been passed a ‘locked’ prop, it returns true if the input has a true ‘focussed’ state or a current value.

If the result of this operation is true, the div is given class ‘focussed’. A simpler operation is then performed to add the class ‘locked’ if the input has a ‘locked’ prop and the ‘focussed’ state is false.

In addition to giving the div a class, we need to allow users to manipulate this class themselves when the input is not locked, giving them the ability to focus and un-focus the field. To do this we can simply use the onFocus and onBlur functions available to input elements, both of which need to check that no ‘locked’ prop has been passed to the input before updating the ‘focussed’ state:

The last addition to the component itself is the addition of a label, which will appear at the top of the input when it is focussed or has a value. Note that if the component has been passed an ‘error’ prop, the error replaces the label and has a different class to allow for error styling:

Finally, the styling needs to be amended to include the addition of a label:

The + label control lets us select the label which is associated with the particular input we’re styling; a neat trick which comes in handy with more complex form elements like radio buttons or checkboxes.

The End Result

That’s it! You now have a fully-functioning input component which looks smart, accepts a pre-determined value, displays errors passed to it and can be locked if required. Happy form-filling.

Full code for the text input can be found here. If you’ve enjoyed this article or it’s helped you in some way, please do let me know! My website is over here.

Full-stack developer based in London.