In this article, we’re going to talk about controlled vs uncontrolled components in React and will give some examples along the way. We’ll discuss their advantages and disadvantages, and finally, we’ll look at a form library available in the React ecosystem.
Without further ado, let’s get started!
To follow along with this article, I would highly recommend reading these articles for some background:
What are controlled components?
Controlled components are components whose value is directed/driven by React’s state. By state, we mean the state we store inside a React component, i.e., inside this.state or with useState.
This means that these components will receive the latest changes.
For any controlled component, we pass on our state variable inside the value prop of the component. The function that changes this state variable of the component is passed in the onChange prop of the controlled component.
Here is an example that demonstrates a controlled component:
- App is a simple function-based React component.
- Its render method consists of a form with an input element and a strong element that displays the value of the state variable of the component.
- We are passing the state variable inputValue as a value prop to the input element.
- Similarly, we are passing the handleOnChange function to the onChange prop of the input component.
- Now, whatever you type inside the input element will set the value of the inputValue state via handleOnChange, and then the same will be displayed in the input element.
- To show that the state variable inputValue is changing, we display it inside the strong element.
Here is a video for the above code:
The controlled component’s value prop does not always need to be a component’s internal state. It can be a prop of the parent component, or it can come from the Redux store. This is similar to the onChange prop.
In a similar manner, we can use react-redux to get the state and the handleOnChange function from the Redux store to have similar functionality to what we had when we were using useState.
The same context can be applied to the checkbox, radio, text area, and select DOM elements so that they can act as controlled elements.
Here are some of the advantages of using controlled components:
- The UI and the data are in sync
- Form data can be passed between different components
- The event handler and the value prop can be from the parent or a redux store
- The React component acts as a source of truth for this component
Some disadvantages of controlled components:
- They render at each input change
- A controlled component can be a bad choice when large changes are required to be made to the component
What are uncontrolled components?
As the name suggests, the value of uncontrolled components is not driven by the React component’s state variable. It is completely controlled by the DOM.
Uncontrolled components are generally used when the use case is simple or the action is not trackable, for example, a user uploading a file using file input.
The basic requirement of any uncontrolled component is to be handled by the DOM. In this case, we cannot pass the state variable inside the form’s input element. Now the question arises — how are we going to set the value inside this input element?
We use React’s useRef in terms of functional component or createRef in terms of class-based components. A useRef is a hook that returns a mutable object with current as its property with value being initialValue that is passed inside the hook.
Here is the syntax of the useRef hook:
A ref object can be used in many ways, e.g., as a state variable that does not re-render the component on change, accessing the DOM elements, etc.
We can leverage this into our uncontrolled component to access input elements attributes, i.e., value properties.
Here is a simple example of how you can create an uncontrolled component:
And here is what the above component will look like:
As you can see, this App component consists of form and a bunch of span elements. We are using the JSON placeholder API in the component to fetch the post based on id. This is the summary of our component, but let’s understand a bit more about how the App component works internally.
- Inside the form element, we have a static select element with a couple of options.
- We are making use of a ref object here. This ref object is named as selectRef and this is being passed on to the select element as a ref prop.
- Here comes the catch— we don’t need to watch for any changes that happen to the select element, nor do we need to make use of a state variable to keep track of the current value that is selected in this element.
- This begs the question, how do we get the latest value from the select element?
- Well, we don’t need to. This is an uncontrolled component/element; a component/element that is managed by the DOM.
- So, the change in the value property of the select element is being managed by the browser, a.k.a by the DOM.
- Now we just need to find a way to capture this value. refs comes to rescue here. Since we passed our ref object into the select element as a prop, we can now access the value present inside this element.
- To get hang of this, you can check out the handleOnSubmit function. This function makes use of the selectRef.current.value to get access to the value property of the select element. The selectRef stores all the values inside the current object. In this case, selectRef.current will store the select element itself. To access the value, simply access its .value property.
- Next, the handleOnSubmit function calls the API. On success, we simply store it in the state variable apiData.
Here are some advantages of uncontrolled components:
- No component re-renders
- Browser DOM handles the changes to the element
- Simple to use
- Keeps track of the internal state
And some of the disadvantages of uncontrolled components:
- Cannot be used in complicated scenarios where we need to pass the value to different components
- It’s not always efficient to use uncontrolled components where we require granular control over the value
In the next section of this article, we are going to talk about a form-based library available for React, called Formik. Let’s start exploring!
Formik is an open-source library to build forms in React and React Native. It’s the world’s leading library for building forms in React.
Formik is a complete solution that helps you to implement form validation, keep track of visited fields, and handle form submission in an efficient and robust manner. It is based on the concept of a controlled component.
Now, let’s look at one simple example of how can Formik can be used to build a form along with the form validation.
We are going to use the same uncontrolled component example. First, we’ll convert the example into a controlled component and then, use Formik to make our life easier.
Here are some of the brief points that will get you up to speed on understanding the above component:
- The Formik library provides us with some easy-to-use components that help us to build forms in no time.
- Formik is a wrapper component that helps to build forms.
- It takes in initialValues, which tells the Formik form about the fields and their initial values.
- Next, we have the validationSchema prop. It can accept a Yup object to define validations inside the form.
- Finally, we provide our submitHandler function as a callback that needs to be executed whenever a submission takes place.
- This is a controlled component. All the values, i.e., initialValues, are handled by Formik.
- In the above component, we have used Formik, Field and ErrorMessage components. These are similar to the normal input elements present inside the form element.
- The Field component, as the name suggests, acts as a wrapper component on top of form input elements.
- Finally, we have the ErrorMessage component. This component takes in the name prop. This name prop will be used by this component to provide an element that displays the error message for that particular field.
- Now, you might be wondering how these components know which field they should work for. Here comes the name and id prop to the rescue. You should provide either of these props to make sure your Field and ErrorMessage components work properly.
- We should also make sure that we pass the submitted values to Yup’s validations that we set earlier. For example, for the select field, we pass in name="selectVal" in the Field and in ErrorMessage component. In this way, Formik knows how to map these wrapper components to their respective fields.
Finally, here is our outcome with form validation in place with the help of Formik:
In this way, you can make use of controlled and uncontrolled components. Choosing which type of component to use in your project completely depends upon the use case.
If you’re looking for tangible results with a simple use case or trying to use memory-intensive operations, such as document editors, uncontrolled components are the way to go.
If you’re looking for more fine-grain control over the value of form elements, then you should use controlled components.
Become More Productive Writing Your React Apps
Every component you create in React often requires tons of repetition no matter what you do, and across every frontend project, there are always custom setups and best practices teams follow. Pieces helps you solve this for any React project by allowing you to create a local micro-repository where you can store any code snippets along with relevant metadata straight on your machine. Additionally, Pieces makes it incredibly easy to share your snippets with others, form collections to onboard others onto a project, and even has integrations to allow you to use your snippets directly in your IDE. Our team of developers are making changes everyday to make the most effective and efficient micro-repo for you.