Home > Software engineering >  React component retains previous state
React component retains previous state

Time:12-29

I've been learning React and React Native for less than a week, so there are concepts that I'm still learning. The code below is a first attempt to code an app with navigation through a bottom tab navigator, more specifically using @react-navigation/material-bottom-tabs and react-native-paper. There are 3 screens called Home, Details and Help. Home and Details both contain a button "ask for help", which will both redirect to the Help screen with a special message that is passed as parameter. For example:

      <Button
        icon="camera"
        mode="contained"
        onPress={() => navigation.navigate("Help", { txt: "Everything is OK!" })}
        style={{ marginTop: 16 }}
      >
        Ask for help
      </Button>

When loading the application, HelpScreen is initiated with initialParams={{ txt: "nothing" }} and the screen will display You said nothing. From HomeScreen, clicking on the button redirects me to HelpScreen with onPress={() => navigation.navigate("Help", { txt: "Everything is OK!" })}. Therefore the screen will display You said Everything is OK!. When moving to another screen and then going back to HelpScreen with the use of the bottom tabs, I expect HelpScreen to be re-rendered with its original value. Therefore I expect the screen to say You said nothing. But no, it still says You said Everything is OK!. The same behavious happens with DetailsScreen with another text.

In the HelpScreen function, I am not saving the parameter to a state. So I don't expect the component to retain the previous value when re-rendering. So why doesn't the component reset to its original value when re-rendering? Or is it not re-rendering? In which case can you please explain why?

Here is the code:

import { registerRootComponent } from "expo";
import { NavigationContainer } from "@react-navigation/native";
import { NativeStackScreenProps } from "@react-navigation/native-stack";
import { createMaterialBottomTabNavigator } from "@react-navigation/material-bottom-tabs";
import { View } from "react-native";
import { Button, Text } from "react-native-paper";
import { SafeAreaProvider } from "react-native-safe-area-context";
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";

type NavigationParamList = {
  Home: undefined;
  Details: undefined;
  Help: { txt: string };
};

type PropsHome = NativeStackScreenProps<NavigationParamList, "Home">;
type PropsDetails = NativeStackScreenProps<NavigationParamList, "Details">;
type PropsHelp = NativeStackScreenProps<NavigationParamList, "Help">;

const Tab = createMaterialBottomTabNavigator<NavigationParamList>();

function HomeScreen({ navigation, route }: PropsHome) {
  console.log("HOME");
  return (
    <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
      <Text>Route name: {route.name}</Text>
      <Button
        icon="camera"
        mode="contained"
        onPress={() =>
          navigation.navigate("Help", { txt: "Everything is OK!" })
        }
        style={{ marginTop: 16 }}
      >
        Ask for help
      </Button>
    </View>
  );
}

function DetailsScreen({ navigation, route }: PropsDetails) {
  console.log("DETAILS");
  return (
    <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
      <Text>Route name: {route.name}</Text>
      <Button
        icon="camera"
        mode="contained"
        onPress={() => navigation.navigate("Help", { txt: "HELP ME!" })}
        style={{ marginTop: 16 }}
      >
        Ask for help
      </Button>
    </View>
  );
}

function HelpScreen({ route }: PropsHelp) {
  console.log("HELP");
  return (
    <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
      <Text>Route name: {route.name}</Text>
      <Text>You said {route.params.txt}</Text>
    </View>
  );
}

export default function App() {
  return (
    <SafeAreaProvider>
      <NavigationContainer>
        <Tab.Navigator initialRouteName="Home" screenOptions={{}}>
          <Tab.Screen
            name="Home"
            options={{
              tabBarIcon: ({ color }) => (
                <MaterialCommunityIcons name="home" color={color} size={26} />
              ),
            }}
            component={HomeScreen}
          />
          <Tab.Screen
            name="Details"
            options={{
              tabBarIcon: ({ color }) => (
                <MaterialCommunityIcons
                  name="alien-outline"
                  color={color}
                  size={26}
                />
              ),
            }}
            component={DetailsScreen}
          />
          <Tab.Screen
            name="Help"
            options={{
              tabBarIcon: ({ color }) => (
                <MaterialCommunityIcons
                  name="chat-question-outline"
                  color={color}
                  size={26}
                />
              ),
            }}
            component={HelpScreen}
            initialParams={{ txt: "nothing" }}
          />
        </Tab.Navigator>
      </NavigationContainer>
    </SafeAreaProvider>
  );
}

registerRootComponent(App);

Note that I have not used the useState hook. I understand that this hook would be needed when one wants the retain a value, not reset it.

CodePudding user response:

React Navigation avoids rerendering each screen every time is focused for both better user experience and avoiding unnecessary screen rerendering.

You should override this default behavior and detect when the screen is revisited/focuses and rerender that screen.

It provided utility hooks to listen to the screen is focused - https://reactnavigation.org/docs/function-after-focusing-screen/

Here code refactor with a solution:

import { registerRootComponent } from "expo";
import {useEffect,useState} from "react"
import { NavigationContainer ,useIsFocused} from "@react-navigation/native";
import { NativeStackScreenProps } from "@react-navigation/native-stack";
import { createMaterialBottomTabNavigator } from "@react-navigation/material-bottom-tabs";
import { View } from "react-native";
import { Button, Text } from "react-native-paper";
import { SafeAreaProvider } from "react-native-safe-area-context";
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";


type NavigationParamList = {
  Home: undefined;
  Details: undefined;
  Help: { txt: string };
};

type PropsHome = NativeStackScreenProps<NavigationParamList, "Home">;
type PropsDetails = NativeStackScreenProps<NavigationParamList, "Details">;
type PropsHelp = NativeStackScreenProps<NavigationParamList, "Help">;

const Tab = createMaterialBottomTabNavigator();
function HomeScreen({ navigation, route }: PropsHome) {
  console.log("HOME");
  return (
    <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
      <Text>Route name: {route.name}</Text>
      <Button
        icon="camera"
        mode="contained"
        onPress={() =>
          navigation.navigate("Help", { txt: "Everything is OK!" })
        }
        style={{ marginTop: 16 }}
      >
        Ask for help
      </Button>
    </View>
  );
}

function DetailsScreen({ navigation, route }: PropsDetails) {
  console.log("DETAILS");

  return (
    <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
      <Text>Route name: {route.name}</Text>
      <Button
        icon="camera"
        mode="contained"
        onPress={() => navigation.navigate("Help", { txt: "HELP ME!" })}
        style={{ marginTop: 16 }}
      >
        Ask for help
      </Button>
    </View>
  );
}

function HelpScreen({ route ,navigation}: PropsHelp) {
  const [message,setMessage] = useState("")

const isFocused = useIsFocused()
  useEffect(()=>{
setMessage(route.params.txt)
return ()=>{
  navigation.setParams({txt:"nothing"})
}
  },[isFocused])
  return (
    <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
      <Text>Route name: {route.name}</Text>
      <Text>You said {message}</Text>
    </View>
  );
}

export default function App() {
  return (
    <SafeAreaProvider>
      <NavigationContainer>
        <Tab.Navigator initialRouteName="Home" screenOptions={{}}>
          <Tab.Screen
            name="Home"
            options={{
              tabBarIcon: ({ color }) => (
                <MaterialCommunityIcons name="home" color={color} size={26} />
              ),
            }}
            component={HomeScreen}
          />
          <Tab.Screen
            name="Details"
            options={{
              tabBarIcon: ({ color }) => (
                <MaterialCommunityIcons
                  name="alien-outline"
                  color={color}
                  size={26}
                />
              ),
            }}
            component={DetailsScreen}
          />
          <Tab.Screen
            name="Help"
            options={{
              tabBarIcon: ({ color }) => (
                <MaterialCommunityIcons
                  name="chat-question-outline"
                  color={color}
                  size={26}
                />
              ),
            }}
            component={HelpScreen}
            initialParams={{ txt: "nothing" }}
          />
        </Tab.Navigator>
      </NavigationContainer>
    </SafeAreaProvider>
  );
}




Working Demo - https://snack.expo.dev/@emmbyiringiro/493761

  • Related