简体   繁体   中英

Strange behavior of React setState with let variable change

The code below will render a <h1> and two <button> .

In my expectation , the changeString1 <button> will change letString to 1 and change <h1> text to 1 finally, while the changeString2 <button> will

  1. change <h1> text to 3 if I click the changeString2 first
  2. change <h1> text to 1 if I click the changeString1 first

But in fact

  1. If I click the changeString1 once first, then click the changeString2 , <h1> text will be 3 ! But why?
  2. What's more , if I click the changeString1 twice first, then click the changeString2 , <h1> text will be 1 ! But why?

It seems that both of 2 facts are contradictory...

You can test this by https://codesandbox.io/s/wy4l1y4o8

import React, { useState } from "react";
import ReactDOM from "react-dom";

function App() {
  let letString = "3";
  const [statString, setStatString] = useState("2");

  function changeString1() {
    letString = "1";
    setStatString(letString);
  }

  function changeString2() {
    console.log(letString);
    setStatString(letString);
  }

  return (
    <div>
      <h1>{statString}</h1>
      <button onClick={changeString1}>changeString1</button>
      <button onClick={changeString2}>changeString2</button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Please look at the flow that is taking place:-

1st click on changeString1

state changes

component re renders

letString gets re-initialized to 3 because of let letString = "3"

value of letString = 3;
value of statString = 1 (state);

2nd click on changeString1

value of letString gets set to 1 in the function call;

But value of statString(state) is already 1 so no state changes and component never re-renders and letString is not re-initialized

After 2nd click values are:-

letString = 1;
statString = 1(state);

Now, when you click on changeString2 value of letString is 1 and statString is also 1 so state doesn't changes and nothing happens and you just see 1.

When you click on changeString2 , local variable letString value is 3.

If you want to persist letString value between render cycles you must use useState or even using a global scope variable (not recommended)

You should track clicking on changeString1 in a separate variable:

 const {useState} = React function App() { const [statString, setStatString] = useState("2"); const [clicked, setClicked] = useState(false) function changeString1() { setClicked(true) setStatString("1"); } function changeString2() { setStatString(clicked ? "1" : "3"); } return ( <div> <h1>{statString}</h1> <button onClick={changeString1}>changeString1</button> <button onClick={changeString2}>changeString2</button> </div> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement); 
 <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script> <div id="root" /> 

The reason your code didn't work as expected is that each invocation of App creates a new instance of the letState variable that is bound to changeString2 function when it is defined . React optimizes calls that don't change the state, avoiding unnecessary re-renders. So when you click the second time you are not causing a replacements of the callback functions. So when you click on changeState2 it's the same function defined in "previous" render.

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