Basic Authentication with Expo and React Hooks that mimic Redux
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.