Protected Routes with React Function Components
Tags: react, javascript, webdev, hooks
Published: 2020-01-21 18:59:12

![React Hooks + Router](https://i.postimg.cc/pXPrvMdp/Protected-Routes-Banner.png)

Protected routes allow us to ensure only logged in users can access certain parts of our site that may contain private user information. In this post, we'll look at one way of implementing protected routes in React using function components along with [react-router](https://reacttraining.com/react-router/core/guides/philosophy). We'll do this first with the *useState* hook, but in a subsequent post we'll also see how this can be achieved with React's Context API, which is a built-in solution for giving nested child components access to our application's state without the need to pass props all the down our component tree, a practice often referred to as prop drilling.

### Getting Started
Let's start by creating a new React project. I'll be using the [Create-React-App](https://github.com/facebook/create-react-app) utility to automatically generate a baseline React app without needing to write any of the boilerplate configuration.

I'm using [yarn](https://yarnpkg.com/lang/en/) but you can use npx if you prefer. Let's run the following command in our terminal:
```
yarn create react-app protected-routes
```
*cd* into the newly created __protected-routes__ folder and run *yarn start* (or *npm start*) to fire up the dev server. It should open up your browser and display the React logo. 

![Create React App default page](https://i.postimg.cc/g2GqN46Z/create-react-app.png)


### React Router
So now that we have a basic React app, let's create a new directory called __components__ inside the __src__ directory. We'll create 2 components inside this directory: Landing.js and Dashboard.js

###### protected-routes/src/components/Landing.js
```jsx
import React from 'react';
import { Link } from 'react-router-dom';

const Landing = () => {
  return (
    <div>
      <h1>Landing</h1>
      <p><Link to='/dashboard'>View Dashboard</Link></p>
      <button>Log In</button>
    </div>
  )
};

export default Landing;

```
*Note, we're using the `<Link>` component from react-router instead of an anchor tag so that our app won't reload the page when the user clicks the link.*

###### protected-routes/src/components/Dashboard.js
```jsx
import React from 'react';

const Dashboard = () => {
  return (
    <div>
      <h1>Dashboard</h1>
      <p>Secret Page</p>
      <button>Log Out</button>
    </div>
  )
};

export default Dashboard;
```

We want to render each of these components depending on the route we're on. To do so, let's install __react-router-dom__.

```
yarn add react-router-dom
```

Open up the __App.js__ file under the __src__ directory and import in the following components from our newly installed package right after the CSS import. Note, I'm giving *BrowserRouter* the alias *Router* for brevity. Let's also import the 2 components we created.

###### protected-routes/src/App.js
```javascript
import { BrowserRouter as Router, Route } from 'react-router-dom';
import Landing from './components/Landing';
import Dashboard from './components/Dashboard';
```

We can delete all the stock HTML inside the return statement besides the top level `<div>` and replace it with our own. We'll use the *Route* component we just imported to specify which component corresponds to which route. Lastly, we'll need to wrap the `<Route>` components with our `<Router>` (aka *BrowserRouter*) component to tie it all together. Our App.js file should look like this: 

```jsx
import React from 'react';
import logo from './logo.svg';
import './App.css';

import { BrowserRouter as Router, Route } from 'react-router-dom';
import Landing from './components/Landing';
import Dashboard from './components/Dashboard';

function App() {
  return (
    <div className="App">
      <Router>
        <Route exact path='/' component={Landing} />
        <Route exact path='/dashboard' component={Dashboard} />
      </Router>
    </div>
  );
}

export default App;
```

Now, when we visit *localhost:3000* in the browser, we should see a rather plain page with the headline "Landing", a link to */dashboard*, and a button for "Log In" (which doesn't do anything yet). Click the link and we'll see that the page now renders the Dashboard component, since our route has now changed to *localhost:3000/dashboard*

![Click on link to /dashboard](https://i.postimg.cc/y6PRkq8S/landing-to-dashboard2.gif)

With react-router, there's no need to write additional conditional-rendering logic or utilize state to keep track of which component we should be displaying. Pretty cool, right? 😎. But we still have an issue: our secret Dashboard page is accessible to anyone and everyone. How we can allow only the people who are authorized to view the page navigate to it? First, we'll need to keep track of whether or not our user is logged in our not. Let's see how we can do that using the *useState* hook.

### useState Hook
Prior to the introduction of hooks in version 16.8, the only way to have stateful components in React was through classes. As it's name implies, the [useState hook](https://reactjs.org/docs/hooks-state.html) allows us to use state inside a function component. Let's implement *useState* to keep track of our logged in status. 

In __App.js__, import *useState* using destructuring in the same line we import React.

###### protected-routes/src/App.js
```javascript
import React, { useState } from 'react';
```

Next, inside of our App function and right before our return block, let's use array destructuring to create a *user* and *setUser* variables, which are the first and second elements that useState returns to us, respectively. We'll pass in an initial state of false, to indicate that we are not logged in when we first visit the page. 

We'll also create a function called __handleLogin__ which will invoke *setUser* and flip the user value to *true* when we click "Log In".

```javascript
function App() {
  const [user, setUser] = useState(false);

  const handleLogin = e => {
    e.preventDefault();
    setUser(true);
  }
```

We need to pass this __handleLogin__ function to our __Landing__ component, but it won't work with our current set up since we're passing __Landing__ in as a *component* prop to Route. We'll need to change the prop from *component* to *render* and pass it in as a function that returns our __Landing__ component. Trust me, it sounds more confusing than it is, but in case you want to read more about it, feel free to check out [this article](https://tylermcginnis.com/react-router-pass-props-to-components/).

Our App.js should look like this:
###### protected-routes/src/App.js
```jsx
import React, { useState } from 'react';
import './App.css';

import { BrowserRouter as Router, Route } from 'react-router-dom';
import Landing from './components/Landing';
import Dashboard from './components/Dashboard';

function App() {
  const [user, setUser] = useState(false)

  const handleLogin = e => {
    e.preventDefault();
    setUser(true);
  }

  return (
    <div className="App">
      <Router>
        <Route exact path='/' handleLogin={handleLogin} render={props => <Landing {...props} user={user.toString()} handleLogin={handleLogin} />} />
        <Route exact path='/dashboard' component={Dashboard} />
      </Router>
    </div>
  );
}

export default App;
```
*Note, I'm passing in __user__ as a string so we can display it in our Landing component. If you have the React Developer Tools Chrome extension, you can use that to inspect the app's state and make sure everything is working properly.*

Let's add an onClick handler to our button in the __Landing__ component using the *handleLogin* function we just passed down as a prop. Remember to pass in *props* as an argument, in order to access it inside our component.

###### protected-routes/src/components/Landing.js
```jsx
import React from 'react';
import { Link } from 'react-router-dom';

const Landing = props => {
  return (
    <div>
      <h1>Landing</h1>
      <p><Link to='/dashboard'>View Dashboard</Link></p>
      <p>Logged in status: {props.user}</p>
      <button onClick={props.handleLogin}>Log In</button>
    </div>
  )
};

export default Landing;
```

We should now be able to click the log in button and see our status change to *true*. This is our state being toggled.

![changing log in status](https://i.postimg.cc/3rXPN8mC/log-in-status.gif)

Great, we have our logged in status. Don't worry about wiring up the *Log Out* button in *Dashboard.js* for now, we'll do that in the next section.

Now we need a way to allow a user to visit the Dashboard component only if their logged in status is *true*. How can we enforce that? Enter the protected route component.

### Protected Routes
We'll create a new file called __ProtectedRoute.js__ within the __components__ directory. At a high-level, this component will act as a wrapper over react-router's __Route__ component, ultimately returning out the component we wish to render. In other words, we're passing in the component we want to render through an intermediary that abstracts away the need to keep track of state inside our `<Dashboard>` component. In our case, `<ProtectedRoute>` becomes a [higher-order component](https://reactjs.org/docs/higher-order-components.html). It will be responsible for checking if we're logged in before rendering the `<Dashboard>` compoent, otherwise it will redirect users to another page (which we'll create shortly).

###### protected-routes/src/components/ProtectedRoute.js
```jsx
import React from 'react';
import { Route } from 'react-router-dom';

const ProtectedRoute = ({ component: Component, ...rest }) => {
  return (
    <Route {...rest} render={
      props => <Component {...rest} {...props} />
    } />
  )
}

export default ProtectedRoute;
```

ProtectedRoute takes in all the same props that we had previously passed into our Route component and returns the very same Route component using the *render* prop. Let's step through each line individually:

```jsx
const ProtectedRoute = ({ component: Component, ...rest }) => {
``` 
- Here we're passing in an object which contains all the props that we'll pass in when we call our `<ProtectedRoute>` component from __App.js__. We specify *Component* so we can reference it later in our render prop. We use [rest](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Rest_syntax_parameters) syntax to pass in our other props without having to know or list them individually.

```jsx
return (<Route {...rest} render={ props => <Component {...rest} {...props} />
``` 
- We're simply returning __react-router__'s `<Route>` component and using its *render* prop to render the Component we passed in as an argument. We pass in the *...rest* props from before in addition to the default props that `<Route>` provides normally.

We'll see how we can add logic here to check whether or not we're logged in. First let's make sure we haven't broken anything.

Open up App.js, import the __ProtectedRoute__ component, and replace *Route* with *ProtectedRoute* where we specify the */dashboard* route. Your return should look like this:

###### protected-routes/src/App.js
```jsx
  return (
    <div className="App">
      <Router>
        <Route exact path='/' handleLogin={handleLogin} render={
          props => <Landing {...props} user={user.toString()} 
          handleLogin={handleLogin} />} />
        <ProtectedRoute exact path='/dashboard' component={Dashboard} />
      </Router>
    </div>
  );
```

Fingers crossed, it should work exactly the same. Now let's go back and fix the log out button before we add the logic to our ProtectedRoute.

In __App.js__, create a *handleLogout* route that looks identical to the *handleLogin* route except that it toggles our *user* state to false. Then pass it down a prop to our ProtectedRoute component. Our full file now looks like this:


###### protected-routes/src/App.js
```jsx
import React, { useState } from 'react';
import './App.css';

import { BrowserRouter as Router, Route } from 'react-router-dom';
import Landing from './components/Landing';
import Dashboard from './components/Dashboard';
import ProtectedRoute from './components/ProtectedRoute';

function App() {
  const [user, setUser] = useState(false)

  const handleLogin = e => {
    e.preventDefault();
    setUser(true);
  }

  const handleLogout = e => {
    e.preventDefault();
    setUser(false);
  }

  return (
    <div className="App">
      <Router>
        <Route exact path='/' handleLogin={handleLogin} render={
          props => <Landing {...props} user={user.toString()} handleLogin={handleLogin} />} />
        <ProtectedRoute exact path='/dashboard' handleLogout={handleLogout} component={Dashboard} />
      </Router>
    </div>
  );
}

export default App;
```

Open up __Dashboard.js__ and add an onClick handler that will trigger our handleLogout function when we click the *Log Out* button. Remember to pass in a *props* argument to our Dashboard function where we previously had empty parentheses.  

```jsx
<button onClick={props.handleLogout}>Log Out</button>
```
Our application should now be able to keep track of our logged in status. You can click each button and use the Back button to see it in action:

![Log out](https://i.postimg.cc/vB3WB2Ls/log-out.gif)

### Redirect Page

Let's create one more component that we'll redirect users to if they try to access our */dashboard* route without first logging in. We'll make this component look a little more interesting than the rest of our black and white app by copying [this cool 403 page from codepen](https://codepen.io/anjanas_dh/pen/ZMqKwb) by user [@anjanas_dh](https://codepen.io/anjanas_dh)

In the __components__ directory, make a file called __Unauthorized.js__ and add the following markup.
 
###### protected-routes/src/Unauthorized.js
```jsx
import React from 'react';
import { Link } from 'react-router-dom';
import '../Unauthorized.scss';

const Unauthorized = () => {
  return (
    <div className='container'>
      <div class="gandalf">
        <div class="fireball"></div>
        <div class="skirt"></div>
        <div class="sleeves"></div>
        <div class="shoulders">
          <div class="hand left"></div>
          <div class="hand right"></div>
        </div>
        <div class="head">
          <div class="hair"></div>
          <div class="beard"></div>
        </div>
      </div>
      <div class="message">
        <h1>403 - You Shall Not Pass</h1>
        <p>Uh oh, Gandalf is blocking the way!<br />Maybe you have a typo in the url? Or you meant to go to a different location? Like...Hobbiton?</p>
      </div>
      <p><Link to='/'>Back to Home</Link></p>
    </div>
  )
}

export default Unauthorized;
```

Create a new SCSS file called __Unauthorized.scss__ in the __src__ directory and paste in [these styles](https://pastebin.com/czc6mCtc). I included the link to a pastebin rather than the code itself since the file is 270 lines long.


Since this is a Sass file, it won't work out of the box, but don't fret! We only need to install the *node-sass* module in order to get us on our wizard way 🧙‍♂️.

```
yarn add node-sass
```

Open up __App.js__ and import the __Unauthorized__ component and add it to our list of __Route__ components. 
```javascript
import Unauthorized from './comoponents/Unauthorized';
/* omitting some of the other LOC to save space */
  return (
    <div className="App">
      <Router>
        <Route exact path='/' handleLogin={handleLogin} render={
          props => <Landing {...props} user={user.toString()}
            handleLogin={handleLogin} />} />
        <ProtectedRoute exact path='/dashboard' handleLogout={handleLogout} component={Dashboard} />
        <Route exact path='/unauthorized' component={Unauthorized} />
      </Router>
    </div>
  );
/* omitting some of the other LOC to save space */
```

If all went according to plan, you should see the following page when you navigate to *'/unauthorized'*

![Unauthorized page](https://i.postimg.cc/bJY0Hf3M/unauthorized.png)

### Protecting the Route
Ok, now we're in the home stretch! Let's add in the logic to ensure we're logged in before we can view the Dashboard component. First, let's pass in our *user* state as a prop to our __ProtectedRoute__ in __App.js__. 

###### protected-routes/src/App.js
```jsx
<ProtectedRoute exact path='/dashboard' user={user} handleLogout={handleLogout} component={Dashboard} />
```

Jump back into __ProtectedRoute.js__ and let's add a conditional that checks if our *user* status is set to true, in which case we'll render the Component, otherwise, redirect to our */unauthorized* route. Therefore we'll need to import the `<Redirect>` component from __react-router__ as well. Here's what the final __ProtectedRoute__ component should look like:

```jsx
import React from 'react';
import { Route, Redirect } from 'react-router-dom';

const ProtectedRoute = ({ component: Component, user, ...rest }) => {
  return (
    <Route {...rest} render={
      props => {
        if (user) {
          return <Component {...rest} {...props} />
        } else {
          return <Redirect to={
            {
              pathname: '/unauthorized',
              state: {
                from: props.location
              }
            }
          } />
        }
      }
    } />
  )
}

export default ProtectedRoute;
```

Note, we're now specifying *user* as one of the props we're passing into our __ProtectedRoute__ component since we're referencing it a little later on in our *if* statement that checks if we've "logged in" or not. 

- If *user* evaluates to *true*, then we'll render our component as normal
- However, if it's set to *false*, we'll use the `<Redirect>` component from __react-router__ to redirect the user to *'/unauthorized'*. 

Alright, now the moment of truth. Let's try accessing the "View Dashboard" link without first "logging in". We should be greeted by Gandalf the Grey.

![Gandalf you shall not pass](https://media.giphy.com/media/njYrp176NQsHS/giphy.gif)

Now let's click the log in button to simulate authentication. Our status changes to *true* and when we click on the "View Dashboard" link, our app should now render the __Dashboard__ component. If we click "Log Out", we'll be immediately booted to the __Unauthorized__ page.

![Demo](https://i.postimg.cc/GtxXyXXQ/full-demo.gif)

### Summary
We got a glimpse of how we can protect private pages with __react-router__ and our `<ProtectedRoute>` higher-order component. We utilized the *useState* hook to give our function components access to state, which we passed down as props to child components. As mentioned at the onset, we'll take a look in a future post at how we can use the Context API to avoid having to prop drill.  

If you made it to the end, thanks for reading. I appreciate any comments or suggestions, so feel free to leave them below. As always, happy hacking!