Rewrite Auth0 Example with React Hooks
Tags: react, auth0
Published: 2019-06-23 12:02:39

---
title: Rewrite Auth0 Example with React Hooks
published: true
description: 
tags: React, Auth0
---


Auth0 [default React example](https://github.com/auth0-samples/auth0-react-samples/tree/master/01-Login) don't use react hooks.

I try to rewrite this example to use React Hooks.

## Full Example

You can read full example in this repository
https://github.com/terrierscript/example-auth0/tree/full-example

# Details

## 1. Create Context

First, I create `AuthContext` that hold auth object and some auth result state.

```jsx
// auth/AuthContext
import React, { createContext, useState, useContext } from 'react';

import { WebAuth } from 'auth0-js';
import { AUTH_CONFIG } from './auth0-variables';

const generateAuth = () =>
  new WebAuth({
    domain: AUTH_CONFIG.domain,
    clientID: AUTH_CONFIG.clientID,
    redirectUri: AUTH_CONFIG.callbackUrl,
    responseType: 'token id_token',
    scope: 'openid'
  });

const Auth0Context = createContext<ReturnType<typeof useContextValue>>(null);

const useAuthState = () => {
  return useState({
    accessToken: null,
    idToken: null,
    expiresAt: 0
  });
};

const useContextValue = () => {
  const [authState, updateAuthState] = useAuthState();
  return {
    auth0: generateAuth(),
    authState,
    updateAuthState
  };
};

export const Auth0Provider = ({ children }) => {
  const value = useContextValue();
  return (
    <Auth0Context.Provider value={value}>{children}</Auth0Context.Provider>
  );
};

export const useAuth0Context = () => {
  return useContext(Auth0Context);
};

```

## 2. Create Context

Next, generate `useAuth`.

Almost logics same as [`Auth.js`](https://github.com/auth0-samples/auth0-react-samples/blob/48c56b9bfbf84aedfc755bbee97ed20732bbfb3f/01-Login/src/Auth/Auth.js#L1-L101)

But `isAuthenticated` changed from `function` to `boolean` value with `useMemo`

```ts
// src/useAuth
import { useCallback, useMemo } from 'react';
import history from '../history'; // TODO: history may pass from props
import { useAuth0Context } from './AuthContext';

const useIsAuthenticated = expiresAt => {
  return useMemo(() => {
    return new Date().getTime() < expiresAt;
  }, [expiresAt]);
};

export const useAuth0 = () => {
  const { auth0, authState, updateAuthState } = useAuth0Context();

  const isAuthenticated = useIsAuthenticated(authState.expiresAt);

  const login = useCallback(() => {
    auth0.authorize();
  }, [auth0]);

  const logout = useCallback(() => {
    updateAuthState({
      accessToken: null,
      idToken: null,
      expiresAt: 0
    });
    localStorage.removeItem('isLoggedIn');

    auth0.logout({
      returnTo: window.location.origin
    });

    // navigate to the home route
    history.replace('/home');
  }, [auth0, updateAuthState]);

  const setSession = useCallback(
    authResult => {
      localStorage.setItem('isLoggedIn', 'true');

      let expiresAt = authResult.expiresIn * 1000 + new Date().getTime();
      updateAuthState({
        accessToken: authResult.accessToken,
        idToken: authResult.idToken,
        expiresAt: expiresAt
      });
      history.replace('/home');
    },
    [updateAuthState]
  );

  const renewSession = useCallback(() => {
    auth0.checkSession({}, (err, authResult) => {
      if (authResult && authResult.accessToken && authResult.idToken) {
        setSession(authResult);
      } else if (err) {
        logout();
        console.error(err);
        alert(
          `Could not get a new token (${err.error}: ${err.error_description}).`
        );
      }
    });
  }, []);

  const handleAuthentication = useCallback(() => {
    auth0.parseHash((err, authResult) => {
      if (authResult && authResult.accessToken && authResult.idToken) {
        setSession(authResult);
      } else if (err) {
        history.replace('/home');
        alert(`Error: ${err.error}. Check the console for further details.`);
      }
    });
  }, []);

  // retun some functions
  return {
    login,
    logout,
    handleAuthentication,
    isAuthenticated,
    renewSession
  };
};
```


## 3. fix `<Callback>`

In base example, `handleAuthentication` called in `router` like [this](https://github.com/auth0-samples/auth0-react-samples/blob/48c56b9bfbf84aedfc755bbee97ed20732bbfb3f/01-Login/src/routes.js#L23-L26).

```jsx
  <Route path="/callback" render={(props) => {
    handleAuthentication(props);
    return <Callback {...props} /> 
  }}/>
```

I feel it's so tricky.
But when use hooks, we call that with `useEffect`

```jsx
// Callback/Callback

import React, { useEffect } from 'react';
import loading from './loading.svg';
import { useAuth0 } from '../Auth/useAuth';

export const Callback = props => {
  const { handleAuthentication } = useAuth0();
  const { location } = props;
  useEffect(() => {
    if (/access_token|id_token|error/.test(location.hash)) {
      handleAuthentication();
    }
  }, [handleAuthentication, location]);

  const style = {
  //....
  };

  return (
    <div style={style}>
      <img src={loading} alt="loading" />
    </div>
  );
};

```

## 4. fix `<App>` and `<Home>`

`<App>` and `<Home>` rewrited too.

`<App>` call `renewSession` with `useEffect`

```jsx
// App

import React, { useCallback, useEffect, useMemo } from 'react';
import { Navbar, Button } from 'react-bootstrap';
import './App.css';
import { useAuth0 } from './Auth/useAuth';

const useGoToHandler = history => {
  return useCallback(route => () => history.replace(`/${route}`), [history]);
};

export const App = ({ history }) => {
  const { login, logout, isAuthenticated, renewSession } = useAuth0();

  const goToHandler = useGoToHandler(history);
  useEffect(() => {
    if (localStorage.getItem('isLoggedIn') === 'true') {
      renewSession();
    }
  }, [renewSession]);

  return (
    <div>
      <Navbar fluid>
        <Navbar.Header>
          <Navbar.Brand>
            <a href="#">Auth0 - React</a>
          </Navbar.Brand>
          <Button
            bsStyle="primary"
            className="btn-margin"
            onClick={goToHandler('home')}
          >
            Home
          </Button>
          {!isAuthenticated && (
            <Button
              id="qsLoginBtn"
              bsStyle="primary"
              className="btn-margin"
              onClick={login}
            >
              Log In
            </Button>
          )}
          {isAuthenticated && (
            <Button
              id="qsLogoutBtn"
              bsStyle="primary"
              className="btn-margin"
              onClick={logout}
            >
              Log Out
            </Button>
          )}
        </Navbar.Header>
      </Navbar>
    </div>
  );
};
```

```jsx
// Home/Home

import React from 'react';
import { useAuth0 } from '../Auth/useAuth';

export const Home = () => {
  const { login, isAuthenticated: isAuthenticated } = useAuth0();
  return (
    <div className="container">
      {isAuthenticated && <h4>You are logged in!</h4>}
      {!isAuthenticated && (
        <h4>
          You are not logged in! Please
          <a style={{ cursor: 'pointer' }} onClick={login}>
            Log In
          </a>
          to continue.
        </h4>
      )}
    </div>
  );
};
```

## 5. fix router

Rewrite router to this.

* Routers wrapped `<Auth0Provider>`.
* `Callback` logic moved that component.
* (trivial) Use `react-router` `<Switch>`.


```jsx
// roter
import React from 'react';
import { Route, Router, Switch } from 'react-router-dom';
import { App } from './App';
import { Home } from './Home/Home';
import { Callback } from './Callback/Callback';
import history from './history';
import { Auth0Provider } from './Auth/AuthContext';

const Routes = () => {
  return (
    <Router history={history}>
      <Route path="/" render={props => <App {...props} />} />
      <Switch>
        <Route path="/home" render={props => <Home {...props} />} />
        <Route path="/callback" render={props => <Callback {...props} />} />
      </Switch>
    </Router>
  );
};

export const makeMainRoutes = () => {
  return (
    <Auth0Provider>
      <Routes />
    </Auth0Provider>
  );
};
```

## 6. Setup Auth0 and `npm start`

That all !