Only React has controlled inputs

3 min read
~440 words
NO DATE
DRAFT

TODO: intro

What are "controlled inputs"?

Whether an input (or more generally a component) is "controlled" or "uncontrolled" depends on where its state is managed.

Controlled components have their state managed by their consumers, and passed down as props. This means that the consumer of a controlled component can force it into a specific state. For <input>s this often looks like a value prop and an onChange event listener:

jsx
import { useState } from 'react'
 
export function ControlledInputExample() {
  const [value, setValue] = useState('')
 
  // use `value` however you want here!
 
  return (
    <input
      // we're setting the input's value here
      value={value}
      onChange={event => {
        setValue(event.currentTarget.value)
      }}
    ></input>
  )
}

Uncontrolled components manage their state internally, and consumers cannot easily force the component into a specific state. Sometimes, you'll be able to set default values, e.g. using the defaultValue prop on <input>s, and often uncontrolled components will emit events to expose some or all of their internal state, e.g. onChange. You can also access the values of native inputs using refs or by using FormData on a surrounding form.

How are controlled inputs different across frameworks?

Let's look at how the following MessageInput component behaves across frameworks:

typescriptjavascript
import { useState } from 'react'
 
function removeVowels(message) {
  return message.replace(/[aeiou]/gi, '')
}
 
export default function MessageInput() {
  const [message, setMessage] = useState('rhythm')
 
  const cleanedMessage = removeVowels(message)
 
  return (
    <>
      <input
        value={cleanedMessage}
        onChange={event => {
          setMessage(event.currentTarget.value)
        }}
      ></input>
    </>
  )
}
import { useState } from 'react'
 
function removeVowels(message) {
  return message.replace(/[aeiou]/gi, '')
}
 
export default function MessageInput() {
  const [message, setMessage] = useState('rhythm')
 
  const cleanedMessage = removeVowels(message)
 
  return (
    <>
      <input
        value={cleanedMessage}
        onChange={event => {
          setMessage(event.currentTarget.value)
        }}
      ></input>
    </>
  )
}

The component implemented across frameworks, in CodeSandbox

React

  • All frameworks update the input's value when
  • React forces the input's value to match the prop when it rerenders and after event handlers have run

How does React do it?

Opinion time!

Is what React does a good thing?

  • forcing inputs' values to match their value props makes things more intuitive
  • not how things work in general
  • but react does patch over other things to make things a bit easier / more intuitive, e.g.
    • oninput, onchange, etc -> onChange
    • whitespace in jsx isn't preserved - only in the strings you add, unlike in html
  • this makes things easier, but could lead to people becoming "React" developers vs web developers - i.e. people learning the framework instead of learning the platform
Found a mistake, or want to suggest an improvement? Source on GitHub here
and see edit history here