React Hooks + Context API = Redux | Build a Scalable APP
Redux has been since the beginning a powerful tool to allow us to handle states in React and other frameworks like Angular and more. The concept of redux is simple, instead to handle the state locally in every component of our application redux combine all state into a big state called Store, from the store all our component can be updated every time our Store change.
In the above picture we can see how the data flow in our application, it has one way. Imagine we have a button, and the user clicks on the button this click event is an Action that we are dispatching, then our Reducer can catch the action, think about Reducer as a filter, this filter can only return the state that matches with our actions, and then the reducer alter and modify out Store, and our changes in the Store will be reflected in our view.
I used the same concept to create my own redux, React has some good features to allow me to perform the same behavior. We can use hooks and Context API, Hooks allow us to create a functional component and at the same time handle state something that was impossible before React 16.8.0, and Context API allows React to pass down props from the Context Provider to let us consume those values from our children components.
Context API was introduced in React 16.x before of this version we had to drill down the props.
In the diagram at your left, you can see how we used to pass down props from Container Component 1 to Child Component 4, we have to pass down first the props to Container Component 4 and then pass down to 4, we still can do it but when we add more and more child component it’s getting really hard to maintain. At the right, we have the context API diagram, we only need to add a Provider component at the top level and then we are able to access the provider information from any child component.
Before context API came out, we had Redux dependency to allow us to create a global store in the scope and then with the method connect to access the store from any child component.
How Context API Works
To create our Store – Global State we’re going to create how context
MainContext.js
import React, { createContext } from "react"; const MainContext = createContext(); export default MainContext;
Creating the context in a separate file gives us the ability to import the context from any child component.
AppProvider.js will give us the ability to wrap our main component with the provider.
We only need to import the MainContext.js and wrap our children’s components with our provider
import React from "react"; import MainContext from "./MainContext"; const AppProvider = props => { const initialValue = { name: "victor" }; return ( <MainContext.Provider displayName="Main Context" value={initialValue}> {props.children} </MainContext.Provider> ); }; export default AppProvider;
Once we have our provider created, we can wrap any component with this one, because we want to access our MainProvider state from any component we need to at it into our root component app.js is our empty point.
import React from "react"; import AppProvider from "./AppProvider"; const App = () => { return ( <AppProvider> <div> Child Component </div> </AppProvider> ); }; export default App;
To test that our provider is working property we are going to create a Child.js component and add it on app.js
In this component, we import our MainContext component and then use the useContext react Hooks to access the MainContext value
import React, { useContext } from "react"; import MainContext from "./MainContext"; const Child = () => { const mainContext = useContext(MainContext); return ( <div> This is my Name From the provider: { mainContext.name } </div> ); }; export default Child;
Adding Child.js component into our app.js entry point.
import React from "react"; import AppProvider from "./AppProvider"; import Child from "./Child"; const App = () => { return ( <AppProvider> <Child /> </AppProvider> ); }; export default App;
Handling Dynamic State in our Context API
In AppProvider.js we are going to add the hooks useState, these hooks allow us to create a state and change the value dynamically.
import React, { useState } from "react"; import MainContext from "./MainContext"; const AppProvider = props => { const [name, setName] = useState("victor"); const changeName = name => setName(name); const initialValue = { name, changeName }; return ( <MainContext.Provider displayName="Main Context" value={initialValue}> {props.children} </MainContext.Provider> ); }; export default AppProvider;
The method changeName allows us to change the name of the state name using the hook setName and then adding this method to the InitialValue object, then we’re able to access them from the Child.js component.
import React, { useContext } from "react"; import MainContext from "../../MainContext"; const Child = () => { const mainContext = useContext(MainContext); const handleChange = e => mainContext.changeName(e.target.value); return ( <div> <div> This is my Name From the provider: {mainContext.name} </div> <p> <input onChange={handleChange} placeholder="Enter your name" /> </p> </div> ); }; export default Child;
useReducer Hooks
In the previous implementation, I used the hooks useState to create and state and also use setName to modify the state, is simple and easy but will be harder to create a state for every property of our application, to solve this issue we can use useReduce to manage our global state better.
For more info about useReducer click here.
import React, { useReducer } from "react"; import MainContext from "./MainContext"; const AppProvider = props => { const reducer = (state, action) => { switch (action.type) { case "CHANGE_NAME": return { ...state, name: action.payload }; default: return state; } }; const initialValue = { name }; const [state, dispatch] = useReducer(reducer, initialValue); const changeNameAction = payload => ({ type: "CHANGE_NAME", payload }); const onChangeName = name => dispatch(changeNameAction(name)); const stateWithMethods = { ...state, onChangeName }; return ( {props.children} ); }; export default AppProvider;
We need to update the method in Child.js instead to use mainContext.changeName we should use mainContext.onChangeName.
const handleChange = e => mainContext.onChangeName(e.target.value);
import React, { useContext } from "react"; import MainContext from "../../MainContext"; const Child = () => { const mainContext = useContext(MainContext); const handleChange = e => mainContext.onChangeName(e.target.value); return ( <div> <div> This is my Name From the provider: {mainContext.name} </div> <p> <input onChange={handleChange} placeholder="Enter your name" /> </p> </div> ); }; export default Child;
How to combine multiple reducers
In order to make our application more scalable we need to refactor our code and organize code in a better way, to do that we need to separate actions and reducers, in our containers we need to add actions and reducers for each component, but how can we combine multiples reducers in one? for that, we need to create a utility function.
Let’s start creating a simple reducer called nameReducer
If you run this code in jsfiddle you will see in your console log this:
Using CombineReducer method
victors1681/contextapi-hooks-tutorial
This is the repo of my tutorial about create an app using Context API and Hooks without Redux – victors1681/contextapi-hooks-tutorial
2 Comments
Leave a Comment
You must be logged in to post a comment.
Today, we are going to explore it and develop a custom Hook to manage global states — an easier to use method than Redux, and more performant than Context API.
It’s sounds good! Let me know how is goes! I already have an app in production and it’s working sweet 🙂