How to manage global data with Context API, without Redux on React
Tags: react, webdev, javascript, redux
Published: 2020-10-04 22:34:05

Hi, [Taishi](https://taishikato.com/) here 👋

How do you manage global data on React?
I used to use Redux for that, however, I currently use Context API for that purpose and I don't even install redux and redux-related packages!

## 2 ways to implement it with Context API

I think there are 2 ways to make it happen.
A simple and complicated one.

I am gonna explain the simple one first ☝️

Imagine we want to manage the logged-in user data.

### 1. Use state variable

First of all, we need a Context component for sure.

I found this way when I was reading [next.js/userContext.js at master · vercel/next.js](https://github.com/vercel/next.js/blob/master/examples/with-firebase/context/userContext.js) 😋

#### Add userContext.js

Let's make `./src/context/userContext.js`.

```jsx
// File: ./src/context/userContext.js
import React, { useState, useEffect, createContext, useContext } from 'react';
import firebase from '../firebase/clientApp';

export const UserContext = createContext();

export default function UserContextComp({ children }) {
  const [user, setUser] = useState(null);
  const [loadingUser, setLoadingUser] = useState(true); // Helpful, to update the UI accordingly.

  useEffect(() => {
    // Listen authenticated user
    const unsubscriber = firebase.auth().onAuthStateChanged(async (user) => {
      try {
        if (user) {
          // User is signed in.
          const { uid, displayName, email, photoURL } = user;
          // You could also look for the user doc in your Firestore (if you have one):
          // const userDoc = await firebase.firestore().doc(`users/${uid}`).get()
          setUser({ uid, displayName, email, photoURL });
        } else setUser(null);
      } catch (error) {
        // Most probably a connection error. Handle appropriately.
      } finally {
        setLoadingUser(false);
      }
    });

    // Unsubscribe auth listener on unmount
    return () => unsubscriber();
  }, []);

  return (
    <UserContext.Provider value={{ user, setUser, loadingUser }}>
      {children}
    </UserContext.Provider>
  );
}

// Custom hook that shorhands the context!
export const useUser = () => useContext(UserContext);
```

As you can see, `UserContextComp` component has `user` state variable.

```javascript
const [user, setUser] = useState(null);
```

We store the user data in this `user` variable and update it with `setUser()` function.

#### Edit index.js

Now we have to use the `UserContextComp` component to consume it!
Edit `./src/index.js` like below.

```jsx
// File: ./src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import UserProvider from './context/userContext';

ReactDOM.render(
  <React.StrictMode>
    <UserProvider>
      <App />
    </UserProvider>
  </React.StrictMode>,
  document.getElementById('root'),
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
```

Now we can use `user` variable and update it with `setuser()` function everywhere ✌️
 
#### How to consume it

Import the `useUser` function from the `./src/context/userContext.js` and get the variable you need.
In this case, we take `loadingUser`, `user`, and `setUser`.

```jsx
import React from 'react';
import { useUser } from '../context/userContext';

const MyComponent = () => {
  const { loadingUser, user, setUser } = useUser();

  return (
    <>
      {loadingUser ? (
        <div>loading…</div>
      ) : (
        <div>Welcome, {user.displayName}</div>
      )}
    </>
  );
};

export default MyComponent;
```

Please just use `setUser` if you need to update the user data just like when you update the usual state variable.


### 2. Use dispatch and reducer (more Redux way)

In this way, we use useContext with [useReducer](https://reactjs.org/docs/hooks-reference.html#usereducer) hook.

I feel like this way is Redux without Redux 🤤
Sure, Redux uses Context API inside itself.

By the way. I made an example app here.
Please take a look at this if you wanna make it happen on your local environment.
{% github taishikato/context-api-with-useReducer %}


Anyway, let's dive into it!

#### Add `./src/context/reducer.js`

If you are familiar with Redux, you can understand this with ease.

Now we are gonna define the reducer function and initialState.
The default value of `user` is `null`.

```javascript
// File: ./src/context/reducer.js
export const initialState = {
  user: null,
};

export const actionTypes = {
  SET_USER: 'SET_USER',
};

const reducer = (state, action) => {
  switch (action.type) {
    case actionTypes.SET_USER:
      return {
        ...state,
        user: action.user,
      };
    default:
      return state;
  }
};

export default reducer;
```

#### Make `./src/context/StateProvider.js`

```javascript
// File: ./src/context/StateProvider.js`

import React, { createContext, useContext, useReducer } from 'react';

export const StateContext = createContext([]);

export const StateProvider = ({ reducer, initialState, children }) => (
  <StateContext.Provider value={useReducer(reducer, initialState)}>
    {children}
  </StateContext.Provider>
);

export const useStateValue = () => useContext(StateContext);
```

#### Set the Provider in `./src/index.js`

Because of this, we can consume the StateContext component everywhere!

```javascript
// File: ./src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
+ import { StateProvider } from './context/StateProvider';
+ import reducer, { initialState } from './context/reducer';

ReactDOM.render(
  <React.StrictMode>
+    <StateProvider initialState={initialState} reducer={reducer}>
      <App />
+    </StateProvider>
  </React.StrictMode>,
  document.getElementById('root'),
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
```

#### Now display the logged in user's name!

Make a [Auth component](https://github.com/taishikato/context-api-with-useReducer/blob/master/src/Auth.js) and use it in `App.js` like below.

We need login/logout methods (`handleLogin`, `handleLogout`) to handle onclick events, so make them as well.

```jsx
// File: ./src/App.js
import React from 'react';

import Auth from './Auth';
import { auth, provider } from './firebase';
import { useStateValue } from './context/StateProvider';
import { actionTypes } from './context/reducer';
import './App.css';

function App() {
  const [state, dispatch] = useStateValue();

  const handleLogin = async () => {
    try {
      const result = await auth.signInWithPopup(provider);
      dispatch({
        type: actionTypes.SET_USER,
        user: result.user,
      });
    } catch (err) {
      alert(err.message);
    }
  };

  const handleLogout = async () => {
    await auth.signOut();
    dispatch({
      type: actionTypes.SET_USER,
      user: null,
    });
  };

  return (
    <Auth>
      <div className="App">
        <header className="App-header">
          <div>{state.user?.displayName}</div>
          {state.user ? (
            <button onClick={handleLogout}>Logout</button>
          ) : (
            <button onClick={handleLogin}>Login</button>
          )}
        </header>
      </div>
    </Auth>
  );
}

export default App;
```

As [the reference](https://reactjs.org/docs/hooks-reference.html#usereducer) says, `useReducer` returns `state` and `dispatch`.

> An alternative to useState. Accepts a reducer of type (state, action) => newState, and returns the current state paired with a dispatch method. (If you’re familiar with Redux, you already know how this works.)

That's why we can get variables like this.

`useStateValue()` returns `useContext(StateContext)`, and this returns `useReducer(reducer, initialState)`.


```javascript
const [state, dispatch] = useStateValue();
```

Now you see like this and you can login/logout.

![Login](https://dev-to-uploads.s3.amazonaws.com/i/6goliwtna6vscni2ek20.png)

If you logged in successfully, you can see your name as below.

When the value of `state.user` is set, your name will be shown.

```javascript
<div>{state.user?.displayName}</div>
```

![Logout](https://dev-to-uploads.s3.amazonaws.com/i/vsh0jsvdtdl493aht96j.png)

## Note

I think `2. Use dispatch and reducer (more Redux way)` may look complicated to you, but we can easily understand what kind of data this app manages globally in [initialState](https://github.com/taishikato/context-api-with-useReducer/blob/master/src/context/reducer.js#L1-L3). In this example, we manage only `user` variable globally, but imagine if we manage like 10 variables 😅

Hope this helps.