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>