why calling setstate method doesn't mutate the state immediately?

  • Last Update :
  • Techknowledgy :

setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value. There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains. Why calling react setState method doesn't mutate the state immediately? Michael Parker mentions passing a callback within the setState. Another way to handle the logic after state change is via the componentDidUpdate lifecycle method, which is the method recommended in React docs.

This is our Splunktool team suggestion, we tried and its working fine ✌
this.setState({
   pencil: !this.state.pencil
}, myFunction)​

I'm reading Forms section of documentation and just tried this code to demonstrate onChange usage (JSBIN).

var React= require('react');

var ControlledForm= React.createClass({
    getInitialState: function() {
        return {
            value: "initial value"
        };
    },

    handleChange: function(event) {
        console.log(this.state.value);
        this.setState({value: event.target.value});
        console.log(this.state.value);

    },

    render: function() {
        return (
            <input type="text" value={this.state.value} onChange={this.handleChange}/>
        );
    }
});

React.render(
    <ControlledForm/>,
  document.getElementById('mount')
);

If you want a function to be executed after the state change occurs, pass it in as a callback.

this.setState({
   value: event.target.value
}, function() {
   console.log(this.state.value);
});

Suggestion : 2

If you’ve tried it out, you might’ve noticed nothing bad happened. If you modify state directy, call this.setState({}) or even this.forceUpdate(), then everything might appear to be just fine. Everybody says don’t do it. Never mutate state directly, always call setState. So there you go: that’s why you shouldn’t mutate state, even if you immediately call setState. Optimized components might not re-render if you do, and the rendering bugs will be tricky to track down.

1._
this.state.cart.push(item.id);
this.setState({
   cart: this.state.cart
});
// renders like normal! maybe?
2._
class ItemList extends React.PureComponent {
  render() {
    return (
      <ul>
        {this.props.items.map(item => <li key={item.id}>{item.value}</li>)}
      </ul>
    );
  }
}
3._
class App extends Component {
  // Initialize items to an empty array
  state = {
    items: []
  };

  // Initialize a counter that will increment
  // for each item ID
  nextItemId = 0;

  makeItem() {
    // Create a new ID and use
    // a random number as the value
    return {
      id: this.nextItemId++,
      value: Math.random()
    };
  }

  // The Right Way:
  // copy the existing items and add a new one
  addItemImmutably = () => {
    this.setState({
      items: [...this.state.items, this.makeItem()]
    });
  };

  // The Wrong Way:
  // mutate items and set it back
  addItemMutably = () => {
    this.state.items.push(this.makeItem());
    this.setState({ items: this.state.items });
  };

  render() {
    return (
      <div>
        <button onClick={this.addItemImmutably}>
          Add item immutably (good)
        </button>
        <button onClick={this.addItemMutably}>Add item mutably (bad)</button>
        <ItemList items={this.state.items} />
      </div>
    );
  }
}

Suggestion : 3

No matter how many setState() calls are in the handleClick event handler, they will produce only a single re-render at the end of the event, which is crucial for maintaining good performance in large applications. The order of requests for updates is always respected; React will always treat the first update requests first. The second parameter to setState() is an optional callback function. This argument will be executed once setState() is completed and the component is re-rendered. The callback function is guaranteed to run after the state update has been applied: State updates in React are asynchronous; when an update is requested, there is no guarantee that the updates will be made immediately. The updater functions enqueue changes to the component state, but React may delay the changes, updating several components in a single pass.

For example, consider the code below:

//React
const handleClick = () => {
   setName("Amaka")
   setAge(20)
   setAddress("No 3 Rodeo drive")
}
2._
//React

handleSearch = (e) => {
   this.setState({
      searchTerm: e.target.value
   }, () => {
      // Do an API call with this.state.searchTerm
   });
}

The componentDidUpdate function is invoked immediately after a state update occurs. To avoid an infinite loop, you should always use a conditional statement to be sure that the previous state and the current state are not the same:

//React

componentDidUpdate(prevProps, prevState) {
   if (prevState.count !== this.state.count) {
      // Do something here
   }
}

Suggestion : 4

Ok... so.... there's this annoying... but super important rule in React that we're totally violating. The rule is: the only time you're allowed to set or change the state property directly is when you're initializing it in the constructor. Everywhere else, you must call setState() instead of changing it directly. Why the heck are we doing this? Remember how I said that setState() is asynchronous? Because of that, if you call setState() now, React may not use that state until a few milliseconds later. And, if something else added a new repLog between now and then... well... with our previous code, our new state would override and remove that new repLog! For me, it's the syntax here that's the most confusing. Here is an expanded version (in case it helps anyone) of the setState() call:

I don't know where is my error. Any help please?

export default class RepLogApp extends Component {    constructor(props) {        super(props);        this.state = {            highlightedRowId: null,            repLogs: [                { id: uuidv4(), reps: 25, itemLabel: "My Laptop", totalWeight: 112.5 },                { id: uuidv4(), reps: 10, itemLabel: "Big fat Cat", totalWeight: 180 },                { id: uuidv4(), reps: 4, itemLabel: "Big fat Cat", totalWeight: 72 }            ]        }                this.handleRowMouseOver = this.handleRowMouseOver.bind(this);        this.handleAppRepLog = this.handleAddRepLog.bind(this);    }    handleRowMouseOver(repLogId) {        this.setState({highlightedRowId: repLogId});    }    handleAddRepLog(itemLabel, reps) {        const newRep = {            id: uuidv4(),            itemLabel,            reps,            totalWeight: Math.floor(Math.random() * 50)        };                this.setState(prevState => ( {repLogs: [...prevState.repLogs, newRep] }) );    }    render() {        return (             // this line is being formatted (!)            <replogs {...this.props}="" {...this.state}="" onrowmouseover="{this.handleRowMouseOver}" onaddreplog="{this.handleAddRepLog}"/>         );    }}RepLogApp.propTypes = {    withTitle: PropTypes.bool};

// this codethis.setState(prevState => ( {repLogs: [...prevState.repLogs, newRep]} ));// is basically equivalent tothis.setState(function(prevState) {    return {        repLogs: [...prevState.repLogs, newRep]    };});

@5:35 Another way to do it is:

const newRepLogs = this.state.repLogs.slice(0).concat(newRep);
5._
// package.json
{
   "dependencies": {
      "@babel/plugin-proposal-object-rest-spread": "^7.12.1" // 7.12.1
   },
   "devDependencies": {
      "@babel/preset-react": "^7.0.0", // 7.12.5
      "@symfony/webpack-encore": "^0.26.0", // 0.26.0
      "babel-plugin-transform-object-rest-spread": "^6.26.0", // 6.26.0
      "babel-plugin-transform-react-remove-prop-types": "^0.4.13", // 0.4.13
      "bootstrap": "3", // 3.3.7
      "copy-webpack-plugin": "^4.4.1", // 4.5.1
      "core-js": "2", // 1.2.7
      "eslint": "^4.19.1", // 4.19.1
      "eslint-plugin-react": "^7.8.2", // 7.8.2
      "font-awesome": "4", // 4.7.0
      "jquery": "^3.3.1", // 3.3.1
      "promise-polyfill": "^8.0.0", // 8.0.0
      "prop-types": "^15.6.1", // 15.6.1
      "react": "^16.3.2", // 16.4.0
      "react-dom": "^16.3.2", // 16.4.0
      "sass": "^1.29.0", // 1.29.0
      "sass-loader": "^7.0.0", // 7.3.1
      "sweetalert2": "^7.11.0", // 7.22.0
      "uuid": "^3.2.1", // 3.4.0
      "webpack-notifier": "^1.5.1", // 1.6.0
      "whatwg-fetch": "^2.0.4" // 2.0.4
   }
}

Suggestion : 5

setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value. There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains. Since setState is a async function. That means after calling setState state variable does not immediately change. So if you want to perform other actions immediately after changing the state you should use callback method of setstate inside your setState update function. Why does calling react setState method not mutate the state immediately?

Using callback method with setState:

To check the updated state value just after the setState, use a callback method like this:

setState({
   key: value
}, () => {
   console.log('updated state value', this.state.key)
})
2._
class NightlifeTypes extends React.Component {
   constructor(props) {
      super(props);

      this.state = {
         barClubLounge: false,
         seeTheTown: true,
         eventsEntertainment: true,
         familyFriendlyOnly: false
      }
   }

   handleOnChange = (event) => {  // Arrow function binds `this`
      let value = event.target.checked;

      if(event.target.className == "barClubLounge") {

         this.setState({ barClubLounge: value}, () => {  //here
             console.log(value);
             console.log(this.state.barClubLounge);
             //both will print same value
         });        

      }
   }

   render() {
      return (
          <input className="barClubLounge" type='checkbox' onChange={this.handleOnChange} checked={this.state.barClubLounge}/>
      )
   }
}

ReactDOM.render(<NightlifeTypes/>, document.getElementById('app'))
3._
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id='app' />

Suggestion : 6

Calls to setState are asynchronous - don’t rely on this.state to reflect the new value immediately after calling setState. Pass an updater function instead of an object if you need to compute values based on the current state (see below for details). Passing an update function allows you to access the current state value inside the updater. Since setState calls are batched, this lets you chain updates and ensure they build on top of each other instead of conflicting: Pass a function instead of an object to setState to ensure the call always uses the most updated version of state (see below).

1._
incrementCount() {
   // Note: this will *not* work as intended.
   this.setState({
      count: this.state.count + 1
   });
}

handleSomething() {
   // Let's say `this.state.count` starts at 0.
   this.incrementCount();
   this.incrementCount();
   this.incrementCount();
   // When React re-renders the component, `this.state.count` will be 1, but you expected 3.

   // This is because `incrementCount()` function above reads from `this.state.count`,
   // but React doesn't update `this.state.count` until the component is re-rendered.
   // So `incrementCount()` ends up reading `this.state.count` as 0 every time, and sets it to 1.

   // The fix is described below!
}
2._
incrementCount() {
   this.setState((state) => {
      // Important: read `state` instead of `this.state` when updating.
      return {
         count: state.count + 1
      }
   });
}

handleSomething() {
   // Let's say `this.state.count` starts at 0.
   this.incrementCount();
   this.incrementCount();
   this.incrementCount();

   // If you read `this.state.count` now, it would still be 0.
   // But when React re-renders the component, it will be 3.
}

Suggestion : 7

If you’re coming from classes, you might be tempted to always call useState() once and put all state into a single object. You can do it if you’d like. Here is an example of a component that follows the mouse movement. We keep its position and size in the local state: Normally, you shouldn’t mutate local state in React. However, as an escape hatch, you can use an incrementing counter to force a re-render even if the state has not changed: constructor: Function components don’t need a constructor. You can initialize the state in the useState call. If computing the initial state is expensive, you can pass a function to useState.

1._
function Example() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
2._
import React from 'react';
import ReactDOM from 'react-dom/client';
import { act } from 'react-dom/test-utils';import Counter from './Counter';

let container;

beforeEach(() => {
  container = document.createElement('div');
  document.body.appendChild(container);
});

afterEach(() => {
  document.body.removeChild(container);
  container = null;
});

it('can render and update a counter', () => {
  // Test first render and effect
  act(() => {    ReactDOM.createRoot(container).render(<Counter />);  });  const button = container.querySelector('button');
  const label = container.querySelector('p');
  expect(label.textContent).toBe('You clicked 0 times');
  expect(document.title).toBe('You clicked 0 times');

  // Test second render and effect
  act(() => {    button.dispatchEvent(new MouseEvent('click', {bubbles: true}));  });  expect(label.textContent).toBe('You clicked 1 times');
  expect(document.title).toBe('You clicked 1 times');
});
3._
function Timer() {
   const intervalRef = useRef();
   useEffect(() => {
      const id = setInterval(() => {
         // ...
      });
      intervalRef.current = id;
      return () => {
         clearInterval(intervalRef.current);
      };
   });

   // ...
}
5._
function Box() {
   const [state, setState] = useState({
      left: 0,
      top: 0,
      width: 100,
      height: 100
   });
   // ...
}
6._
  // ...
  useEffect(() => {
           function handleWindowMouseMove(e) {
              // Spreading "...state" ensures we don't "lose" width and height      setState(state => ({ ...state, left: e.pageX, top: e.pageY }));    }
              // Note: this implementation is a bit simplified
              window.addEventListener('mousemove', handleWindowMouseMove);
              return () => window.removeEventListener('mousemove', handleWindowMouseMove);
           }, []);
        // ...