Search code examples
javascriptreactjsonkeyup

React - State not updated or getting reset to old state


I am trying to build a chat application with the functionality of input field which can be used as filter for chat_groups array which is in the state as chat_groups. Here is how my code looks:

    constructor(props) {
    super(props);
    this.state = {
      currUserId: "--id--",
      chats: [],
      chat_groups: [],
      users: [],
    };
  }
  .
  .
  .
  <input
className="chat-group__search__input"
placeholder="Search for group..."
onChange={(ev) => {
    console.log(ev.currentTarget.value);
    var thatState = this.state;
    thatState.chat_groups = thatState.chat_groups.map(
    (gp) => {
        gp["visible"] = gp.group_name
        .toLowerCase()
        .includes(ev.currentTarget.value);
        return gp;
    }
    );
    // getting correct state in thatState variable
    this.setState(thatState);
}}
/>
// getting old state in setState callback and componentDidUpdate lifecycle

The weird problem is I am getting the correct value in thatState variable before setting state. But after setState function is called, if I try to check the state in setState callback or componentDidUpdate lifecycle, I am getting the old state only.

I tried that for keydown and change events also. So, seems to be less of an issue of event as well.

I would like to know if some issue in the code is evident or there is something that I can do to debug the issue.

Edit: After changes, my current onChange looks as below, but the issue is still there; the setState function does not seem to change the state as I can see only the old state in componentDidUpdate lifecycle and setState callback.

 onChange={(ev) => {
                      console.log(ev.currentTarget.value);
                      let chat_groups = this.state.chat_groups.map((gp) => ({
                        ...gp,
                        visible: gp.group_name
                          .toLowerCase()
                          .includes(ev.currentTarget.value),
                      }));
                      console.log(
                        "Before",
                        chat_groups.map((gp) => gp.visible)
                      );
                      this.setState({ chat_groups: chat_groups });
                    }}

Solution

  • The problem is that you are mutating the state.

    When you do var thatState = this.state; the reference is still the same for both the objects. So automatically when you update thatState.chat_groups you are updating/mutating state as well.

    Change your onChange method to like below

    onChange = ev => {
      console.log(ev.currentTarget.value);
      let { chat_groups } = this.state;
      chat_groups = chat_groups.map(gp => ({
          ...gp,
          visible: gp.group_name.toLowerCase().includes(ev.currentTarget.value)
      }));
    
      this.setState(state => ({
        ...state,
        chat_groups
      }));
    };
    //Other code
    //....
    //....
    <input
      className="chat-group__search__input"
      placeholder="Search for group..."
      onChange={this.onChange} />
    
    

    I suspect there's one problem while checking the group_name with the input value i.e., you are converting the group_name to lower case using gp.group_name.toLowerCase() but the input value you are not converting to lower case. This could be one issue why the visible attribute value is not getting updated. So in the below snippet I have converted the input value also to lower case while comparing.

    Here, below is a runnable snippet with your requirement. Doing console.log in the setState's callback function and the state is getting updated.

    class App extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          currUserId: "--id--",
          chats: [],
          chat_groups: [{
            group_name: "Group One"
          }, {
            group_name: "Group Two"
          }],
          users: []
        }
      }
    
      onChange = ev => {
        console.log(ev.currentTarget.value);
        let {
          chat_groups
        } = this.state;
        chat_groups = chat_groups.map(gp => ({
          ...gp,
          visible: gp.group_name.toLowerCase().includes(ev.currentTarget.value.toLowerCase())
        }));
        this.setState(state => ({
          ...state,
          chat_groups
        }), () => { console.log(this.state.chat_groups); });
      };
      
      render() {
        return <input 
          placeholder="Search for group..."
          onChange={this.onChange} />
      }
    }
    
    ReactDOM.render(<App />, document.getElementById("react"));
    .as-console-wrapper {
      max-height: 100% !important;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
    <div id="react"></div>