Hi there,
In this build I am going to show the use of the React Context API and the useReduce hook to manage state in a react application. To demonstrate this we will build a user authentication react component, taking advantage of the simple FaceIO face authentication system to authenticate our users. Once they are authenticated we will then pass the user data to the state and be able to consume it in other components.
Why we will use FaceIO
FaceIO is a facial authentication system for websites and web based applications.
FaceIO is,
Very easy to implement. It takes less than 3 lines of code and basically entails adding a script like the google gtag. For more you can check here.
Seamless and does not entail the user getting FIDO keys, OTP codes and security questions thereby enhancing user experience in our site.
Very secure. First it provides a defense level facial recognition accuracy ensuring that we accurately authenticate each and every user using their facial recognition engine. Secondly it guarantees the privacy of the user by ensuring that there data is safe and also that they don’t actually store the face of the user but rather a facial vector.
This is part two of a two part series if you would like to follow along part one is here.
We are going to pick up from where we left last time. Firstly we are going to arrange our content appropriately.
In the src folder let us create a folder pages and in it create two files named Home.tsx and SignUp.tsx.
Next we are going to add react router dom
So that your build should now look like this,
We are going to transfer all the contents of App.tsx to the home page except the SignUpForm.
const Home = () => {
return (
<div className='min-h-screen flex flex-col '>
<h1 className='text-3xl font-bold text-yellow-600 flex justify-center items-center'>
FaceIO authentication using react and typescript
</h1>
</div>
);
};
export default Home
Import the signup component to the sign up page.
import SignupForm from '../components/SignupForm';
const SignUp = () => {
return (
<div>
<SignupForm />
</div>
);
};
export default SignUp;
Let us now bring the react route dom into the App.tsx so that we are able to route to different pages of our app.
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import Home from './pages/Home';
import SignUp from './pages/SignUp';
function App() {
return (
<div className='min-h-screen flex flex-col justify-center items-center'>
<BrowserRouter>
<Routes>
<Route path='/' element={<Home />} />
<Route path='signup' element={<SignUp />} />
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
Since now we have finished the arrangement of our site let us get into the gist of our build.
We are going to create a login function to login through FaceIO (remember we had created a signup function in the previous part).
Then we are going to take the user data we get from FaceIO response and pass it into the state using useContext and useReducer.
Setting up useContext in React and Typescript
Let us create a new folder in src folder named context and in there a file named StateProvider.tsx, in there we will make userContext provider.
import React, {createContext, ReactNode} from 'react';
export const userContext = createContext()
const StateProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
return(
<userContext.Provider value={}>
{children}
</userContext.Provider>
)
};
export default StateProvider;
Because we are using typescript we will need to provide types for our build,
in src/@types/user.d.ts we will create the userContext type.
So our createContext will be of type userContextType since when there will be no user the user will be null.
Now let create a folder helpers in src and in it a file named Reducers.ts
/** src/helpers/Reducer.ts */
import { userContextType, Action } from '../@types/user';
//the initial state of the user
export const initialState = {
user: null,
};
//the action we are going to take when we login that is set the user
export const actionTypes = {
SET_USER: 'SET_USER',
};
//the reducer function note the parameter type annotations
export const reducer = (state: userContextType, action: Action) => {
console.log(action);
switch (action.type) {
case actionTypes.SET_USER:
return {
...state,
user: action.user,
};
default:
return state;
}
};
There is one type we did not define that is the action type let us update it accordingly
Go to src/@types/user.d.ts and add,
type Action = {
type: 'SET_USER';
user: Iuser;
};
Now once we are done with this we can update StateProvider as this,
import React, { createContext, ReactNode, useReducer } from 'react';
import { userContextType } from '../@types/user';
import { initialState, reducer } from '../helpers/Reducers';
export const userContext = createContext<{
state: userContextType;
dispatch: React.Dispatch<any>;
}>({
state: initialState,
dispatch: () => null,
});
const StateProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
//bring the useReducer hook and feed it with the reducer function and the initial state
const [state, dispatch] = useReducer(reducer, initialState);
return (
//update the value with the state and dispatch thus you are able to access this values in any component or page
<userContext.Provider value={{ state, dispatch }}>
{children}
</userContext.Provider>
);
};
Go to App.tsx import the StateProvider and wrap the entire build with it.
<StateProvider>
<div className='min-h-screen flex flex-col justify-center items-center'>
<BrowserRouter>
<Routes>
<Route path='/' element={<Home />} />
<Route path='signup' element={<SignUp />} />
</Routes>
</BrowserRouter>
</div>
</StateProvider>
Let us now create a login component to be able to feed user data to the state.
In the folder componets create a file Login.tsx and in it create a login,
import { Link } from 'react-router-dom';
const Login = () => {
return (
<div>
<h1 className='text-3xl font-bold text-blue-600 mb-4'>
Welcome, please login
</h1>
<button className='w-full p-3 bg-blue-700 rounded-md text-white text-sm mb-4'>
Login
</button>
<div>
<p>You don't have an account? Please sign-up.</p>
<Link to='/signup'>
<button className='w-full p-3 bg-white outline-blue-800 rounded-md text-blue-800 text-sm mb-4'>
Sign Up
</button>
</Link>
</div>
</div>
);
};
export default Login;
This component will be our gate guard that will refuse anyone admittance to our site unless they have loged in
So in our home component we will need to make some few changes.
We will query whether there is a user in the state that is if user is not null then we will show the home otherwise we will show
the login component
..... return (
<div className='min-h-screen flex flex-col '>
{!state?.user ? (
<Login />
) : (
<div>
<h1 className='text-3xl font-bold text-yellow-600 flex justify-center items-center'>
FaceIO authentication using react and typescript
</h1>
</div>
)}
</div>
);
This is how your page should look like by now,
Let us now go back to the login component and update the logic, that is call FaceIO and dispatch user data.
When we call FaceIO to authenticate (FaceIO authentication is documented here), and the authentication is successful we will receive a payload with the data the user provided when they signed up. That payload is what we are going to dispatch to the state.
import React, { useContext, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { userContext } from '../context/StateProvider';
import { actionTypes } from '../helpers/Reducers';
const Login = () => {
//we load faceIO using a useEffect hook
let faceio: any;
useEffect(() => {
faceio = new faceIO('fioa48b7');
}, []);
//we use a useContext hook dispatch to be able to dispatch our user to the state
const { dispatch } = useContext(userContext);
//we set up the handle login function
const handleLogin = async () => {
try {
let response = await faceio.authenticate({
locale: 'auto',
});
alert(`
You have been identified successfully
Unique Facial ID: ${response.facialId}
PayLoad: ${JSON.stringify(response.payload)}
`);
dispatch({ type: actionTypes.SET_USER, user: response.payload });
alert('You have successfully logged in');
} catch (error) {
console.log(error);
alert('Failed to login, please refresh and try again');
}
};
return (
<div className='flex flex-col justify-center items-center'>
<h1 className='text-3xl font-bold text-blue-600 mb-4'>
Welcome, please login
</h1>
<button onClick={handleLogin} className='w-full p-3 bg-blue-700 rounded-md text-white text-sm mb-4'>
Login
</button>
<div>
<p>You don't have an account? Please sign-up.</p>
<Link to='/signup'>
<button
className='w-full p-3 bg-white outline-blue-800 rounded-md text-blue-800 text-sm mb-4'
>
Sign Up
</button>
</Link>
</div>
</div>
);
};
export default Login;
Once we are done with that our application is good and ready but let us add a welcome user statement in the home page.
Under the h1 tags add.
<h2 className='text-blue-900 pt-28 font-bold'>
Welcome {state.user.name} Email:{state.user.email}
</h2>
We are now done, if you were successful, and you try loging in you will see,
The whole of this build can be found in Github, while you are there please add a star to it.
For more on FaceIO
Originally published here.