Basic Authentication with Expo and React Hooks that mimic Redux

Ahmad Awdiyanto
4 min readMay 10, 2020

In my recent Expo project I decide to not using redux in it, and take the advantage of React Hooks, like useContext and useReducer

I will show you how I do that.

Installation

First, create a folder and initiate an Expo project.

$ mkdir example
$ cd example && expo init

until this step, you can run your project and test it in your devices with expo client

$ yarn start

Prepare some pages

Create Pages folder in your root project, then create Home.js file

// Filename: Home.jsimport React from 'react'
import { View, Text, TouchableOpacity } from 'react-native'
const Home = ({navigation}) => {
return (
<View>
<Text>this is our home page</Text>
<TouchableOpacity onPress={() =>
navigation.navigate('Login')}>
<Text>To Login Page</Text>

</TouchableOpacity>
</View>
)
}
export default Home

Then create Login.js file

// Filename: Login.jsimport React from 'react'
import { View, Text, TouchableOpacity } from 'react-native'
const Login = ({navigation}) => {
return (
<View>
<Text>this is our login page</Text>
<TouchableOpacity onPress={() =>
navigation.navigate('Home')}>
<Text>To Home Page</Text> </TouchableOpacity>
</View>
)
}
export default Login

Navigation with React navigation

Now we have to setup a navigation to move between our home to login page. you can follow the instruction from https://reactnavigation.org/docs/getting-started/

$ yarn add @react-navigation/native

then install it dependencies

$ expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view$ yarn add @react-navigation/stack

now update your App.js

// Filename: App.jsimport React from 'react';
import Home from './Pages/Home'
import Login from './Pages/Login'
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();export default function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Login" component={Login} />
</Stack.Navigator>
</NavigationContainer>
);
}

Context and Provider

Our project is running well at this point. Now create Contexts folder in your root project.

$ mkdir Contexts

then create new file in Context folder we create earlier and name it AuthContext.js you can give any name to it.

// filename: AuthContext.jsimport React, { useReducer, useEffect, useMemo } from 'react';let reducer = (state, action) => {
switch (action.type) {
case "authentication_starting":
return {
...state,
auth_data: {},
logged_in: false,
auth_message: ''
}
break;
case "authentication_done":
return {
...state,
auth_data: action.data,
logged_in: true,
auth_message: ''
}
break;
case "authentication_failed":
return {
...state,
auth_data: {},
logged_in: false,
auth_message: 'Something went wrong'
}
break;
}
}
let initialState = {
auth_data: {},
logged_in: false,
auth_message : ''
}
const AuthContext = React.createContext(initialState);function AuthProvider(props) {
const [state, dispatch] = useReducer(reducer, initialState);

const value = useMemo(() => {
return { state };
}, [state]);

return (
<AuthContext.Provider value={{ value, dispatch }}>
{props.children}
</AuthContext.Provider>
);
}
export { AuthContext, AuthProvider };

let reducer are a function that take state and action to return new state to be stored.

let initialState are Object that hold initial value of the state.

const AuthContext will hold our context

function AuthProvider is our state provider, this component will inherit any of it state to its child

last, export it.

Integrate AuthContext with App.js

update content of our app.js, wrap our existing App.js with AuthProvider

import { AuthProvider } from './Contexts/AuthContext'...<AuthProvider>
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Login" component={Login} />
</Stack.Navigator>
</NavigationContainer>
</AuthProvider>
...

Create Login Function in AuthProvider

...const [state, dispatch] = useReducer(reducer, initialState);const login = ({ username, password }) => {
dispatch({ type: 'authentication_starting' })
// do login process, call an API endpoint here
// for testing purpose i will use dummy data
let login_data = {
user_name: username,
first_name: 'Testing',
last_name: 'Something',
email: 'some@thing.com'
}
dispatch({
type: 'authentication_done',
data: login_data
})
}
const value = useMemo(() => {
return { state, login }; // <-- and add login function here
}, [state]);
...

now let us update our UI to simulate login, update Login.js

// Filename: Login.jsimport React, { useContext } from 'react'
import { AuthContext } from '../Contexts/AuthContext'
...const Login = ({navigation}) => {
const { value: authProp } = useContext(AuthContext);
return (
<View>
<Text>this is our login page</Text>
{ authProp.state.logged_in ?
<View>
<Text style={{fontSize: 30}}>Hi,
{authProp.state.auth_data.first_name} </Text>
</View> :
<TouchableOpacity onPress={() =>
authProp.login({username: 'psudo', password: 'code'})}
<Text>Login</Text>
</TouchableOpacity>
}
<TouchableOpacity onPress={() => navigation.navigate('Home')}
<Text>To Home Page</Text>
</TouchableOpacity>
</View>
)
}
...

and Home.js

// Filename: Home.jsimport React, { useContext } from 'react'
import { AuthContext } from '../Contexts/AuthContext'
...const Home = ({navigation}) => {
const { value: authProp } = useContext(AuthContext);
return (
<View>
<Text>this is our home page</Text>
{ authProp.state.logged_in &&
<View>
<Text style={{fontSize: 30}}>Hi,
{authProp.state.auth_data.first_name} </Text>
</View>
}
<TouchableOpacity onPress={() => navigation.navigate('Home')}
<Text>To Home Page</Text>
</TouchableOpacity>
</View>
)
}
...

now home page and login page share the same data.

--

--