0

I've read this post: React setState not Updating Immediately

and realized that setState is async and may require a second arg as a function to deal with the new state.

Now I have a checkbox

class CheckBox extends Component {
    constructor() {
        super();
        this.state = {
            isChecked: false,
            checkedList: []
        };
        this.handleChecked = this.handleChecked.bind(this);
    }

    handleChecked () {
        this.setState({isChecked: !this.state.isChecked}, this.props.handler(this.props.txt));
    }

    render () {
        return (
            <div>
                <input type="checkbox" onChange={this.handleChecked} />
                {`   ${this.props.txt}`}
            </div>
            )
    }
}

And is being used by another app

class AppList extends Component {
    constructor() {
        super();
        this.state = {
            checked: [],
            apps: []
        };
        this.handleChecked = this.handleChecked.bind(this);
        this.handleDeleteKey = this.handleDeleteKey.bind(this);
    }
    handleChecked(client_id) {
        if (!this.state.checked.includes(client_id)) {
            let new_apps = this.state.apps;
            if (new_apps.includes(client_id)) {
                new_apps = new_apps.filter(m => {
                    return (m !== client_id);
                });
            } else {
                new_apps.push(client_id);
            }
            console.log('new apps', new_apps);
            this.setState({apps: new_apps});
            // this.setState({checked: [...checked_key, client_id]});
            console.log(this.state);
        }
    }
    render () {
        const apps = this.props.apps.map((app) =>
            <CheckBox key={app.client_id} txt={app.client_id} handler={this.handleChecked}/>
        );

        return (
            <div>
                <h4>Client Key List:</h4>
                {this.props.apps.length > 0 ? <ul>{apps}</ul> : <p>No Key</p>}
            </div> 
        );
    }


}

So every time the checkbox status changes, I update the this.state.apps in AppList

when I console.log new_apps, everything works accordingly, but console.log(this.state) shows that the state is not updated immediately, which is expected. What I need to know is how I can ensure the state is updated when I need to do further actions (like register all these selected strings or something)

JChao
  • 2,178
  • 5
  • 35
  • 65
  • Function `this.setState()` is async! but as second argument you can invoke a callback, try with `this.setState({ apps: new_apps }, _ => console.log(this.state)` – Carmelo Catalfamo Sep 07 '18 at 15:47
  • This is well covered in the react docs, and the answers in the question you linked to. – user229044 Sep 07 '18 at 15:50
  • @meagar I couldn't understand why my console.log wasn't outputing the right answer even after I used a callback function in checkbox. That's why this post – JChao Sep 07 '18 at 15:52

3 Answers3

5

setState enables you to make a callback function after you set the state so you can get the real state

this.setState({stateYouWant}, () => console.log(this.state.stateYouWant))

in your case:

this.setState({apps: new_apps}, () => console.log(this.state))
Ricardo Costa
  • 704
  • 6
  • 27
  • so what you're saying is that the state is actually changed successfully. I'm just `console.log`ing at the wrong place? – JChao Sep 07 '18 at 15:49
  • 2
    What i'm saying is both execute at the same time, so console.log has the old state, it will always be one state behind if you get what i mean, by doing this we only log after the state was changed (callback is usually a function that is called after a certain operation was complete, in this case the setState) – Ricardo Costa Sep 07 '18 at 15:50
  • If it fixed your problem choose a correct answer so other people that see this can resolve it easily – Ricardo Costa Sep 07 '18 at 15:52
  • 1
    I'll do it once I can. stackoverflow doesn't allow correct answer to be chosen within 5 mins – JChao Sep 07 '18 at 15:53
2

The others have the right answer regarding the setState callback, but I would also suggest making CheckBox stateless and pass isChecked from MyApp as a prop. This way you're only keeping one record of whether the item is checked, and don't need to synchronise between the two.

rooch84
  • 624
  • 5
  • 13
  • just curious, right now I'm setting `isChecked` to be `!isChecked` when checkbox changes status. If I process it as a prop, how do I keep track of this status? – JChao Sep 07 '18 at 15:56
  • 1
    You have to create a function that modifies the state and pass via props to the other component and then call it there – Ricardo Costa Sep 07 '18 at 15:58
2

Actually there shouldn't be two states keeping the same thing. Instead, the checkbox should be stateless, the state should only be kept at the AppList and then passed down:

const CheckBox = ({ text, checked, onChange }) => 
        (<span><input type="checkbox" checked={checked} onChange={() => onChange(text)} />{text}</span>);
        
class AppList extends React.Component {
  constructor() {
    super();
    this.state = { 
      apps: [
        {name: "One", checked: false },
        { name: "Two", checked: false }
      ], 
    };
  }
  
  onChange(app) {
    this.setState(
      previous => ({ 
         apps: previous.apps.map(({ name, checked }) => ({ name, checked: checked !== (name === app) })),
      }),
      () => console.log(this.state)
    );
  }
  
  render() {
    return <div>
     {this.state.apps.map(({ name, checked }) => (<CheckBox text={name} checked={checked} onChange={this.onChange.bind(this)} />))}
    </div>;
  }
}

ReactDOM.render(<AppList />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • In general I like the approach you suggested, but the xor thing is overly clever (clever code is bad code). I'd suggest rewriting it to be more straightforward. – Jeff M Sep 07 '18 at 16:05
  • @jeff I'm not sure if a nested comparison is better ... what would you suggest for "straightforward" ? – Jonas Wilms Sep 07 '18 at 16:11
  • I get `input is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`.` as an error. Something in `` creating this problem? – JChao Sep 07 '18 at 17:38
  • @JonasWilms I would use a ternary. `checked: name === app ? !checked : checked`. That is, if this is the app we want to toggle, then toggle, else leave as is. I think this is more clear and obvious to future readers, and less likely to be screwed up by the writer. – Jeff M Sep 07 '18 at 17:42