Effective State Management in React using Context API and Hooks!
Hi fellows!
In this blog, I will show you how you can build a simple yet functional state management solution for your React applications. We will use the new Context API
along with some useful hooks
to build this.
When I first tried out the context API, I was confused as to what is the best way to implement it. Furthermore, I didn't fully understand terms like providers
, consumers
, reducers
etc. that were thrown at me in every online article I read. Thus, I was hesitant in using this simple solution in the applications I built.
It was only recently, that I really understood the concept of Context API and how it can be combined with hooks to build out a pretty powerful state management solution in React. I give all the credit to Brad Traversy for showing how to do this. Hopefully, in this blog post, I will be able to explain it in a very easy and simple way.
Let's get started then!
About the Project
For this blog, we will be building a very simple library application. The idea is not to learn how to build react apps, but how we can use Context API in react. So feel free to copy the code for these components and paste it into your own project. Here's how the final project looks like:
The application consists of 3 components Library
, BookShelf
, and Book
. We want to pass the data to the Book
component without passing down props. So, we will use Context API for this.
The entire code for this project can be found on my GitHub: Context-API-Demo
So feel free to reference it if you get stuck.
1. Create A React Application
The first step is to create a basic react application. We will use create-react-app
to do this. Just use the following command to create your react project:
npx create-react-app <your_app_name>
Since I will use Bootstrap for styling so you can also add the cdn
inside the index.html
file as follows:
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" crossorigin="anonymous" />
Great! Now you can open the project in your favorite code editor and move to the second step.
2. Create the Components
Now in the src
directory create a folder called components
. We will place our component files here. Inside this folder, create the following 3 files:
Library.js
import React from "react";import BookShelf from "./bookShelf";const Library = () => { return ( <div className="pt-3"> <p> I am inside the library component. <span role="img" aria-labelledby="emoji"> </span> </p> <BookShelf /> </div> );};export default Library;
BookShelf.js
import React from "react";import Book from "./book";const BookShelf = () => { return ( <React.Fragment> <p> I am inside the bookshelf component <span role="img" aria-labelledby="emoji"> </span> </p> <Book /> </React.Fragment> );};export default BookShelf;
Book.js
import React, { useContext } from "react";import LibraryContext from "../context/libraryContext";const Book = () => { return ( <React.Fragment> <p> Hi, I am a book inside the bookshelf. My information is coming from the context! <span role="img" aria-labelledby="emoji"> </span> </p> </React.Fragment> );};export default Book;
Here we are not doing anything fancy. Just making 3 very basic components. The BookShelf
component has the Book
component inside it, and the Library
has the BookShelf
component inside it.
Finally, add the following code to App.js
:
import React from "react";import Library from "./components/library";const App = () => { return ( <React.Fragment> <div className="container-fluid pt-4 pl-4"> <h2> React Context API Demo <span role="img" aria-labelledby="emoji"> </span> </h2> <Library /> </div> </React.Fragment> );}export default App;
Here we render the Library component inside the App component. Our component tree looks like this:
3. Implementing Context API
Now comes the fun part: implementing the Context API. First, create a new folder in src
called context
. I like to keep all the context files in this folder. Inside the folder, we will create 3 files.
These files are: LibraryContext.js
, LibraryState.js
, and LibraryReducer.js
. You can jumble up all these into a single file, but that would become confusing very fast. So, I like to keep all these separate since they handle different functionalities.
First, let's go over how our state management will work. Then we will populate these files.
Library Context
The LibraryContext
will create the context and export it. That's it. Nothing more. Here's the code for it:
import { createContext } from "react";// create a contextconst LibraryContext = createContext();export default LibraryContext;
We use the built-in function createContext()
to create a context and then we export it as a default export.
Library State
Inside the LibraryState
component we will first create our initial state. Then we will use the useReducer
hook to add reducers to our initial state. This will give us the dispatch method which can be used to dispatch actions to the reducers.
Let me explain it in easy words: Every time we want to update the state we use dispatch
to fire off an action
to the reducer
. The reducer then updates our state and returns the new state. This pattern is similar to the one used by Redux.
Finally, we return a Provider
component which will enable us to access the state anywhere in our application. All we have to do is wrap the component with our LibraryState
component and we will get access to the state inside of that component.
Let's look at the code for this:
import React, { useReducer } from "react";import { LibraryReducer } from "./libraryReducer";import LibraryContext from "./libraryContext";// create a provider componentconst LibraryState = (props) => { // create initial state const initialState = { name: "Harry Potter and the Goblet of fire ", quantity: 7, shelf: "3A", }; // useReducer() hook const [state, dispatch] = useReducer(LibraryReducer, initialState); // actions to manipulate state const incrementQuantity = () => { dispatch({ type: "INCREMENT_QUANTITY", }); }; const resetQuantity = () => { dispatch({ type: "RESET_QUANTITY", }); }; return ( <LibraryContext.Provider value={{ state: state, incrementQuantity: incrementQuantity, resetQuantity: resetQuantity, }} > {props.children} </LibraryContext.Provider> );};export default LibraryState;
As you can see we are passing the value inside the Provider which can be accessed by all the components inside of it. In our case, this value includes the state
coming from our useReducer
and the 2 methods to manipulate that state.
Library Reducer
Inside the LibraryReducer
we will create a function that will update the state according to the actions. For every action, it will update the state depending upon the action type. Take a look at the code:
export const LibraryReducer = (state, action) => { switch (action.type) { case "INCREMENT_QUANTITY": return { ...state, quantity: state.quantity + 1 }; case "RESET_QUANTITY": return { ...state, quantity: 0 }; default: return state; }};
Here we are incrementing the count or resetting it depending upon the type of action. As you may recall, we used dispatch
to fire off these actions inside the LibraryState
component.
4. Accessing State inside Book Component
Now that our context is ready, all we have to do is wrap the Library
component inside the LibraryState
component which acts as our Provider
component. And we will have access to the state inside the Library
component and its children.
Head on over to App.js
and make the following changes:
Import LibraryState:
import LibraryState from "./context/libraryState";
Wrap Library
component:
<LibraryState> <Library /></LibraryState>
That's it! Now we can access the values from the Provider component anywhere inside the Library component and all its children.
For the last step, we will change the Book component to access the state values and add a couple of buttons to increment or reset the count as well.
Copy and paste the following into the Book component:
import React, { useContext } from "react";import LibraryContext from "../context/libraryContext";const Book = () => { // useContext() hook const context = useContext(LibraryContext); return ( <React.Fragment> <p> Hi, I am a book inside the bookshelf. My information is coming from the context! <span role="img" aria-labelledby="emoji"> </span> </p> {/* Book Information */} <p>Book Name: {context.state.name} </p> <p>Quantity: {context.state.quantity}</p> <p>Book Shelf: {context.state.shelf}</p> <div> <button className="btn btn-primary" onClick={context.incrementQuantity}> Increment <span role="img" aria-labelledby="emoji"> </span> </button> <button className="ml-2 btn btn-dark" onClick={context.resetQuantity}> Reset <span role="img" aria-labelledby="emoji"> </span> </button> </div> </React.Fragment> );};export default Book;
Here we use the useContext
hook to pull out the context. Then we access the values inside the context and display them in the component. Since we passed state
inside the value of our provider so we access the values as context.state.value
.
Finally, we add 2 buttons to increment and reset the count and we add the passed methods as onClick
to the buttons.
That's it! The application is now complete. You can see the state values being displayed in the Book
component without having to pass down any props
whatsoever. And the buttons can be used to manipulate the state as well.
Summary
Here's a quick summary of implementing context:
- Create the context using the
createContext
method. - Create the Provider component with the values to pass as state.
- Create reducers to manipulate state and attach with the state using
useReducer
hook. - Wrap the component where value is to be accessed inside the
Provider
component. - Access values using the
useContext
hook.
Follow me around the web
Some places where you can find me online!
Original Link: https://dev.to/codewithfahad/effective-state-management-in-react-using-context-api-and-hooks-15ch
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To