florian22
florian22

Reputation: 13

useEffect cleanup function not working in React Native

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

enter image description here

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

Answers (2)

Ben Walton
Ben Walton

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

Ayush Gupta
Ayush Gupta

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

Related Questions