React: input checkbox not updating after re-render

Created on 25 Aug 2018  ·  22Comments  ·  Source: facebook/react

React version: 16.4.2

The checkbox in the DOM is not checked after a state change.

constructor(props) {
    super(props);

    this.state = {
        checked: false
    }

componentDidMount() {
    this.setState({
        checked: this.props.checked
    });
}

render() {
    let checked = <input type='checkbox' onChange={() => {
        this.setState({checked: !this.state.checked});
    }} defaultChecked={this.state.checked} />

    return (
         // JSX
     )
}

Console logging checked, the first render returns a react.element type: 'input" with the following props

props:
    defaultChecked: false

Second render after state change

props:
    defaultChecked: true

However, in my app, the checkbox is not checked. If I explicit set the defaultChecked property to true, then it will be checked. If I default the state.checked to true, it will be checked. I've tried removing the onChange listener, suspecting that that may be the culprit, but it's not.

My current workaround

constructor(props) {
    super(props);

    if (this.props.checked) {
        this.state = {
             checked: true
        }
    } else {
        this.state = {
             checked: false
        }
    }
}

Feels messy.

Most helpful comment

Also getting the same issue with checked attribute set to state, state updating, and not updating DOM. The DOM updates (reflecting the new checked value) as soon as I change anything else in the state. This happens on my project for checkbox and radio input.

I'm using a fully controlled input in both cases. The state variable is updated in React dev tools, and I also printed out the updated value before render. It just seems the DOM won't update in this particular case.

Unfortunately I couldn't repro this on codepen so there must be some way my project is set up (along with the other people reporting this) that is triggering this effect.

UPDATE
I figured out that I had a parent component that had an onClick that was calling evt.preventDefault(). I removed that call and the checkbox and radio inputs now update in the DOM when state is updated. I'm not sure why preventDefault would prevent a render of the DOM in this specific case, but I'm also fairly new to React and don't know the inner workings. Is anyone able to shed some light on this?

Here's a Code Sandbox demonstrating the issue: https://codesandbox.io/s/v67qwj1870

All 22 comments

try:

static getDerivedStateFromProps(props) { return { editingQuark: checked:props.checked }; }

I think you’re confused about how props and state works.

If you want checked to be controlled by the parent you should remove it from the local state. Don’t try to “sync” props and state — instead, let the component accept checked and onChange as props from the parent that owns the state, and pass them down.

render() {
  return <input onChange={this.props.onChange} checked={this.props.checked} />
}

Please read the documentation about lifting state up:

https://reactjs.org/docs/lifting-state-up.html

Similarly, despite the suggestion in this thread, you don’t want to use getDerivedStateFromProps for this. Please read our blog post: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

Hope this helps!

Are you saying that after a state change, it is intended that a checkbox stays unchecked even if its value is true? (proven through console log). The state returns true, but the checkbox is unchecked. When checking the checkbox, the state changes to false. But the checkbox checked is from the state (true).

The parent cannot pass down every props for every onChange, onClick, etc. If the input props is to be controlled by the parent, how do we capture changes to inputs in a form component?

Think of a form where user can create as new or edit. If they edit, it will populate the form with values passed from the parent. But you cannot just set the values of the form with the props, or else the value will never change if the user tries to edit.

For example

<input type='text' value={this.props.value} onChange={(e) => {this.setState({value: e.target.value})}} />

The state may be updated, but the value of the input will always remain this.props.value.

If the form is not mounted from an edit, it's a blank form. So a default value is needed if no props is passed in from the parent. And this is why a local state is needed.

this.state = {
    name: '',
    age: '',
    male: false,
    female: false
}

componentDidMount() {
    if (this.props.forms) {
        this.setState({this.props.forms});
    }
    // this.props.forms being { name: 'John', age: '21', male: true, female: false }
}

<input type='text' name='name' value={this.state.name} onChange={(e) => {this.setState({name: e.target.value})}} />

<input type='text' name='age' value={this.state.age} onChange={(e) => {this.setState({age: e.target.value})}} />

<input type='checkbox' name='male' onChange={(e) => {this.setState({male: !this.state.male})}} checked={this.state.male} />

I hope this makes sense and for now, I will use the workaround.

The state may be updated, but the value of the input will always remain this.props.value.

Which is why normally the parent wouldn't just pass value as a prop, but onChange too.

<input type='text' value={this.props.value} onChange={(e) => {this.props.onChange({value: e.target.value})}} />

This is becoming very hard to discuss without a runnable example. If you create something in CodeSandbox or similar I can help. Otherwise I'm afraid we're going to keep talking past one another.

To sum it up again. Components in React can be either:

  • Fully uncontrolled — in this case your custom <Input> would take defaultChecked as a prop, and pass it to DOM <input>. The state would be inside the <Input> component itself. The prop value would only ever be used once, when the component is first rendered, and then ignored. The parent would have no way to "force" that state to become something else. If you ever need to reset the input, you'd have to mount it with a different key.

  • Fully controlled — in this case your custom <Input> would receive both checked and onChange as props, and pass them down to the DOM <input>. In this scenario, <Input> wouldn't have any state at all. Instead, the <Parent> would need to manage its state. If you have many inputs the <Parent>, and writing a separate handleChange handler for each of them is annoying, you can use the same handler for them.

Don't try to mix controlled and uncontrolled paradigms in one component. That only makes you confused. If you receive something as a prop, don't try to put it in state and then "sync" them. Instead, either keep it fully uncontrolled, or lift the shared state up and remove it from the child.

Hope this helps.

I can't reproduce the error on codepen and the error went away when I try to replicate on what I did yesterday. I love programming...

But thanks, as this has been a great learning experience.

I think I have a similar problem with a _fully controlled_ checkbox that updates its checked value according to the React dev tools, but doesn't update the DOM.

@osartun would you believe it, I have encountered the same error as you for the first time, today.

I have a fully controlled input which seems to be receiving updates in React dev tools, but not in the DOM.

@prmichaelsen For me it got resolved just as randomly as it occurred. I think I wrapped the stuff in another context consumer that I needed and then the bug stopped occurring, but I'm not entirely sure. Either way this unpredictability wasn't very reassuring. 😬

Also running into this. checked value is true yet DOM does not reflect this.

+1
I am seeing the same issue, checked value is true, yet DOM does not reflect it,

+1, same here

Also getting the same issue with checked attribute set to state, state updating, and not updating DOM. The DOM updates (reflecting the new checked value) as soon as I change anything else in the state. This happens on my project for checkbox and radio input.

I'm using a fully controlled input in both cases. The state variable is updated in React dev tools, and I also printed out the updated value before render. It just seems the DOM won't update in this particular case.

Unfortunately I couldn't repro this on codepen so there must be some way my project is set up (along with the other people reporting this) that is triggering this effect.

UPDATE
I figured out that I had a parent component that had an onClick that was calling evt.preventDefault(). I removed that call and the checkbox and radio inputs now update in the DOM when state is updated. I'm not sure why preventDefault would prevent a render of the DOM in this specific case, but I'm also fairly new to React and don't know the inner workings. Is anyone able to shed some light on this?

Here's a Code Sandbox demonstrating the issue: https://codesandbox.io/s/v67qwj1870

I found an old thread that covers a bit more on this as I also ran into the same issue today https://github.com/facebook/react/issues/3446

For those with this problem, as @a1theredbull said, do not include preventDefault on the change function:

function onCheckedChanged(e)
  {
    //e.preventDefault();  <-- Remove this, it will mute the event.
    folder.setSelected(e.target.checked);
  }

For those with this problem, as @a1theredbull said, do not include preventDefault on the change function:

function onCheckedChanged(e)
  {
    //e.preventDefault();  <-- Remove this, it will mute the event.
    folder.setSelected(e.target.checked);
  }

this works.

I'm seeing this problem as well, there's no preventDefault call yet the checkbox does not reflect the state true/false for checked attribute.

@benskz did you manage to solve this?

Make sure the value you’re passing into checked is not undefined. If it is undefined, then the component will be uncontrolled, even if you have a valid onChange (form3 in codepen link)

Also, as stated by other people, make sure you’re not calling evt.preventDefault() in your event handler (form4 in codepen link)

Examples codepen: https://codepen.io/mrcoles/pen/WNbGwVw

@a1theredbull You are a life-saver! Thanks a lot! 👍👍👍👍

UPDATE
I figured out that I had a parent component that had an onClick that was calling evt.preventDefault(). I removed that call and the checkbox and radio inputs now update in the DOM when state is updated. I'm not sure why preventDefault would prevent a render of the DOM in this specific case, but I'm also fairly new to React and don't know the inner workings. Is anyone able to shed some light on this?

Here's a Code Sandbox demonstrating the issue: https://codesandbox.io/s/v67qwj1870

That was my issue exactly. For those, who wonder, why a parent component would have a preventDefault on onClick: My HTML designer created a clickable card, with product number, few details and checkbox to allow comparison of multiple products. However, whole card was designed as a parent <a/> with divs and checkbox inside. And clicking the checkbox would actually trigger the link.

I had no time to fiddle around with the HTML, so I simply added the onClick preventDefault on the checkbox's parent div.

I found the solution in the thread linked above: use stopPropagation instead of preventDefault.

Was this page helpful?
0 / 5 - 0 ratings