ReactJS Input Elements: to useRef or to useState?

ReactJS Input Elements: to useRef or to useState?

What are controlled and uncontrolled form components? What are their use cases? How should you use the useRef or the useState React hooks?

When it comes to handling input elements in React, how do you choose whether to use controlled or uncontrolled components? If you’ve ever wondered what the difference is between useRef and useState and how to apply them, this article is here to clear the air.

Do you know what controlled or uncontrolled components are? Maybe we should start from there.

Imagine two entertainment shows. One is a puppet show where every character’s move is orchestrated by a master puppeteer. The other is performed by free-spirited artists, in person, on stage. In technical terms, uncontrolled components refer to form elements that store their own state internally, rather than relying on React's state management, unlike controlled components.

Uncontrolled Components: The Free Spirit

Uncontrolled components are like free-spirited artists, doing their thing without any strings attached. With uncontrolled components, a form input element maintains its state internally. We can obtain the value of the element when needed using a technique called “ref”. The example below is a React component that renders a form containing email and password input.

import { useRef } from 'react';

const FormComponent = () => {
  const emailInputRef = useRef(null);
  const passwordInputRef = useRef(null);

  const handleSubmit = (event) => {
    event.preventDefault();

    const email = emailInputRef.current.value;
    const password = passwordInputRef.current.value;

    // Perform further actions like sending data to the backend or database

    // Clear the form elements
    event.target.reset();
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>Email:</label>
      <input type='email' name='email' ref={emailInputRef} />

      <label>Password:</label>
      <input type='password' name='password' ref={passwordInputRef} />

      <button type='submit'>Submit</button>
    </form>
  );
};

export default FormComponent;

In the code above, we use the useRef hook to create a reference to the input elements. The handleSubmit function retrieves the value from the input element by accessing the value property of the inputRef.current. This approach allows us to fetch the value at any given time without relying on React's state management. After submitting the form, the elements are reset to null using event.target.reset().

Add some CSS to the code above and we would have a form that looks like the image below.

Form containing email, password and submit button.

For uncontrolled components, data is handled by the DOM.

Controlled Components: The Puppet Master

Controlled components are like puppets in a show where every move is orchestrated by a master puppeteer. With controlled components, the state of the input element is managed by React itself. We define the value of the input element and handle changes through state updates. The example we gave earlier can be written thus:

import { useState } from 'react';

const initialState = {
  email: '',
  password: '',
};

const FormComponent = () => {
  const [formState, setFormState] = useState(initialState);

  const handleSubmit = (event) => {
    event.preventDefault();

    // Perform further actions like sending data to the backend or database

    // Clear the form elements
    setFormState(initialState);
  };

  const handleChange = (event) => {
    const { name, value } = event.target;
    setFormState((previousState) => ({ ...previousState, [name]: value }));
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>Email:</label>
      <input type='email' name='email' value={formState.email} onChange={handleChange} />

      <label>Password:</label>
      <input type='password' name='password' value={formState.password} onChange={handleChange} />

      <button type='submit'>Submit</button>
    </form>
  );
}

export default FormComponent;

In the code above, we define a single-state object formState that contains initial values for email and password. The handleChange function is responsible for updating the state each time the user types a new character in the input.

By assigning the name attribute to each input element, we can use it as the key to update the corresponding property in the state object within the handleChange function. This allows us to handle changes for all inputs with a single event handler. The handle change function uses the spread operator to merge the existing state with the updated property, ensuring that the rest of the state remains unchanged. Notice the setFormState(initialState) method used to reset the elements.

This code simplifies form management by centralizing the state and the event handling logic, making it more concise and easier to maintain.

For controlled components, data is handled by the React component state.

Sometimes, we may need to use the value of an input element in another element. For instance, disabling the submit button when no email address has been entered. This can be implemented when we use a controlled form element by just fetching the email address from the state, like in the code snippet below. In the uncontrolled component example, this cannot be achieved.

<button type={!formState.email} type='submit'>Submit</button>

Conclusion

  1. If you ever need to update or use the value of an input element from anywhere outside the element, use a controlled component with the React setState method.

  2. When you have more than 2 elements, it is more efficient to use controlled components to have clean, well-structured and centralised state management.

  3. Controlled components are the ReactJS recommended way to handle form elements. It gives you access to some advance React features and the React DevTools.

  4. The file input, <input type="file" /> is a read-only element and it's value cannot be set programmatically. Therefore, use uncontrolled components in this case.

In the end, the choice between controlled and uncontrolled components, and whether to use refs or not, depends on your specific needs and the nature of your project. Obrigado!

Thank you for reading till the very end. If you have any questions, please drop them in the comments. Follow me on LinkedIn, Twitter and GitHub @uniqcoda. Check out my YouTube channel for more front-end development content.