Search code examples
javascriptreactjsreact-nativerender

In react, useState doesn't reflect a change immediately


From what I learnd everywhere, it seems like useState set doesn't chage the value immediately as you know.

What I want to inplement: In my simple app, there is a button for removing a value from an array with setState, and at the same time navigating to another screen. (Like, when there is no item on the array, then it navigates to another screen)

Here are my initial codes:

import React, { useState, useEffect } from "react";
import { View, Button } from "react-native";

function Home({ navigation }) {
  const [array, setArray] = useState(["X"]);

  const removeValue = () => {
    setArray((current) => {
      return current.filter((item) => item !== array[0]);
    });
    console.log("array", array); //It still refers to the old value.
    if (JSON.stringify(array) === JSON.stringify([])) {
      navigation.navigate("About");
    }
  };

  return (
    <View>
      <Button title="GoToAbout" onPress={removeValue} />
    </View>
  );
}
export default Home;
import React from "react";
import { View, Text } from "react-native";

function About() {
  return (
    <View>
      <Text>There is no item in your list.</Text>
    </View>
  );
}
export default About;
import { StyleSheet, Text, View } from "react-native";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";

import Home from "./Home";
import About from "./About";

const Stack = createNativeStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={Home} />
        <Stack.Screen name="About" component={About} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,

    alignItems: "center",
    justifyContent: "center",
  },
});

Because state doesn't change the value immediately, google serch says you can use useEffect to deal with the problem, so I tried it with useEffect. Here is the code with useEffect:

import React, { useState, useEffect } from "react";
import { View, Button } from "react-native";

function Home({ navigation }) {
  const [array, setArray] = useState(["X"]);

  const removeValue = () => {
    setArray((current) => {
      return current.filter((item) => item !== array[0]);
    });
  };

  useEffect(() => {
    if (JSON.stringify(array) === JSON.stringify([])) {
      console.log("array", array);
      navigation.navigate("About");
    }
  }, [array]);
  //Using this second aug [array] doesn't work well, because I use 'array' in everywhere in my app, so it accidentally renders where I don't want it to render.

  return (
    <View>
      <Button title="GoToAbout" onPress={removeValue} />
    </View>
  );
}

export default Home;

That worked beautifully here. However in my app, I use the 'array' in everywhere, so using it as the second aug in useEffect doesn't work well because it accidentally renders where I don't want it to do.

So, now I'm stack. Is there any other way that soloves this problem?

Thank you in advance.


Solution

  • OK. So it's not necessary to use the callback form of setArray here.

    Instead, let's wrap up the removeValue function in a useCallback hook and use the non-callback version of setArray:

      const removeValue = React.useCallback(() => {
        const newArrayValue = array.filter((item) => item !== array[0])
        setArray(newArrayValue);
        // ... now we know what the new value is already... it's stored in
        // newArrayValue
        
        if (newArrayValue.length === 0) { //an easier way to check for empty
          navigation.navigate("About");
        }
      }, [array, navigation]);