Home > database >  Using Hooks API: does React respect setState order?
Using Hooks API: does React respect setState order?

Time:11-17

I have fairly nonexistent knowledge in react but I'm learning as I go. I learned the basics back in school, with class components (classic React), but now I'm delving into the Hooks API (mainly because I find it easier to learn and manage, although there seems to be more tricks involved regarding async behavior). So my question might seem silly.

I found this thread regarding setState behavior on the same topic, but this is regarding class components.

In my current application, I'm trying to set three different states using an event handler. It seems that the last state is set immediately, whereas the other two states remain undefined for a bit before changing to a real value. I'm using React-Native components for mobile development, so you'll see snippets in the code such as <SafeAreaView>.

export default App = () => {
    const [ destLong, setDestLong ] = useState();
    const [ destLat, setDestLat ] = useState();
    const [ startNav, setStartNav ] = useState(false);
    const [ locations, setLocations ] = useState([
        {
            name: 'IKEA',
            long: '-74.00653395444186',
            lat: '40.68324646680103',
        },
        {
            name: 'JFK Intl. Airport',
            long: '-73.78131423688552',
            lat: '40.66710279890186',
        },
        {
          name: 'Microcenter',
          long: '-74.00516039699959',
          lat: '40.67195933297655',
        }
    ]);

    const startNavigation = (goinglong, goinglat) => {
        setDestLong(goinglong);
        setDestLat(goinglat);
        setStartNav(true);
    }

    return (
        <SafeAreaView style={styles.container}>
          { startNav ? 
            <MapView 
              destLong = {destLong}
              destLat = {destLat}
            />
            :
            <View style={styles.buttonContainer}>
              <ScrollView>
                {
                  locations.map((location, i) => {
                    return(
                      <Card
                        style={styles.card}
                        key={i}
                        title={ location.name }
                        iconName="home"
                        iconType="Entypo"
                        description={ location.long   ", "   location.lat }
                        onPress={() => startNavigation(location.long, location.lat)}
                      />
                    );
                  })
                }
              </ScrollView>
            </View>
          }
        </SafeAreaView>
    );
}

const styles = StyleSheet.create({
    container: {
      flex: 1,
    },
    buttonContainer: {
      width: '100%',
      height: '100%',
      justifyContent: 'center',
      alignItems: 'center'
    },
    logo: {
      width: '50%',
      height: '50%',
      resizeMode: 'contain'
    },
    card: {
      marginBottom: 10,
    }
  });

This throws an error, because MapView is expecting destLong and destLat to render properly. When I console log inside my startNavigation function, it seems that it immediately updates the state for startNav to true onPress, but destLong and destLat remain undefined for a few cycles before being set.

I've tried a different approach like this:

    useEffect(() => {
        setStartNav(true);
    }, [destLong]);

    const startNavigation = (goinglong, goinglat) => {
        setDestLong(goinglong);
        setDestLat(goinglat);
    }

But it just crashes the app (my guess is infinite loop).

I've also tried removing the startNav state altogether and rendering <MapView> on destLong like this

{ destLong ? 
<MapView 
    destLong = {destLong}
    destLat = {destLat}
/>
:
<View style={styles.buttonContainer}>
    ...
</View>
}

But that did not work either.


Which brings me to this question: does the Hooks API respect the order of setState, or is each other carried out asynchronously? From my understanding it's the latter. But then, how do you handle this behavior?

CodePudding user response:

I'm adding my comment here as well since I am unable to add proper formatting to my comment above.

Setting a state via useState is actually asynchronous, or rather the state change is enqueued and it will then return its new value after a re-render. This means that there is no guarantee in what order the states will be set. They will fire in order, but they may not be set in the same order.

You can read more here: https://dev.to/shareef/react-usestate-hook-is-asynchronous-1hia, as well as here https://blog.logrocket.com/a-guide-to-usestate-in-react-ecb9952e406c/#reacthooksupdatestate

In your case I would use useState and useEffect like this:

useEffect(() => {
    if(destLong && destLat && !startNav) {
        setStartNav(true);
    }
}, [destLong, destLat, startNav]);

const startNavigation = (goinglong, goinglat) => {
    setDestLong(goinglong);
    setDestLat(goinglat);
}

With that said, I think you could further simplify your code by omitting the startNav state altogether and update your conditional render:

{ (destLat && destLong) ? 
    <MapView 
      destLong = {destLong}
      destLat = {destLat}
    />
    :
    <View style={styles.buttonContainer}>
      ...
    </View>
  }

The above should have the same effect since you have two states that are undefined to begin with, and when they are both defined you want to render something and use their values.

And if you want to display the options again you can set the states to undefined again by doing setDestLat(undefined) and setDestLong(undefined)

  • Related