Is Usestate Synchronous

React useState() doesn't update value synchronously

Let's assume the user does not have valid credentials. The problem is here:

if (email.length < 4) {  // <== this gets executed
setError(true);
}
if (password.length < 5) { // <== this gets executed
setError(true);
}
console.log(error); // <== still false even after setting it to true
if (!error) { // <== this check runs before setError(true) is complete. error is still false.
console.log("validation passed, creating token");
setToken();
} else {
console.log("errors");
}

You are using multiple if-checks that all run independently, instead of using a single one. Your code executes all if-checks. In one check, you call setError(true) when one of the conditions is passed, but setError() is asynchronous. The action does not complete before the next if-check is called, which is why it gives the appearance that your value was never saved.

You can do this more cleanly with a combination of if-else and useEffect instead: https://codesandbox.io/s/dazzling-pascal-78gqp

import * as React from "react";

const Login: React.FC = (props: any) => {
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
const [error, setError] = React.useState(null);

const handleEmailChange = (e: any): void => {
const { value } = e.target;
setEmail(value);
};

const handlePasswordChange = (e: any): void => {
const { value } = e.target;
setPassword(value);
};

const handleSubmit = (e: any): void => {
e.preventDefault();
if (email.length < 4 || password.length < 5) {
setError(true);
} else {
setError(false);
}
};

const setToken = () => {
//token logic goes here
console.log("setting token");
};

React.useEffect(() => {
if (error === false) {
setToken();
}
}, [error]); // <== will run when error value is changed.

return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="email@address.com"
onChange={handleEmailChange}
/>
<br />
<input
type="password"
placeholder="password"
onChange={handlePasswordChange}
/>
<br />
<input type="submit" value="submit" />
</form>

{error ? <h1>error true</h1> : <h1>error false</h1>}
</div>
);
};

export default Login;

React- is useState synchronous (not the setter returned by useState but useState itself)?

Yes it is. Given

const [someState, setSomeState] = useState(initialValue);

When the component mounts, someState will always be initialValue - it won't be undefined or anything else that requires a re-render before the state gets initially populated.

How to synchronously change state within useState

I'm sort of confused on what you want to achieve. But don't forget, unlike a class you have to set all properties a in state each time.

setState({numCols: Math.max(Math.floor(state.containerWidth / baseThumbnailWidth), 1) }

This code will replace all your state with just numCols. You want the rest of state in there like this, now only numCols will change, everything else will be the same.

setState({...state, numCols: Math.max(Math.floor(state.containerWidth / baseThumbnailWidth), 1) }

Next thing to remember is if you want to change state multiple time in one render use this form:

setState(oldState => {...oldState, newValue: 'newValue'});

This will allow for multiple updates to state in one render with the last value set instead of on the last render. For example:

const [state, setState] = useState(0); // -> closed on this state's value!

setState(state + 1);
setState(state + 1);
setState(state + 1); //State is still 1 on next render
// because it is using the state which happened on the last render.
// When this function was made it "closed" around the value 0
// (or the last render's number) hence the term closure.

vs this:

const [state, setState] = useState(0);

setState(state => state + 1);
setState(state => state + 1);
setState(state => state + 1); //State is 3 on next render.

But why not just calculate the values synchronously?

  const setCardSize = (width) => {
const containerWidth = width > 0 ? width : defaultWidth;
const numCols = Math.max(Math.floor(containerWidth / baseThumbnailWidth), 1);
const numRows = Math.ceil(sheetList.size / numCols);
const cardWidth = Math.floor(containerWidth / numCols - 2 * marginBetweenCards);
const cardHeight = Math.round(cardWidth * thumbnailProportion);
setState({containerWidth, numCols, numRows, cardWidth, cardHeight});
}

Check out the docs it discusses

Unlike the setState method found in class components, useState does
not automatically merge update objects.

If the new state is computed using the previous state, you can pass a
function to setState. The function will receive the previous value,
and return an updated value. Here’s an example of a counter component
that uses both forms of setState:

The useState set method is not reflecting a change immediately

Much like .setState() in class components created by extending React.Component or React.PureComponent, the state update using the updater provided by useState hook is also asynchronous, and will not be reflected immediately.

Also, the main issue here is not just the asynchronous nature but the fact that state values are used by functions based on their current closures, and state updates will reflect in the next re-render by which the existing closures are not affected, but new ones are created. Now in the current state, the values within hooks are obtained by existing closures, and when a re-render happens, the closures are updated based on whether the function is recreated again or not.

Even if you add a setTimeout the function, though the timeout will run after some time by which the re-render would have happened, the setTimeout will still use the value from its previous closure and not the updated one.

setMovies(result);
console.log(movies) // movies here will not be updated

If you want to perform an action on state update, you need to use the useEffect hook, much like using componentDidUpdate in class components since the setter returned by useState doesn't have a callback pattern

useEffect(() => {
// action on update of movies
}, [movies]);

As far as the syntax to update state is concerned, setMovies(result) will replace the previous movies value in the state with those available from the async request.

However, if you want to merge the response with the previously existing values, you must use the callback syntax of state updation along with the correct use of spread syntax like

setMovies(prevMovies => ([...prevMovies, ...result]));

Is there a synchronous alternative of setState() in Reactjs

As you have read from the documentation, there is NO sync alternative, reason as described is performance gains.

However I presume you want to perform an action after you have changed your state, you can achieve this via:

class MyComponent extends React.Component {  constructor(props) {    super(props);    this.state = {     x: 1    };        console.log('initial state', this.state);  }    updateState = () => {   console.log('changing state');    this.setState({      x: 2    },() => { console.log('new state', this.state); })  }    render() {    return (      <div>      <button onClick={this.updateState}>Change state</button>    </div>    );     }}
ReactDOM.render( <MyComponent />, document.getElementById("react"));
<div id="react"></div><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>

React hooks: Why do several useState setters in an async function cause several rerenders?

In react 17, if code execution starts inside of react (eg, an onClick listener or a useEffect), then react can be sure that after you've done all your state-setting, execution will return to react and it can continue from there. So for these cases, it can let code execution continue, wait for the return, and then synchronously do a single render.

But if code execution starts randomly (eg, in a setTimeout, or by resolving a promise), then code isn't going to return to react when you're done. So from react's perspective, it was quietly sleeping and then you call setState, forcing react to be like "ahhh! they're setting state! I'd better render". There are async ways that react could wait to see if you're doing anything more (eg, a timeout 0 or a microtask), but there isn't a synchronous way for react to know when you're done.

You can tell react to batch multiple changes by using unstable_batchedUpdates:

import { unstable_batchedUpdates } from "react-dom";

const handleClickAsync = () => {
setTimeout(() => {
unstable_batchedUpdates(() => {
setValue("two");
setIsCondition(true);
setNumber(2);
});
});
};

In version 18 this isn't necessary, since the changes they've made to rendering for concurrent rendering make batching work for all cases.



Related Topics



Leave a reply



Submit