Home > Software design >  update state on unmonted component with context and hooks - react native
update state on unmonted component with context and hooks - react native

Time:10-24

UPDATE2: I applied one of the proposed solutions, but the problem still persists. I uploaded all my current code as it is short and rather simple .. if anyone would like to give an opinion ... thanks in advance

UPDATE: I've applied the instructor in this post, but even using the state isMounted and the useEffect cleanup function I still can't solve this problem. the code seems to work fine, but I always get this warning.

I have an app component that manages the navigation of two pages through conditional rendering, if I am logged in I enter one, if I am not I enter the other.

import {context} from "./components/context"

const Stack = createNativeStackNavigator();

export default function App() {
    const isMounted = useRef(false);
    const [isLoggedIn, setLoggedIn] = useState(false);

    useEffect(() => {
        isMounted.current = true;

        let store = async () => {
            let accessToken = await SecureStore.getItemAsync("accessToken");
            if(accessToken && isMounted.current) {
                setLoggedIn(true)
            }
        }
        store().then()

        return () => {
            isMounted.current = false
        }
    }, [])


    const handleLoggedIn = (loggedIn) => {
        if(isMounted.current) {
            setLoggedIn(loggedIn)
        }
    }

    return (
        <>
            <NavigationContainer>
                <context.Provider value={{isLoggedIn, handleLoggedIn}}>
                    <Stack.Navigator >
                        <Stack.Screen name={isLoggedIn ? "HomePantry" : "Home"} component={isLoggedIn? HomePantry : Home}  />
                    </Stack.Navigator>
                </context.Provider>
            </NavigationContainer>
        </>
    );
}

My file context.js:

export const context = React.createContext({});

This is my simple home component (before user login).

export default function Home({navigation}) {

    return (
            <View>
                <Text> My pantry </Text>
                <UserLogin />
            </View>
    );
}

This is the UserLogin child component. I am using the context to be able to update the isLoggedIn state once the user has entered their correct credentials. The problem is that the state is updated when the app component is unmounted and this causes no-op.

I get this warning: "Can't perform a React state update on an unmounted component - memory leak?"

I haven't been able to resolve this situation yet if anyone has any ideas. thanks in advance.

import {context} from "./components/context"

export default function UserLogin() {
    const value = React.useContext(context)

    return (
        <View style={styles.inputsContainer}>
            <Formik
                initialValues={{ email: '', password: '' }}
                onSubmit={
                    async (values, actions) => {
                        if(values.email.trim() !== "" && values.password.trim() !== ""){
                            const response = await fetch('https://lam21.iot-prism-lab.cs.unibo.it/auth/login', {
                                method: 'POST',
                                headers: {
                                    'Content-Type': 'application/json'
                                },
                                body: JSON.stringify({
                                    email: values.email,
                                    password: values.password
                                })
                            });
                            let json = await response.json()
                            if(json.accessToken){
                                value.handleLoggedIn(true)
                                await SecureStore.setItemAsync("accessToken", json.accessToken);
                                actions.resetForm({})
                            } else {
                                alert("Username o password sbagliati!")
                            }
                        }}}
            >
                {({ handleChange, handleBlur, handleSubmit, values }) => (
                    <View style={styles.inputsContainer}>
                        <Text style={styles.labelText}> Email </Text>
                        <TextInput
                            required
                            onChangeText={handleChange('email')}
                            onBlur={handleBlur('email')}
                            value={values.email}
                            placeholder={"Inserisci la tua mail.."}
                            style={styles.inputText}
                        />
                        <Text style={styles.labelText}> Password </Text>
                        <TextInput
                            required
                            onChangeText={handleChange('password')}
                            onBlur={handleBlur('password')}
                            value={values.password}
                            placeholder={"Inserisci la tua password.."}
                            style={styles.inputText}
                        />
                        <View style={styles.inputButton}>
                            <Button onPress={handleSubmit} title="Submit" color="purple" style={styles.inputButton} />
                        </View>
                    </View>
                )}
            </Formik>
        </View>
    );
}

The homepantry component after the login:

export default function HomePantry() {

    return (
        <View>
            <Text> My pantry </Text>
        </View>
    );
}

CodePudding user response:

The problem is when you set a state on a promise. The component was mounted before the promise was resolved so you just need to check if it is still mounted;

useEffect(() => {
        let isMounted = true;
        let store = async () => {
            let accessToken = await SecureStore.getItemAsync("accessToken");
            if(accessToken && isMounted){
                setLoggedIn(true)
            }
        }
        store().then()
        return () => {
            isMounted = false;
        };
    },[]);

CodePudding user response:

You implement the ford04's structure inside of useEffect hook to prevent unwanted setState for an unmounted component. So far so good.

But, wait a minute, you are passing the setLoggedIn function via the context provider to set the login status inside of your child components. So the same problem is happening here.

Thus, you need to implement the same structure (using isMounted) before updating the isLoggedIn with the setLoggedIn. it's a little tricky.

import React, {useRef, useState, useEffect} from 'react';

function App() {
  const isMounted = useRef(false);
  const [isLoggedIn, setLoggedIn] = useState(false);

  useEffect(() => {
    isMounted.current = true;

    let store = async () => {
      let accessToken = await SecureStore.getItemAsync("accessToken");
      if(accessToken && isMounted.current) {
        setLoggedIn(true)
      }
      return;
    }
    store()
    
    return () => {
      isMounted.current = false
    }
  }, [])


  const handleLoggedIn = (loggedIn) => {
    if(isMounted.current) {
      setLoggedIn(loggedIn)
    }
  }
}

Now, pass this handleLoggedIn instead of setLoggedIn with the context provider:

<context.Provider value={{isLoggedIn, handleLoggedIn}}>

Note: since the isMounted variable is a local scoop variable and can't use it outside of the useEffect, I creat it with useRef method with the same effect.

Also, I suggest you change the conditional render as below:


<Stack.Navigator >
  <Stack.Screen name={isLoggedIn ? "HomePantry" : "Home"} component={isLoggedIn? HomePantry : Home}  />
</Stack.Navigator>

  • Related