Write your own authorization hook using React Stores – a shared state manipulating library for React and React Native
Tags: react, typescript, tutorial, usehooks
Published: 2019-11-11 22:20:18

Hello everyone! In this tutorial, I would like to show you how to learn three powerful techniques that I use to build great React and React Native applications with TypeScript.

1. Using react hooks.
2. Using my tiny but very powerful shared stores library [React Stores](https://github.com/ibitcy/react-stores#readme).
3. Making protected routes with React Router 5.x with those techniques.

So let's begin.

## Initializing project

Open your terminal and initialize a new React application (let's use [Create React App](https://github.com/facebook/create-react-app)). Don't forget `--typescript` flag for use TypeScript boilerplate during the creation of our application.

```bash
create-react-app my-app --typescript
cd my-app
```

Okay, we've just initialized our CRA, now its time to run. I prefer to use `yarn` but you can choose your favorite package manager.

```bash
yarn start
```

Then open your browser and go to http://localhost:3000.
Yay! Now we have our shiny new app is up and running!

Commit #1. [See on GitHub](https://github.com/ruslanchek/cra-hooks-tutorial-dev-to/commit/935d742fb84a6a0922ffd0f3e0a74ffdee07cf72).

## Installing dependencies

Let's install react-stores library and react-router-dom with its TypeScript definitions:

```bash
yarn add react-stores react-router-dom @types/react-router-dom
```

Now we're ready to create our first shared store. Let's do it. Create file `store.ts` inside `src` directory:

```typescript
// store.ts
import { Store } from "react-stores";

interface IStoreState {
  authorized: boolean;
}

export const store = new Store<IStoreState>({
  authorized: false
});
```

Here we created a few things:

1. Exported store instance that we can use everywhere in the app.
2. The interface for the store that strictly declares store contents.
3. And passed initial state values (actually only one value here `authorized`, but you can put as much as you need).

Commit #2. [See on GitHub](https://github.com/ruslanchek/cra-hooks-tutorial-dev-to/commit/dacddf560867196eafda5458a2a1616125a4b196).

## Routes and navigation

Nothing special here, just create simple routing with React Dom Router.

```jsx
// App.tsx
import React from "react";
import { BrowserRouter, Route, Switch, Link } from "react-router-dom";

const HomePage = () => (
  <div>
    <h1>Home</h1>
    <p>Welcome!</p>
  </div>
);

const PublicPage = () => (
  <div>
    <h1>Public page</h1>
    <p>Nothing special here</p>
  </div>
);

const PrivatePage = () => (
  <div>
    <h1>Private page</h1>
    <p>Wake up, Neo...</p>
  </div>
);

const App: React.FC = () => {
  return (
    <BrowserRouter>
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/public">Public</Link>
        </li>
        <li>
          <Link to="/private">Private</Link>
        </li>
      </ul>
      <Switch>
        <Route exact path="/" component={HomePage} />
        <Route exact path="/public" component={PublicPage} />
        <Route exact path="/private" component={PrivatePage} />
      </Switch>
    </BrowserRouter>
  );
};

export default App;
```

Now we have simple SPA with a few routes and navigation.

Commit #3. [See on GitHub](https://github.com/ruslanchek/cra-hooks-tutorial-dev-to/commit/96f3a3cd3790e014ed5bffaf2d368e9f378f6906).

## Adding some complexity

Here we add a header with navigation, new authorization route and fake Login/Exit button, plus some simple CSS-styles.

```jsx
// App.tsx
import React from "react";
import { BrowserRouter, Route, Switch, NavLink } from "react-router-dom";
import "./index.css";

const HomePage = () => (
  <div>
    <h1>Home</h1>
    <p>Welcome!</p>
  </div>
);

const PublicPage = () => (
  <div>
    <h1>Public page</h1>
    <p>Nothing special here</p>
  </div>
);

const PrivatePage = () => (
  <div>
    <h1>Private page</h1>
    <p>Wake up, Neo...</p>
  </div>
);

const AuthorizePage = () => (
  <div>
    <h1>Authorize</h1>
    <button>Press to login</button>
  </div>
);

const App: React.FC = () => {
  return (
    <BrowserRouter>
      <header>
        <ul>
          <li>
            <NavLink exact activeClassName="active" to="/">
              Home
            </NavLink>
          </li>
          <li>
            <NavLink exact activeClassName="active" to="/public">
              Public
            </NavLink>
          </li>
          <li>
            <NavLink exact activeClassName="active" to="/private">
              Private
            </NavLink>
          </li>
        </ul>

        <ul>
          <li>
            <NavLink exact activeClassName="active" to="/authorize">
              Authorize
            </NavLink>
          </li>
        </ul>
      </header>

      <main>
        <Switch>
          <Route exact path="/" component={HomePage} />
          <Route exact path="/public" component={PublicPage} />
          <Route exact path="/private" component={PrivatePage} />
          <Route exact path="/authorize" component={AuthorizePage} />
        </Switch>
      </main>
    </BrowserRouter>
  );
};

export default App;
```

```css
/* index.css */
body {
  margin: 0;
  font-family: Arial, Helvetica, sans-serif;
}

header {
  background-color: #eee;
  padding: 10px 30px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

main {
  padding: 30px;
}

ul {
  list-style: none;
  padding: 0;
  display: flex;
  align-items: center;
}

ul li {
  margin-right: 30px;
}

a {
  text-decoration: none;
  color: #888;
}

a.active {
  color: black;
}
```

Commit #4. [See on GitHub](https://github.com/ruslanchek/cra-hooks-tutorial-dev-to/commit/54b2cd6c4e6cd3d3d5ae1273734d80be86158468).

## Using React Stores in components

Now its time to add simple authorization logic and use our Store to see it in action.

### Separating components into files

First, let's move our navigation and pages components into separate files for code separation, it's a good practice 😊.

```jsx
// App.tsx
import React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import "./index.css";
import { Nav } from "./Nav";
import { HomePage } from "./HomePage";
import { PublicPage } from "./PublicPage";
import { PrivatePage } from "./PrivatePage";
import { AuthorizePage } from "./AuthorizePage";

const App: React.FC = () => {
  return (
    <BrowserRouter>
      <Nav />
      <main>
        <Switch>
          <Route exact path="/" component={HomePage} />
          <Route exact path="/public" component={PublicPage} />
          <Route exact path="/private" component={PrivatePage} />
          <Route exact path="/authorize" component={AuthorizePage} />
        </Switch>
      </main>
    </BrowserRouter>
  );
};

export default App;
```

```jsx
// Nav.tsx
import React from "react";
import { NavLink } from "react-router-dom";

export const Nav: React.FC = () => {
  return (
    <header>
      <ul>
        <li>
          <NavLink exact activeClassName="active" to="/">
            Home
          </NavLink>
        </li>
        <li>
          <NavLink exact activeClassName="active" to="/public">
            Public
          </NavLink>
        </li>
        <li>
          <NavLink exact activeClassName="active" to="/private">
            Private
          </NavLink>
        </li>
      </ul>
      <ul>
        <li>
          <NavLink exact activeClassName="active" to="/authorize">
            Authorize
          </NavLink>
        </li>
      </ul>
    </header>
  );
};
```

```jsx
// AuthorizePage.tsx
import React from "react";

export const AuthorizePage = () => (
  <div>
    <h1>Authorize</h1>
    <button>Press to login</button>
  </div>
);
```

```jsx
// HomePage.tsx
import React from "react";

export const HomePage = () => (
  <div>
    <h1>Home</h1>
    <p>Welcome!</p>
  </div>
);
```

```jsx
// PrivatePage.tsx
import React from "react";

export const PrivatePage = () => (
  <div>
    <h1>Private page</h1>
    <p>Wake up, Neo...</p>
  </div>
);
```

```jsx
// PublicPage.tsx
import React from "react";

export const PublicPage = () => (
  <div>
    <h1>Public page</h1>
    <p>Nothing special here</p>
  </div>
);
```

Commit #5. [See on GitHub](https://github.com/ruslanchek/cra-hooks-tutorial-dev-to/commit/0b69553d358b858f4b23f5dbebf0eac8af7320fc).

### Using store state

Now time to add shared states to our components. The first component will be `Nav.tsx`. We will use built-in React hook from `react-stores` package – `useStore()`.

```typescript
// Nav.tsx
...
import { store } from './store';
import { useStore } from 'react-stores';

export const Nav: React.FC = () => {
  const authStoreState = useStore(store);
  ...
}
```

Now our `Nav` component is bound to the Store through `useStore()` hook. The component will update each time store updates. As you can see this hook is very like usual `useState(...)` from the React package.

Next, let's use `authorized` property from the Store state. To render something depends on this property. For example we can render conditional text in Authorize navigation link in our navigation.

```jsx
// Nav.tsx
...
<li>
  <NavLink exact activeClassName='active' to='/authorize'>
    {authStoreState.authorized ? 'Authorized' : 'Login'}
  </NavLink>
</li>
...
```

Now, text inside this link depends on `authorized` property. You can try now to change the initial store state to see how "Login" changes to "Authorized" in our `Nav.tsx` when you set its value from `false` to `true` and vice versa.

```typescript
// store.ts
...
export const store = new Store<IStoreState>({
  authorized: true, // <-- true or false here
});
...
```

Next, we're going to change `AuthorizePage.tsx` to bind it to our Store and set another one conditional rendering by `useState()` hook.

```jsx
// AuthorizePage.tsx
import React from "react";
import { useStore } from "react-stores";
import { store } from "./store";

export const AuthorizePage: React.FC = () => {
  /* 
    You must pass exactly that store instance, that you need to use. 
    Because you can have multiple stores in your app of course.
  */
  const authStoreState = useStore(store);

  return authStoreState.authorized ? (
    <div>
      <h1>Authorized</h1>
      <button>Press to exit</button>
    </div>
  ) : (
    <div>
      <h1>Unauthorized</h1>
      <button>Press to login</button>
    </div>
  );
};
```

You can play with the initial state to see how page `/authorize` changes depending on the Store. 🤪

Commit #6. [See on GitHub](https://github.com/ruslanchek/cra-hooks-tutorial-dev-to/commit/d17303825a72d2d1b2627dd36aff6a51a63193d0).

### Mutating store

Now it's time to implement our authorization flow. It will be a simple function, but that's will be enough to show the concept.

And of course, you can write your own auth flow, for example, fetch some data from a server to get a token or some login-password authentication mechanism it does not important.

Our functions just toggles the `authorized` boolean value.

Create file `authActions.ts`:

```typescript
// authActions.ts
import { store } from "./store";

export function login() {
  store.setState({
    authorized: true
  });
}

export function logout() {
  store.setState({
    authorized: false
  });
}
```

As you can see, here we call the Store instance `setState()` method to mutate its state and update all the components bound to the Store.

Now we can bind auth button to `authActions`.

```jsx
// AuthorizePage.tsx
...
import { login, logout } from './authActions';

...
  return authStoreState.authorized ? (
    <div>
      <h1>Authorized</h1>
      <button onClick={logout}>Press to logout</button>
    </div>
  ) : (
    <div>
      <h1>Unauthorized</h1>
      <button onClick={login}>Press to login</button>
    </div>
  );
...
```

That's it... For now. You can try to navigate to `/authorize` and click the Login/Logout button to see it in action. The page and Navigation should update to show your current authorization state each time you toggle.

## Custom hook

Time to write your custom hook. Let's call it `useProtectedPath`. Its purpose is to check the current browser's location path, compare it to a given protected paths list and return a boolean value: `true` if path protected and the user is authorized, otherwise `false`, or if path is not in protected, return `true` whether user authorized or not.

So, create a file `useProtectedPath.ts`.

```typescript
import { useStore } from "react-stores";
import { store } from "./store";
import { useRouteMatch } from "react-router";

const PROTECTED_PATHS = ["/private"];

export const useProtectedPath = () => {
  const { authorized } = useStore(store);
  const match = useRouteMatch();
  const protectedPath =
    PROTECTED_PATHS.indexOf((match && match.path) || "") >= 0;
  const accessGrant = !protectedPath || (protectedPath && authorized);

  return accessGrant;
};
```

After that you can use it in `PrivatePage.tsx` like that:

```jsx
import React from "react";
import { useProtectedPath } from "./useProtectedPath";
import { Redirect } from "react-router";

export const PrivatePage = () => {
  const accessGrant = useProtectedPath();

  if (!accessGrant) {
    return <Redirect to="/authorize" />;
  }

  return (
    <div>
      <h1>Private page</h1>
      <p>Wake up, Neo...</p>
    </div>
  );
};
```

Now your `/private` page will redirect you to `/authorize` to let you authorize.

That's it we've made it!
🥳🥳🥳

Commit #7. [See on GitHub](https://github.com/ruslanchek/cra-hooks-tutorial-dev-to/commit/72424f3f5100c9190ce126eb47ad95325887799d).

## Bonus

Try this snippet in your `store.ts`. Then authorize and reload the page in the browser. As you can see, your authorized state will be restored. That means your Store now has a persistent state from session to session.

```typescript
// store.ts
export const store = new Store<IStoreState>(
  {
    authorized: false
  },
  {
    persistence: true // This property does the magic
  }
);
```

React Stores support persistence. That's mean that you can store your store state in Local Storage by default, or even make your own driver, for example, IndexedDB or Cookies, or even a network fetcher as a Driver. See readme and sources on https://github.com/ibitcy/react-stores#readme.

...And never use LocalStorage to store your token or other sensitive data in the Local Storage. This article uses a Local Storage driver for persist authorization state only for the explanation of the concept. 😶

One more thing... You can make Time Machine functionality via creating snapshots of your states. You can see how it works here: https://ibitcy.github.io/react-stores/#Snapshots.

Thank you for reading. I hope it will help someone to create something great and with ease.

🤓🦄❤️

[Online demo](https://react-stores-auth.netlify.com)