How To Build An App With React Context API
Tags: javascript, webdev, react, tutorial
Published: 2022-05-30 10:26:21

Overtime, props have proven to be very useful in passing data across components. But as the application grows, it is almost always certain that most components deep in the tree will require data from parent/top in the tree components. As such, using props will make the whole app cumbersome. For example,

```jsx
<App>
  <Header postsLength={posts.length} /> {/* assuming the header requires the length of the post */}
    <PostsList posts={posts}>
   <PostItem post={post}>
```

If the `PostItem` should require any new data from the parent component, you'd have to pass the data as props to every single component in-between. This is the reason for state management in React, and React provides a built-in state management solution - [React context](https://reactjs.org/docs/context.html) to handle passing data through descendant components without having to pass props through every component level.

So this is what we will discuss in this article by creating this [Blog](https://devblog-react.netlify.app/). I recommend you code along, so at the end of this article, you'd have created a [simple blog with React Context API](https://github.com/Elijah-trillionz/devblog-react-context-tutorial/).

## Getting Started

Let's set up all we would need to get the app started, and that would be creating a new React app. So head up to your terminal and run the following command

```shell
npx create-react-app devblog
```

When it is done, you can run `npm start` to preview the app. We don't need extra dependencies to use React context, so let's get started.

The blog is going to be a very simple one, with only a page to read, add, and delete posts.

## Creating components

First off, I want us to create the components we need for this app, so head up to the _src_ directory and create a new directory called _components_.

### 1. The header

We will create a simple component for the header of our blog. The header will contain a title and button to create new posts. This button is going to trigger another component which we will attach to the header. So go ahead and create a _Header.jsx_ file and paste the code below

```js
import { useState } from 'react';
import AddPost from './AddPost';

const Header = () => {
  const [openModal, setOpenModal] = useState(false);

  const closeModal = () => {
    setOpenModal(false);
  };

  return (
    <header>
      <h1>DevBlog</h1>
      <button onClick={() => setOpenModal(!openModal)}>Create Post</button>
      {openModal && <AddPost closeModal={closeModal} />}
    </header>
  );
};

export default Header;
```

Now we will create the `AddPost` component

### 2. The form component

The form will contain two input fields i.e the title and the body, and a submit button. On submission, we would validate the input to make sure there are values for the title and body. So create an _AddPost.jsx_ file in the _components_ dir.

```js
import { useState } from 'react';

const AddPost = ({ closeModal }) => {
  const [title, setTitle] = useState('');
  const [body, setBody] = useState('');
  const [error, setError] = useState(false);

  const validateInputs = (e) => {
    e.preventDefault();

    if (!title || !body) return setError('All fields are required');

    console.log({ title, body });
    closeModal();
  };

  return (
    <>
      <form onSubmit={validateInputs}>
        <input
          type='text'
          placeholder='Enter title'
          onChange={(e) => setTitle(e.target.value)}
        />
        <br />
        <br />
        <textarea
          placeholder='Enter body'
          onChange={(e) => setBody(e.target.value)}
        ></textarea>
        <br />
        <br />
        <button type='submit'>Submit</button>
        <br />
        {error && <p>{error}</p>}
      </form>
    </>
  );
};

export default AddPost;
```

For now, we are merely logging the values of title and body in the console, but we would do more with it soon.

### 3. The posts list component

This component is where we loop through the available posts. Each post will trigger a component (the same component) that will read the title and body of the post. So create a _PostList.jsx_ file and past the following

```js
import PostItem from './PostItem';

const PostList = () => {
  const posts = [{ id: 1, title: 'a title', body: 'a body' }];

  return (
    <ul>
      {posts.map((post) => (
        <PostItem key={post.id} post={post} />
      ))}
    </ul>
  );
};

export default PostList;
```

The `posts` array is just a template to preview the UI, we will change it in a bit. But for now, we will create the `PostItem` component

### 4. The post item component

The post item component will use the `post` props passed into it to read the title and body of the post. We would also have a delete and an edit button side by side. As we mentioned before only the delete button will be used in this article, but I believe by the time we are done you will know all you need to work on the edit function and make it possible to edit posts.

So go ahead and create a _PostItem.jsx_ file and paste the code below

```js
const PostItem = ({ post: { title, id, body } }) => {
  return (
    <li>
      <h2>{title}</h2>
      <p>{body}</p>
      <div>
        <i className='fas fa-edit'></i>
        <i className='fas fa-trash'></i>
      </div>
    </li>
  );
};

export default PostItem;
```

Right now nothing is being done to the delete button. And by the way, the delete and edit button is represented by a [FontAwesome](https://fontawesome.com/) icon, to make fontawesome work in your React app, add the `link` tag below to your _index.html_ file in the _public_ directory

```html
<link
  rel="stylesheet"
  href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.1/css/all.min.css"
  integrity="sha512-gMjQeDaELJ0ryCI+FtItusU9MkAifCZcGq789FrzkiM49D8lbDhoaUaIX4ASU187wofMNlgBJ4ckbrXM9sE6Pg=="
  crossorigin="anonymous"
  referrerpolicy="no-referrer"
/>
```

Since we are already here (_index.html_), let's quickly update the title of our app to "DevBlog". In the `head` element there is a `title` element, change the value from "React App" to "DevBlog". Additionally, you can remove those comments to make it clean, and change the `meta` description value to "Blog for devs" or something else.

All these components we've created will have no effect until we include them in our _App.js_ file. So paste the following to replace the current code in _App.js_

```js
import './App.css';
import Header from './components/Header';
import PostList from './components/PostList';

function App() {
  return (
    <div>
      <Header />
      <main>
        <h3>
          New Posts: <span>1 posts</span> {/*hard-coded, will change later*/}
        </h3>
        <PostList />
      </main>
    </div>
  );
}

export default App;
```

You might be wondering why I had to create a `PostList` component rather than just looping through the posts in _App.js_. Well, I can't give a straight answer now but what I can tell you is that the `PostList` component is going to be useful in learning context api. And we will also have a footer that will not be a component and that would also be useful in learning a few things about context api. So just hold on to that for now, I promise to explain better in a bit.

Before you save and test run, let's update the CSS

## Creating styles

This blog is going to have a dark and a light theme, so we would create an additional CSS file but for now, just copy the following styles into _App.css_

```css
header {
  display: flex;
  justify-content: space-around;
  align-items: center;
  background-color: khaki;
  margin-bottom: 40px;
}

button {
  padding: 15px;
  min-width: 150px;
  border: 2px solid rosybrown;
  border-radius: 4px;
  cursor: pointer;
}

main {
  padding: 0 30px;
  max-width: 800px;
  margin: 0 auto;
}

main h3 span {
  font-weight: 400;
  font-style: italic;
  color: #777;
}

main ul {
  list-style: none;
  margin: 40px 0;
  padding: 0;
}

main li {
  border: 2px solid #ccc;
  padding: 15px;
  border-radius: 6px;
  margin-bottom: 30px;
  transition: border-color 0.2s ease-in-out;
}

main li:hover {
  border-color: #444;
}

main li h2 {
  margin-top: 0;
}

main li div {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-right: 20px;
}

main li i {
  cursor: pointer;
}

.fa-trash {
  color: tomato;
}

form {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background-color: #fff;
  border: 2px solid #ccc;
  padding: 30px;
  border-radius: 6px;
}

form input,
form textarea {
  width: 300px;
  padding: 14px 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

footer {
  position: fixed;
  bottom: 20px;
  right: 20px;
  background-color: #fff;
  border: 2px solid #444;
  border-radius: 50%;
  height: 35px;
  width: 35px;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
}
```

Now create an _App.dark.css_ file and add these styles to it

```css
header.dark {
  background-color: #444;
  color: khaki;
}

header.dark form {
  background-color: #444;
}

main.dark li {
  border-color: #444;
  background-color: #444;
  color: khaki;
}

main.dark li:hover {
  border-color: #ccc;
}

footer.dark {
  background-color: #444;
  color: #fff;
}
```

Go ahead and import the _App.dark.css_ into _App.js_ and save. The dark theme is not going to work just yet because we haven't configured it yet, but we will do that in a bit.

## Creating contexts

In the _src_ directory, create a _context_ directory and in it an _AppState.js_ file. We will create our app's context here.
The code examples demonstrated in this section are to explain React context, the actual examples that involve our app will come later on.

### How to Create Contexts

**React.createContext** is a React context API for creating contexts, it is a function that takes a default value as its only argument. This value could represent the initial states of the context as you can access this value wherever you use the context's provider (details coming shortly).

```js
const posts = [];
const AppContext = React.createContext({ posts });
```

The `createContext` function returns a context object which provides a `Provider` and `Consumer` component. The `Provider` component is used to wrap components that will use our created context. Any component that needs access to the values passed as default value (initial state) to the `createContext`, will have to be wrapped with the returned `Provider` component like this

```js
const App = () => {
  return (
    <AppContext.Provider>
      <Header />
      <Main />
    </AppContext.Provider>
  );
};
```

The `Header` and `Main` components can all access the `posts` array we set initially while creating the `AppContext` context. The provider component provides a `value` prop which overrides the default value in `AppContext`. When we assign this prop a new value, the `Header` and `Main` components will no longer have access to the `posts` array. But there is a simple approach in which the values provided in the provider component can coexist with the default value of `AppContext`, and that is with [React states](https://reactjs.org/docs/hooks-state.html)(discussed below).

### How to Access Context Values

Components that access and use the values of `AppContext` are called consumers. React provides two methods of accessing context values

#### 1. The Consumer Component

I already mentioned this above as it is a component from the `AppContext` object just like `AppContext.Provider`:

```js
const Header = () => {
  return (
    <AppContext.Consumer>
      {(value) => {
        <h2>Posts Length: {value.posts.length} </h2>;
      }}
    </AppContext.Consumer>
  );
};
```

__A request:__ We have put together everything from where we created the context to now. Kindly do not mind that I am not repeating each step i.e creating the context, providing the context, and consuming the context into one code, but I trust you get it like this.

So inside the consumer component, we created a function that has a value parameter (provided by React), this value is the current `AppContext` value (the initial state).

Recall we said the current context value could also be provided by the provider component, and as such it will override the default value, so let's see that in action

```js
const posts = [];
const AppContext = React.createContext({ posts });

const App = () => {
  const updatedPosts = [
    { id: 1, title: 'a title', body: 'a body' },
    { id: 2, title: 'a title 2', body: 'a body 2' },
  ]

  return (
    <AppContext.Provider value={{posts: updatedPosts}}>
      <AppContext.Consumer>
        {({posts}) => {
          <p>{posts.length}</p> {/* 2 */}
        }}
      </AppContext.Consumer>
    </AppContext.Provider>
  )
}
```

Well, congratulations, you now know how to create and access a context, but there is more you need to know. First, let's take a look at the other method (the most used) to access a context value.

#### 2. The useContext hook

It is a React hook that simply accepts a context object and returns the current context value as provided by the context default value or the nearest context provider.

```js
// from
const Header = () => {
  return (
    <AppContext.Consumer>
      {(value) => {
        <h2>Posts Length: {value.posts.length} </h2>;
      }}
    </AppContext.Consumer>
  );
};

// to
const Header = () => {
  const value = useContext(AppContext);
  return <h2>Posts Length: {value.posts.length} </h2>;
};
```

__Note:__ Any component that uses the provider component cannot use the useContext hook and expect the value provided by the provider component, i.e

```js
const posts = [];
const AppContext = React.createContext({
  posts,
});

const App = () => {
  const updatedPosts = [
    { id: 1, title: 'a title', body: 'a body' },
    { id: 2, title: 'a title 2', body: 'a body 2' },
  ];
  const { posts } = useContext(AppContext);

  return (
    <AppContext.Provider value={{ posts: updatedPosts }}>
      <p>{posts.length}</p> {/* 0 */}
    </AppContext.Provider>
  );
};
```

This is obviously because the `useContext` hook does not have access to the values provided by the provider component. If you need access to it, then it has to be at least one level below the provider component.

But that's not to worry because you would hardly have the need to access it immediately as your app will always be componentized (full of components I mean).

You might wonder, why not use the default `posts` as the updated `posts` and still get access to it i.e

```js
const posts = [
  { id: 1, title: 'a title', body: 'a body' },
  { id: 2, title: 'a title 2', body: 'a body 2' },
]; // the used to be updatedPosts
```

Well, this is just hard-coded and it will not always be like this in real application because your values will often change and the most suitable place to handle these changes is inside a component. So in this example, we have just assumed that the updated posts were fetched from a database and have been updated, a more realistic example would be.

```js
const posts = []
const AppContext = React.createContext({ posts });

const AppProvider = ({ children }) => {
  const [updatedPosts, setUpdatedPosts] = React.useState(posts);

  const getPosts = async () => {
    const res = await fetch('some_api.com/posts');
      const jsonRes = await res.json()
      setUpdatedPosts(jsonRes.posts);
  }

  useEffect(() => {
    getPosts() // not a good practice, only trying to make it short. see why below
  }, [])

  return (
    <AppContext.Provider value={{posts: updatedPosts}}>
      {children}
    </AppContext.Provider>
  )
}

const App = () => {
  return (
    <AppProvider>
      <Header />
    </AppProvider>
  )
}

const Header = () => {
  const { posts } = useContext(AppContext)
  return (
    <p>{posts.length}</p> {/* Whatever is returned from the api */}
  )
}
```

**Note:** The "not a good practice" warning above is very important, you would naturally use the await keyword as it is an async function, and doing so in a `useEffect` function would require [proper subscribing and unsibscribing](https://dev.to/elijahtrillionz/cleaning-up-async-functions-in-reacts-useeffect-hook-unsubscribing-3dkk).

The returned provider component from our context app is just like any other component in React, and that is the reason we can use React state in `AppProvider` in the example above. Let's see how to use states more effectively in React context

## Using Reducers

React provides a [useReducer hook](https://reactjs.org/docs/hooks-reference.html#usereducer) that helps you keep track of multiple states, it is similar to the useState hook and it allows for custom state logic.

Because we will need to perform a series of updates in our context using states (like we did in the example above), it is often convenient to use the useReducer hook to handle all states in it.

The `useReducer` function takes in two required arguments, the first being the reducer function and the second being the initial states for the hook. So every state you require in your context will (should) be included in the initial states. The function then returns an array containing the current state, and a function to handle the states (just like useState).

```js
const reducer = (state, action) => {
  if (action.type === 'TOGGLE_THEME') {
    return { ...state, isDarkTheme: !state.isDarkTheme };
  }

  return state;
};

const App = () => {
  const [state, dispatch] = useReducer(reducer, {
    isDarkTheme: false,
  });

  const toggleTheme = () => {
    dispatch({
      type: 'TOGGLE_THEME',
    });
  };

  return (
    <div>
      <h2>Current theme: {state.isDarkTheme ? 'dark' : 'light'}</h2>
      <button onClick={toggleTheme}>Toggle theme</button>
    </div>
  );
};
```

In the `reducer` function, the action parameter contains the object we passed to the dispatch function. The state parameter contains the current state of the initial state(s) passed into the `useReducer` function. Don't worry if it's not so clear at first, you can always go to the docs and learn more for yourself.

The `reducer` function should always return a state to act as the current states of the useReducer hook, it could simply be the unchanged state or updated state, but something has to be returned.

If we use this in our `AppContext` to handle fetching posts we would have

```js
const reducer = (state, action) => {
  if (action.type === 'GET_POSTS') {
    return { ...state, posts: action.payload };
  }

  return state;
};

const initialStates = { posts: [] }
const AppContext = React.createContext(initialStates);

const AppProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialStates)

  const getPosts = async () => {
    const res = await fetch('some_api.com/posts');
    const jsonRes = await res.json()
    dispatch({
      type: 'GET_POSTS',
      payload: jsonRes.posts
    });
  }

  useEffect(() => {
    getPosts() // not a good practice, only trying to make it short. see why below
  }, [])

  return (
    <AppContext.Provider value={{posts: state.posts}}>
      {children}
    </AppContext.Provider>
  )
}

const App = () => {
  return (
    <AppProvider>
      <Header />
    </AppProvider>
  )
}

const Header = () => {
  const { posts } = useContext(AppContext)
  return (
    <p>{posts.length}</p> {/* Whatever is returned from the api */}
  )
}
```

Using context may not be clear yet, but it gets clearer as you develop with and in it. Now with that said let's see React context in action in our blog.

So paste the code below into _AppState.js_

```js
import { createContext, useReducer } from 'react';

const appReducer = (state, action) => {
  switch (action.type) {
    case 'DELETE_POST': {
      return {
        ...state,
        posts: state.posts.filter((post) => post.id !== action.payload),
      };
    }
    case 'ADD_POST': {
      return {
        ...state,
        posts: [action.payload, ...state.posts],
      };
    }
    case 'SET_DARK_THEME': {
      return {
        ...state,
        darkTheme: action.payload,
      };
    }
    default: {
      return state;
    }
  }
};

const initialState = {
  posts: [
    {
      id: 1,
      title: 'Post One',
      body: 'This is post one, do to it as you please',
    },
    {
      id: 2,
      title: 'Post Two',
      body: 'This is post two, do to it as you please',
    },
    {
      id: 3,
      title: 'Post Three',
      body: 'This is post three, do to it as you please',
    },
    {
      id: 4,
      title: 'Post Four',
      body: 'This is post four, do to it as you please',
    },
  ],
  darkTheme: false,
};

export const AppContext = createContext(initialState);

export const AppProvider = ({ children }) => {
  const [state, dispatch] = useReducer(appReducer, initialState);

  const deletePost = (id) => {
    dispatch({
      type: 'DELETE_POST',
      payload: id,
    });
  };

  const addPost = (post) => {
    dispatch({
      type: 'ADD_POST',
      payload: post,
    });
  };

  const setDarkTheme = (bool) => {
    dispatch({
      type: 'SET_DARK_THEME',
      payload: bool,
    });
  };

  return (
    <AppContext.Provider
      value={{
        posts: state.posts,
        darkTheme: state.darkTheme,
        deletePost,
        addPost,
        setDarkTheme,
      }}
    >
      {children}
    </AppContext.Provider>
  );
};
```

The reducer function can always be moved to a different file, I for one always move it to a separate file it makes it cleaner for me.

The functions we create in this component will be useful outside the component so we can pass it as a value in the `AppContext.Provider` component and any child of the `AppProvider` component can access and use it. But this component does not have its effect yet till we wrap it around the _App.js_ component, so let's make the updates

```js
import './App.css';
import './App.dark.css';
import Header from './components/Header';
import PostList from './components/PostList';
import { AppContext, AppProvider } from './contexts/AppState';

function App() {
  return (
    <AppProvider>
      <Header />
        <AppContext.Consumer>
        {({ posts, darkTheme, setDarkTheme }) => (
          <>
            <main className={`${darkTheme ? 'dark' : ''}`}>
              <h3>
                New Posts: <span>{posts.length} posts</span>
              </h3>
              <PostList />
            </main>

            <footer
              onClick={() => setDarkTheme(!darkTheme)}
              className={`${darkTheme ? 'dark' : ''}`}
            >
              <i className={`fas fa-${darkTheme ? 'sun' : 'moon'}`}></i>
            </footer>
          </>
        )}
        </AppContext.Consumer>
    </AppProvider>
  );
}

export default App;
```

While we can do this (i.e using the consumer component), we can also create components for both `main` and `footer`. But this is to illustrate to you that there will always be this option - the option of using the consumer component. There will be times when it would be the only option.

Finally, let's update the `Header` component, all we need is the `darkTheme` state and add it as a class name i.e

```js
import { useContext, useState } from 'react';
import { AppContext } from '../contexts/AppState';
import AddPost from './AddPost';

const Header = () => {
  const { darkTheme } = useContext(AppContext);
  const [openModal, setOpenModal] = useState(false);

  const closeModal = () => {
    setOpenModal(false);
  };

  return (
    <header className={`${darkTheme ? 'dark' : ''}`}>
      <h1>DevBlog</h1>
      <button onClick={() => setOpenModal(!openModal)}>Create Post</button>
      {openModal && <AddPost closeModal={closeModal} />}
    </header>
  );
};

export default Header;
```

## Reading Posts

The posts we have created in `AppContext` has no effect yet because we are making use of the hard-coded posts array in _components/PostList.jsx._ So let's head there and make some changes.

Here, we only need to get the new posts array from `AppContext` using the useContext hook. So replace

```js
const posts = [{ id: 1, title: 'a title', body: 'a body' }];
```
with
```js
const { posts } = useContext(AppContext);
```

You should ensure `useContext`, and `AppContext` are imported. Now save and test it out.

## Adding Posts

Head up to _components/AddPost.jsx_ and instead of logging into the console, pass in the post object into the `addPost` function from our app context

```js
import { useContext, useState } from 'react';
import { AppContext } from '../contexts/AppState';

const AddPost = ({ closeModal }) => {
  const { addPost } = useContext(AppContext);
  const [title, setTitle] = useState('');
  const [body, setBody] = useState('');
  const [error, setError] = useState(false);

  const validateInputs = (e) => {
    e.preventDefault();

    if (!title || !body) return setError('All fields are required');

    addPost({ title, body });
    closeModal();
  };

  // .....
};

export default AddPost;
```

Save your app, and try adding a new post, you'd dis.

## Deleting Posts

Just like we've done above, we will simply access the `deletePost` function and pass it to the delete button. Recall the delete button is in _components/PostItem.jsx_, so add an onClick button like

```js
//..
<i className='fas fa-trash' onClick={() => deletePost(id)}></i>
//..
```

Recall the deletePost function can be accessed as

```js
const { deletePost } = useContext(AppContext);
```

With this, I believe you will be able to make the edit button functional, but if you want to go a little further you can add authors as part of the features. And for that, I'd recommend you create another context (`UserContext`) to handle the authors' section. This context could contain functions like creating an author, logging an author in, updating the author's profile, listing all authors, and many more.

## Conclusion

React context has its limits, for a simple blog like this it is the most suitable, but you may struggle to handle a web app that handles tons of changes with React context. But the community has great alternatives if you want to build the next Twitter, Netlify, or something. Some of which are Redux, Recoil, Remix, etc they will be discussed as part of a state management series that I have just started with this article. Remix is not a state management library, but it is a great alternative to it.

So far, we've created a blog that reads, deletes, and adds new posts with React context. This is a big step into React state management so go ahead and create cool apps with it.

You can tag me on [Twitter @elijahtrillionz](https://twitter.com/elijahtrillionz) with a link to the blog you created as a result of this tutorial.

Kindly leave a comment below to let me know what you think of the series, please like and share for others to gain, and if you like what I do, you can show your support by [buying me a coffee](https://buymeacoffee.com/elijahtrillionz).

[![Buy me a coffee](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m3bttdygfyhm1phhqouv.png)](https://buymeacoffee.com/elijahtrillionz)