简体   繁体   中英

Don't understand how to update React hook

I've tried to ask this ungooglable to me question dozens of times. I've made almost the simpliest example possible to ask this question now.

I change the value of the hook in the handleChange method. But then console.log always shows previous value, not new one. Why is that?

I need to change the value of the hook and then instead of doing console.log use it to do something else. But I can't because the hook always has not what I just tried to put into it.

const options = ["Option 1", "Option 2"];

export default function ControllableStates() {
  const [value, setValue] = React.useState(options[0]);

  const handleChange = val => {
    setValue(val);
    console.log(value);
  };

  return (
    <div>
      <div>{value}</div>
      <br />
      <Autocomplete
        value={value}
        onChange={(event, newValue) => {
          handleChange(newValue);
        }}
        options={options}
        renderInput={params => (
          <TextField {...params} label="Controllable" variant="outlined" />
        )}
      />
    </div>
  );
}

You can try it here. https://codesandbox.io/s/awesome-lumiere-y2dww?file=/src/App.js

I believe the problem is that you are logging the value in the handleChange function. Console logging the value outside of the function logs the correct value. Link: https://codesandbox.io/s/async-fast-6y71b

Hooks do not instantly update the value you want to update, as you might have expected with classes (though that wasn't guaranteed either)

State hook, when calling setValue will trigger a re-render. In that new render, the state will have the new value as you expected. That's why your console.log sees the old value.

Think of it as in each render, the state values are just local variables of that component function call. And think as the result of your render as a result of your state + props in that render call. Whenever any of those two changes (the props from your parent component; the state, from your setXXX function), a new render is triggered.

If you move out the console.log outside of the callback handler (that is, in the body of your rendered), there you will see in the render that happens after your interaction that the state is logged correctly.

In that sense, in your callbacks events from interactions, you just should worry about updating your state properly, and the next render will take care to, given the new props/state, re-render the result

The value doesn't "change" synchronously - it's even declared with a const , so even the concept of it changing inside the same scope doesn't make sense.

When changing state with hooks, the new value is seen when the component is rerendered . So, to log and do stuff with the "new value", examine it in the main body of the function:

const ControllableStates = () => {
  const [value, setValue] = React.useState(options[0]);

  const handleChange = val => {
    setValue(val);
  };
  // ADD LOG HERE:
  console.log('New or updated value:', value);

  return (
    <div>
      <div>{value}</div>
      <br />
      <Autocomplete
        value={value}
        onChange={(event, newValue) => {
          handleChange(newValue);
        }}
        options={options}
        renderInput={params => (
          <TextField {...params} label="Controllable" variant="outlined" />
        )}
      />
    </div>
  );
}

You're printing out the old value in handleChange , not the new val . ie

  const handleChange = val => {
    setValue(val);
    console.log(value);
  };

Should be:

  const handleChange = val => {
    setValue(val);
    console.log(val);
  };

Actually, lets get a little back and see the logic behind this scenario. You should use the "handleChange" function ONLY to update the state hook, and let something else do the logic depends on that state hook value, which is mostly accomplished using "useEffect" hook.

You could refactor your code to look like this:

  const handleChange = val => {
    setValue(val);
   };

  React.useEffect(() => {
    console.log(value);
    // do your logic here
  }, [value])

So I think that the main problem is that you're not understanding how React deals with components and states.

So, I'll vastly simplify what React does.

  • React renders a new component and remembers it's state, it's inputs (aka props) and it's the state and inputs of the children.
  • If at any given point an input changes or a state changes, React will render the component again by calling the component function.

Consider this:

function SomeComponent(text) {
    return (<div>The <i>text</i> prop has the value {text}</div>)
}

Let's say the initial prop value is "abc" , React will call SomeComponent("abc") , then the function returns <div>The <i>text</i> prop has the value abc</div> and React will render that. If the prop text does not change, then React does nothing anymore.

Now the parent component changes the prop to "def" , now React will call SomeComponent("def") and it will return <div>The <i>text</i> prop has the value def</div> , this is different from last call, so React will update the DOM to reflect the change.

Now let's introduce state

function SomeComponent() {
    const [name, setName] = React.useState("John")

    function doSomething()
    {
        alert("The name is " + name)
    }

    return (
        <p>Current name: {name}</p>
        <button onClick={() => setName("Mary")}>Set name to Mary</button>
        <button onClick={() => setName("James")}>Set name to James</button>
        <button onClick={() => doSomething()}>Show current name</button>
    )
}

So here React will call SomeComponent() and render the name John and the 3 button. Note that the value of the name variable does not change during the current execution, because it's declared as const . This variable only reflects the latest value of the state.

When you press the first button, setName() is executed. React will internally store the new value for the state and because of the change of state, it will render the component again, so SomeComponent() will be called once again. Now the variable name will reflect again the latest value of the state (that's what useState does), so in this case Mary . React will realize that the DOM has to be updated and it prints the name Mary .

If you press the third button, it will call doSomething() which will print the latest value of the name variable because every time React calls SomeComponent() , the doSomething() function is created again with the latest value of name . So once you've called setName() , you don't need to do anything special to get the new value. React will take care of calling the component function again.

So when you don't use class components but function components, you have to think differently: the function gets called all the time by React and at any single execution it reflects the latest state at that particular point in time. So when you call the setter of a useState hook, you know that the component function will be called again and useState will return the new value.

I recommend that you read this article , also read again Components and Props from the React documentation.

So how should you do proceed? Well, like this:

const options = ["Option 1", "Option 2"];

export default function ControllableStates() {
  const [value, setValue] = React.useState(options[0]);

  const handleChange = val => {
    setValue(val);
    console.log(value);
  };

  const handleClick = () => {
      // DOING SOMETHING WITH value
      alter(`Now I'm going to do send ${value}`);
  }

  return (
    <div>
      <div>{value}</div>
      <br />
      <Autocomplete
        value={value}
        onChange={(event, newValue) => {
          handleChange(newValue);
        }}
        options={options}
        renderInput={params => (
          <TextField {...params} label="Controllable" variant="outlined" />
        )}
      />
      <button type="button" onClick={handleClick}>Send selected option</button>
    </div>
  );
}

See CodeSandbox .

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