Home > database >  Issue with Auth Flow using react-navigation v6 in React Native. Error: Invalid hook call. Invariant
Issue with Auth Flow using react-navigation v6 in React Native. Error: Invalid hook call. Invariant

Time:12-10

I'm new to React Native and I can't figure out how to make my Auth Flow work with react-navigation v6. I have a file named AuthContext.js, which is where I declare my Context object and manage the Auth state. I suspect the issue has something to do with the way I'm exporting AuthContext and or the way I'm using AuthContext in my App.js.

Snack Example

Any help is greatly appreciated!

AuthContext.js

import React, { createContext, useReducer, useEffect, useMemo } from 'react';
import * as SecureStore from 'expo-secure-store';
import myAPI from './myAPI'

// declare AuthContext as a Context object
const AuthContext = createContext();

// reducer to manage auth state
const [state, dispatch] = useReducer(
  (prevState, action) => {
    switch (action.type) {
      case 'AUTH_ERROR':
        return {
          ...prevState,
          errorMessage: action.error,
        };
      case 'CLEAR_AUTH_ERROR':
        return {
          ...prevState,
          errorMessage: '',
        };
      case 'LOG_IN':
        return {
          ...prevState,
          isLogout: false,
          userToken: action.token,
        };
      case 'LOG_OUT':
        return {
          ...prevState,
          isLogout: true,
          userToken: null,
        };
      case 'RESTORE_TOKEN':
        return {
          ...prevState,
          userToken: action.token,
          isLoading: false,
        };
      default:
        return state;
    }
  },
  {
    isLoading: true,
    isLogout: false,
    userToken: null,
    //errorMessage: '',
  }
);

//****    fetch token action    ****//
//**********************************//
useEffect(() => {
  const fetchToken = async () => {
    let userToken;
    try {
      userToken = await SecureStore.getItemAsync('userToken');
    } catch (err) {
      // Restoring token failed
      console.log('Unable to fetch token.');
    }
    // After restoring token, we may need to validate it
    dispatch({ type: 'RESTORE_TOKEN', token: userToken });
  };
  fetchToken();
}, []);
//**********************************//

//**** signUp, logIn, and logOut actions   ****//
//*********************************************//
const authContext = useMemo(
  () => ({
    signUp: async ({ email, username, password }) => {
      try {
        const response = await myAPI.post('/public/auth/users', {
          email,
          username,
          password,
        });
        console.log('\n', response.status);
        console.log('\n', response.data);
        // to log the user in after signup, store the token and dispatch the LOG_IN action
        // await SecureStore.setItemAsync('userToken', response.data.access_token);
        // dispatch({ type: 'LOG_IN', token: response.data.access_token });
      } catch (err) {
        dispatch({ type: 'AUTH_ERROR', error: err.response.data.detail });
        console.log('\n', err.response.status);
        console.log('\n', err.response.data);
      }
    },
    logIn: async ({ username, password }) => {
      var qs = require('query-string');
      const config = {
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      };

      try {
        const response = await myAPI.post(
          '/public/auth/users/login',
          qs.stringify({ username, password }),
          config
        );
        console.log('\n', response.status);
        await SecureStore.setItemAsync('userToken', response.data.access_token);
        dispatch({ type: 'LOG_IN', token: response.data.access_token });
      } catch (err) {
        dispatch({ type: 'AUTH_ERROR', error: err.response.data.detail });
        console.log('\n', err.response.status);
        console.log('\n', err.response.data);
      }
    },
    logOut: async () => {
      await SecureStore.deleteItemAsync('token');
      dispatch({ type: 'LOG_OUT' });
    },
    clearErrorMessage: () => dispatch({ type: 'CLEAR_AUTH_ERROR' }),
  }),
  []
);
//*********************************************//

export const AuthProvider = ({ children }) => {
  return (
    <AuthContext.Provider value={authContext}>{children}</AuthContext.Provider>
  );
};

export default AuthContext;

App.js

// Import Installed Libraries
import * as React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import 'react-native-gesture-handler';
import { Ionicons, FontAwesome, Foundation } from '@expo/vector-icons';

// Import Local Files
import SignupScreen from './components/SignupScreen';
import LoginScreen from './components/LoginScreen';
import FeedScreen from './components/FeedScreen';
import SearchScreen from './components/SearchScreen';
import ProfileScreen from './components/ProfileScreen';
import SettingsScreen from './components/SettingsScreen';
import LoadingScreen from './components/LoadingScreen';
import { AuthProvider } from './components/AuthContext';

// Authentication Flow
const AuthStack = createNativeStackNavigator();
const AuthFlow = () => (
  <AuthStack.Navigator>
    <AuthStack.Screen name="Login" component={LoginScreen} />
    <AuthStack.Screen name="SignUp" component={SignupScreen} />
  </AuthStack.Navigator>
);

// Feed Flow
const FeedStack = createNativeStackNavigator();
const FeedFlow = () => (
  <FeedStack.Navigator initialRouteName="Feed">
    <FeedStack.Screen name="Feed" component={FeedScreen} />
  </FeedStack.Navigator>
);

// Search Flow
const SearchStack = createNativeStackNavigator();
const SearchFlow = () => (
  <SearchStack.Navigator initialRouteName="Search">
    <SearchStack.Screen name="Search" component={SearchScreen} />
  </SearchStack.Navigator>
);

// Profile Flow
const ProfileStack = createNativeStackNavigator();
const ProfileFlow = () => (
  <ProfileStack.Navigator initialRouteName="Profile">
    <ProfileStack.Screen name="Profile" component={ProfileScreen} />
    <ProfileStack.Screen name="Settings" component={SettingsScreen} />
  </ProfileStack.Navigator>
);

// Bottom Tap App Flow
const AppTabs = createBottomTabNavigator();
const AppFlow = () => (
  <AppTabs.Navigator
    screenOptions={{
      tabBarShowLabel: false,
      tabBarInactiveTintColor: '#858585',
      tabBarActiveTintColor: '#000000',
    }}>
    <AppTabs.Screen
      name="FeedFlow"
      component={FeedFlow}
      headerMode="none"
      options={{
        tabBarIcon: ({ color }) => (
          <Foundation name="home" size={25} color={color} />
        ),
        headerShown: false,
      }}
    />
    <AppTabs.Screen
      name="SearchFlow"
      component={SearchFlow}
      options={{
        tabBarIcon: ({ color }) => (
          <FontAwesome name="search" size={25} color={color} />
        ),
        headerShown: false,
      }}
    />
    <AppTabs.Screen
      name="ProfileFlow"
      component={ProfileFlow}
      options={{
        tabBarIcon: ({ color }) => (
          <Ionicons name="ios-person" size={25} color={color} />
        ),
        headerShown: false,
      }}
    />
  </AppTabs.Navigator>
);

const RootStack = createNativeStackNavigator();
const RootStackFlow = () => {
  const { state } = React.useContext(authContext);

  if (state.isLoading) {
    // We haven't finished checking for the token yet
    return <LoadingScreen />;
  }

  return (
    <RootStack.Navigator>
      {state.userToken == null ? (
        // No token found, user isn't logged in
        <RootStack.Screen
          name="AuthStack"
          component={AuthFlow}
          options={{
            headerShown: false,
          }}
        />
      ) : (
        // User is logged in
        <RootStack.Screen
          name="AppTabs"
          component={AppFlow}
          options={{
            headerShown: false,
          }}
        />
      )}
    </RootStack.Navigator>
  );
};

export default () => {
  return (
    <AuthProvider>
      <RootStackFlow />
    </AuthProvider>
  );
};

package.json

{
  "main": "node_modules/expo/AppEntry.js",
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web",
    "eject": "expo eject"
  },
  "dependencies": {
    "@react-native-async-storage/async-storage": "^1.15.14",
    "@react-navigation/bottom-tabs": "^6.0.9",
    "@react-navigation/drawer": "^6.1.8",
    "@react-navigation/native": "^6.0.6",
    "@react-navigation/native-stack": "^6.2.5",
    "axios": "^0.24.0",
    "expo": "^43.0.3",
    "native-base": "^3.2.2",
    "query-string": "^7.0.1",
    "react": "^17.0.2",
    "react-native": "^0.64.3",
    "react-native-gesture-handler": "^1.10.2",
    "react-native-masked-text": "~1.13.0",
    "react-native-reanimated": "^2.2.4",
    "react-native-safe-area-context": "^3.3.2",
    "react-native-screens": "~3.8.0",
    "react-native-svg": "^12.1.1",
    "react-native-switch-selector": "^2.1.4",
    "react-native-tab-view": "^3.1.1",
    "expo-secure-store": "~11.0.3"
  },
  "devDependencies": {
    "@babel/core": "^7.16.0"
  },
  "private": true
}

Console Log Screenshot

CodePudding user response:

Here is a working example: expo auth flow

if you want to use the dispatch for example from:

const LoginScreen = () => {
  const { state, dispatch } = React.useContext(AuthContext);

 // here you can use a function to login or something, and dispatch it.

  return (
    <View>
      <Text>Login Screen</Text>
    </View>
  );
};
  • Related