user2839636
user2839636

Reputation: 3

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 });
                    }}

Upvotes: 0

Views: 2044

Answers (4)

kabirbaidhya
kabirbaidhya

Reputation: 3490

State should only be updated using the setState method.

You are mutating the state directly in your code above - this isn't recommended. You would get unexpected results and it's not predictable.

This is how you should do it - create a new updated object and pass it to the setState.

onChange={(ev) => {
  console.log(ev.currentTarget.value);

  const updatedChatGroups = this.state.chat_groups.map((gp) => {
    const visible = gp.group_name.toLowerCase().includes(ev.currentTarget.value);

    return {
      ...gp,
      visible,
    };
  });

  // Update the modified object using this.setState().
  this.setState({ chat_groups: updatedChatGroups });
}}

Read More

Upvotes: 0

Nithish
Nithish

Reputation: 5999

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>

Upvotes: 1

Yannick Vermeulen
Yannick Vermeulen

Reputation: 148

Seems like you are trying to manipulate state directly which is a big no in React.

onChange={(ev) => {
    this.setState({
        chat_groups: this.state.chat_groups.map((gp) => {
            gp["visible"] = gp.group_name
            .toLowerCase()
            .includes(ev.currentTarget.value);
            return gp;
        })
    });
}}

Upvotes: 0

Vaibhav Rai
Vaibhav Rai

Reputation: 168

No, don't do this var thatState = this.state it's just an object it will easily get mutate and you will not get the update as for react state change never occured.

instead do this var { chat_groups } = this.state and then use it further and inlast set the state this.setState({ chat_groups: chat_groups }) also try to avoid the mutation as much as possible means copy the values of chat_groups also

Upvotes: 0

Related Questions