Reputation: 13
I'm trying to get the token from Expo's SecureStore and then dispatch an action.
I'm using useEffect so that I can check if there is a token I can use when the component is first rendered.
useEffect(() => {
let mounted = true;
SecureStore.getItemAsync('token').then((token) =>
token ? dispatch({ type: 'signin', payload: token }) : null
);
return () => (mounted = false);
}, []);
However, I can't get rid of:
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function
More code as requested:
import React, { useReducer, createContext, useEffect } from
'react';
import * as SecureStore from 'expo-secure-store';
import forumApi from '../api/forumApi';
export const Context = createContext();
const reducer = (state, action) => {
switch (action.type) {
case ('signup', 'signin'):
return { ...state, token: action.payload };
case 'error': {
return { token: undefined, errorMessage: action.payload };
}
default:
return state;
}
};
const AuthProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, {
token: undefined,
errorMessage: '',
});
useEffect(() => {
let mounted = true;
const runAsync = async () =>
await SecureStore.getItemAsync('token').then((token) =>
token ? dispatch({ type: 'signin', payload: token }) : null
);
runAsync();
return () => (mounted = false);
}, []);
const signup = async (fullName, email, password, passwordConfirm)
=> {
try {
const res = await forumApi.post('/api/v1/users/signup', {
fullName,
email,
password,
passwordConfirm,
});
const { token } = res.data;
await SecureStore.setItemAsync('token', token);
dispatch({ type: 'signup', payload: token });
} catch (err) {
dispatch({
type: 'error',
payload: 'We could not register you. Please try with
different email.',
});
}
};
const signin = async (email, password) => {
try {
const res = await forumApi.post('/api/v1/users/signin', {
email,
password,
});
const { token } = res.data;
await SecureStore.setItemAsync('token', token);
dispatch({ type: 'signin', payload: token });
} catch (err) {
dispatch({
type: 'error',
payload: 'We could not log you in. Please try again.',
});
}
};
const signinGoogle = async (token, fullName, email, photo) => {
try {
const res = await forumApi.post('/api/v1/users/auth/google', {
fullName,
email,
photo,
});
await SecureStore.setItemAsync('token', token);
dispatch({ type: 'signup', payload: token });
} catch (err) {
dispatch({
type: 'error',
payload: 'We could not register you. Please try with different email.',
});
}
};
return (
<Context.Provider
value={{ state, signup, signin, signinGoogle, tryLocalSignin }}
>
{children}
</Context.Provider>
);
};
export default AuthProvider;
Upvotes: 1
Views: 553
Reputation: 453
@florian22 so it appears you are attempting to update the state on the provider level. In order to update the state of a context you must be inside the provider. The useEffect must be on an inner node.
See example:
const App = ({children}) => {
useEffect(() => {
const runAsync = async () => {
const token = await SecureStore.getItemAsync('token');
if(token) dispatch({ type: 'signin', payload: token });
}
runAsync();
}, []);
return (<View>{children}</View>);
}
const MainApp = ({children}) => {
return (
<AuthProvider>
<App>
{children}
</App>
</AuthProvider>
}
Upvotes: 1
Reputation: 1227
There is no need for using mounted
variable. This is enough
useEffect(() => {
SecureStore.getItemAsync("token").then((token) =>
token ? dispatch({ type: "signin", payload: token }) : null
);
}, []);
Upvotes: 1