React’s useState and Context for Auth Routing

Nick Bell
3 min readJan 8, 2019

--

In this article, I will explain how using features in React 16.6 and 16.7 can make route protection very easy.

Requirements:

  1. React 16.7.alpha-*
  2. React Router

useState is a “hook” added in React 16.7 to make functional components stateful, whereas before developers needed to rely on either a store or HOC to pass state into functional components. They can be used as simply as this:

import React, {useState} from 'react';export default () => {
const [count, setCount] = useState(0);
return <button onClick={()=>setCount(count+1)}/>
}

The useState definition here sets the const count to 0, and provides a method to set it called setCount.

Context is another new feature to the core React library, added in React 16.6, that allows developers to avoid the hideous prop-drilling that was needed to pass props down a long component tree. There are other options to achieve this, most notably Redux. It allows us to drastically simplify how components are structured, and allows us to keep components knowing only about things they care about, rather than parents needing to know everything about their children.

Context in React 16.6 forces us to write render-prop driven components, which can look pretty ugly:

import React, {createContext} from 'react';const ExampleContext = createContext();class Provider extends React.Component {
constructor(props) {
this.state = {color: "Red"}
this.setColor = this.setColor.bind(this);
}
this.setColor(color) {
this.setState(color: color);
}
render() {
return (
<ExampleContext.Provider value={this.state}>
{this.props.children}
</ExampleContext.Provider>
)
}
}
const Consumer = () => (
<ExampleContext.Consumer>
{({color, setColor}) => (
<button color={color} onClick={()=>setColor("Green")}>
Colored Button
</button>
)
}
</ExampleContext.Consumer>
)
export default = () => (
<Provider>
<Consumer>
</Provider>
)

Look at all those brackets and parens! Plus using class components is so 2018…

In React 16.7, there is another hook called useContext that allows us to drastically simplify the above example, and when combined with useState, allows us to build the same component in a semantic manner:

import React, { createContext, useState, useContext } from 'react';const ExampleContext = createContext();const Provider = ({children}) => {
const [color, setColor] = useState("Red");
const value = { color, setColor };
return (
<ExampleContext.Provider>
{children}
</ExampleContext.Provider>
)
}
export default () => {
const {color, setColor} = useContext(ExampleContext);
return (
<Provider>
<button color={color} onClick={()=>setColor("Green")}>
Colored Button!
</button>
<Provider>
)
}

Now, on to the meat of what we want to talk about, Authentication.

In this example, let’s assume a user can sign in using a third-party service which we’ll stub out, and can have two roles: admin and super_admin. We want to limit some pages (like editing other users, for example) to super_admins, but allow all users to edit their own information.

Using Context and setState, we can implement a very simple, yet flexible, authentication solution for our entire application!

First, let’s create a component called RootContext, which takes {children} as a prop and wraps that in the authentication context. This component reads and writes the user’s authentication status and role in local storage.

// RootContext.js
import React, { useEffect, useState } from 'react';
export const RootContext = React.createContext();export default ({ children }) => {
const prevAuth = window.localStorage.getItem('auth') || false;
const prevAuthBody = window.localStorage.getItem('authBody')) || null;
const [authenticated, setAuthenticated] = useState(prevAuth);
const [authBody, setAuthBody] = useState(prevAuthBody);
useEffect(
() => {
window.localStorage.setItem('authenticated', authenticated);
window.localStorage.setItem('authBody', authBody);
},
[authenticated, authBody]
);
const defaultContext = {
authenticated,
setAuthenticated,
authBody,
setAuthBody
};
return (
<RootContext.Provider value={defaultContext}>
{children}
</RootContext.Provider>
);
};

This component does all the storage of authentication we need. It utilizes useState to store the authentication status (authenticated) and auth information ( authBody), reading them from local storage. Then, in the useEffect function, we’re saying “any time authenticated or authBody changes, execute this block” — i.e. update them in local storage. Lastly, we’re combining both the object and updaters into one object, and passing that object to RootContext as the value.

Now, we can wrap the root node of the application in RootContext like so:

<RootContext>
<App/>
</RootContext>

This allows us to create an AuthenticatedRoute component using React Router:

import React, { useContext } from 'react';
import { Redirect, Route } from 'react-router-dom';
import { RootContext } from '../../RootContext';
export default ({ render, ...routeProps }) => {
const { authenticated } = useContext(RootContext);
return (
<Route
{...routeProps}
render={() => (authenticated ?
render() :
<Redirect to='/login' />)
}
/>
);
};

This component is a simple wrapper function that says “Look in the Root Context for authenticated = true, and evaluate this switch statement based on that answer”. It’s really as simple as that.

Thanks for reading!

--

--