简体   繁体   中英

React state variable updates automatically without calling setState

I am facing the following issue and not able to figure it out.

I have two variables inside the state called userDetails & userDetailsCopy. In componentDidMount I am making an API call and saving the data in both userDetails & userDetailsCopy.

I am maintaining another copy called userDetailsCopy for comparison purposes.

I am updating only userDetails inside setState but even userDetailsCopy is also getting updated instead of have old API data.

Below is the code:

constructor(){
    super()
    this.state={
        userDetails:{},
        userDetailsCopy: {}
    }
}

componentDidMount(){
     // API will return the following data
       apiUserDetails : [
            {
                 'name':'Tom',
                 'age' : '28'
            },
            {
                 'name':'Jerry',
                 'age' : '20'

            }
        ]

       resp.data is nothing but apiUserDetails
    /////

     apiCall()
     .then((reps) => {
         this.setState({ 
             userDetails: resp.data,
             userDetailsCopy: resp.data
         })
     })
}

updateValue = (text,i) => {
     let userDetail = this.state.userDetails
     userDetail[i].name = text
     this.setState({ 
         userDetails: userDetail
     })
}

submit = () => {
     console.log(this.state.userDetials) // returns updated values
     console.log(this.state.userDetailsCopy) // also return updated values instead of returning old API data
}

Need a quick solution on this.

The problem with this is that you think you are making a copy of the object in state by doing this

     let userDetail = this.state.userDetails
     userDetail.name = text

But, in Javascript, objects are not copied like this, they are passed by referrence. So userDetail at that point contains the referrence to the userDetails in your state, and when you mutate the userDetail it goes and mutates the one in the state.

ref: https://we-are.bookmyshow.com/understanding-deep-and-shallow-copy-in-javascript-13438bad941c

To properly clone the object from the state to your local variable, you need to instead do this:

let userDetail = {...this.state.userDetails}

OR

let userDetail = Object.assign({}, this.state.userDetails)

Always remember, Objects are passed by referrence not value.

EDIT: I didn't read the question properly, but the above answer is still valid. The reason userDetailCopy is being updated too is because resp.data is passed by referrence to both of them, and editing any one of them will edit the other.

React state and its data should be treated as immutable.

From the React documentation :

Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.

Here are five ways how to treat state as immutable:

Approach #1: Object.assign and Array.concat

updateValue = (text, index) => {
  const { userDetails } = this.state;

  const userDetail = Object.assign({}, userDetails[index]);

  userDetail.name = text;

  const newUserDetails = []
    .concat(userDetails.slice(0, index))
    .concat(userDetail)
    .concat(userDetails.slice(index + 1));

  this.setState({
    userDetails: newUserDetails
  });
}

Approach #2: Object and Array Spread

updateValue = (text, index) => {
  const { userDetails } = this.state;

  const userDetail = { ...userDetails[index], name: text };

  this.setState({
    userDetails: [
      ...userDetails.slice(0, index),
      userDetail,
      ...userDetails.slice(index + 1)
    ]
  });
}

Approach #3: Immutability Helper

import update from 'immutability-helper';

updateValue = (text, index) => {
  const userDetails = update(this.state.userDetails, {
    [index]: {
      $merge: {
        name: text
      }
    }
  });

  this.setState({ userDetails });
};

Approach #4: Immutable.js

import { Map, List } from 'immutable';

updateValue = (text, index) => {
  const userDetails = this.state.userDetails.setIn([index, 'name'], text);

  this.setState({ userDetails });
};

Approach #5: Immer

import produce from "immer";

updateValue = (text, index) => {
  this.setState(
    produce(draft => {
        draft.userDetails[index].name = text;
    })
  );
};

Note: Option #1 and #2 only do a shallow clone. So if your object contains nested objects, those nested objects will be copied by reference instead of by value. So if you change the nested object, you'll mutate the original object.

To maintain the userDetailsCopy unchanged you need to maintain the immutability of state (and state.userDetails of course).

 function getUserDerails() { return new Promise(resolve => setTimeout( () => resolve([ { id: 1, name: 'Tom', age: 40 }, { id: 2, name: 'Jerry', age: 35 } ]), 300 )); } class App extends React.Component { state = { userDetails: [], userDetailsCopy: [] }; componentDidMount() { getUserDerails().then(users => this.setState({ userDetails: users, userDetailsCopy: users })); } createChangeHandler = userDetailId => ({ target: { value } }) => { const { userDetails } = this.state; const index = userDetails.findIndex(({ id }) => id === userDetailId); const userDetail = {...userDetails[index], name: value }; this.setState({ userDetails: [...userDetails.slice(0, index), userDetail, ...userDetails.slice(index + 1) ] }); }; render() { const { userDetails, userDetailsCopy } = this.state; return ( <React.Fragment> {userDetails.map(userDetail => ( <input key={userDetail.id} onChange={this.createChangeHandler(userDetail.id)} value={userDetail.name} /> ))} <pre>userDetails: {JSON.stringify(userDetails)}</pre> <pre>userDetailsCopy: {JSON.stringify(userDetailsCopy)}</pre> </React.Fragment> ); } } ReactDOM.render( <App />, document.getElementById("root") );
 <div id="root"></div> <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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM