skip to content
Samuel Edwin's Website

Controlled vs uncontrolled React components

/ 4 min read

When you learn deeper about React, you will eventually hear about controlled and uncontrolled components.

In this post we will learn about:

  • The difference between controlled and uncontrolled components.
  • When to prefer one over the other and why it matters.

The “only” difference

It all comes down to one thing: how the component does state management.

Although that’s the only difference, it has a big impact on how we use the component.

Let’s take a look on how native <input>s are used in a controlled vs uncontrolled way.

Controlled input

function MyForm() {
const [name, setName] = useState('')
return (
<form>
<input
type="text"
onChange={(event) => {
setName(event.target.value)
}} />
</form>
)
}

You know you’re using a controlled component when you:

  • useState to store the component value every time it changes.
  • Pass the value back to the component to display the updated value.

This is how you use the input in a controlled way, which has several implications explained below.

Where the state is managed

MyForm is responsible for managing the state of the input.

The name state defines the displayed content of the input.

Every time the input value is changed, MyForm needs to call setName to keep name up to date.

Reading the input value

When you want to validate or submit the form data, you can use the name state to get its current value.

const [name, setName] = useState('')
function onSubmit(e) {
e.preventDefault()
if (name.length === 0) {
alert('Name must not be empty')
}
}

Changing input value

You can set the name to any value at any time you want

return (
<form>
<input type="text" onChange={(event) => { setName(event.target.value) }} />
<button onClick={
() => setName(getRandomName())
}>Set random name</button>
</form>
)

Performance

Everytime you set the name, re-renders will happen.

As an example, I’ll add a console log to get notified when re-renders happen.

function MyForm() {
const [name, setName] = useState('')
console.log('name =', name)
return (
<form>
<input type="text" onChange={(event) => { setName(event.target.value) }} />
</form>
)
}

This is what we’ll see in the console if I type “Samuel” in the input box

name = S
name = Sa
name = Sam
name = Samu
name = Samue
name = Samuel

When using controlled components in a complex React component, be mindful about re-renders.

Try it yourself using this sandbox below.

Uncontrolled input

Now let’s take a look at how uncontrolled input works.

function MyForm(props) {
return (
<form>
<input type="text" defaultValue={props.name}>
</form>
)
}

defaultValue is set instead of value.

value must not be set otherwise the component will count as a controlled input.

Where the state is managed

The parent component doesn’t need to do anything.

No need to use state to store its value and pass it back to the input.

The uncontrolled component is responsible for managing its own state.

Reading the input value

There are two ways to read the input value

Use ref to get the reference to the input component

const ref = useRef()
function handleSubmit(e) {
e.preventDefault()
const name = ref.current?.value
}
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={ref} />
</form>
)

Use ref to store the input value

const ref = useRef()
function handleSubmit(e) {
e.preventDefault()
const name = ref.current
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => ref.current = e.target.value } />
</form>
)

Note: While it is possible to store the value in useState, it will trigger a lot of re-renders. This is the same drawback we get from using controlled components.

Changing the input value

We only have one chance to set the text displayed by the input, when the component is mounted through the defaultValue prop.

There is no way to set the input value after the component is mounted.

Note: It is possible to “cheat” this by using refs to change the input value, but in general it’s impossible for most custom React components.

Performance

If we do console logs like previously, no re-renders will happen at the parent component.

Uncontrolled components tend to have better performance by default.

Now try the sample below and compare it with the controlled inputs.

Open source samples

MUI supports both controlled and uncontrolled mode for its input components: Rating, Autocomplete, Select, Slider and many more.

Although I haven’t checked in other UI libraries, I’m pretty sure a lot of them will have both controlled and uncontrolled modes.

Controlled vs uncontrolled: which one to use?

This is how I decide whether to use controlled vs uncontrolled inputs.

Do I need to change the component’s value from the parent component after mount?

I’ll use controlled component if the answer is yes, otherwise uncontrolled component would be my default choice because of performance reason.

What’s next?

In the next next post, you can learn how to build a custom component that supports controlled and uncontrolled mode.