How I replace Redux, Redux Saga with React Hooks
This article will show you how to easily and effectively replace redux, redux saga with React Hooks.
Recently, I’ve been searching for solutions to replace Redux using React Hooks completely. By completely, I meant I have to replace how redux with the help of redux-saga handle side effects when making API request. Most articles, blog posts I found make use of ContextAPI together with useState/ useReducer, which is fine if you only need to make a few API calls. But if you plan to go production, with a lot of API call, then maintaining the codebase with this approach will soon become a nightmare.
Let’s say we are building a React application that makes the request call to some backends that use YoutubeAPI to fetch a list of Youtube Video by keywords.
*** This post assumes you have knowledge about React, React Hooks, Redux, and Redux-Saga, as I am not going to explain much about them.
Firstly, we need to have a plan. Let’s see how React, Redux, Redux Saga fits into an application:
As you can see, in a typical React-Redux project, we have a store which holds all the data for the application, action which is used to send information about a particular activity on the view to the store. In order to handle side effects ( asynchronous calls, handle errors,… ), people use Redux-saga, which simply is a middleware function that stands between our action and our store.
To completely replace redux and redux-saga, we have to all the components in the above architecture. I am going use useReducer together with ContextAPI to replace the action and the store. For the middleware, I am going to use a pure javascript function to replace it. Now we understand our problem, and have a plan to tackle it, let’s get started.
Create a new folder called store, and inside that create a new file called actionTypes.js
.../store/actionTypes.js export default {
LOAD_YOUTUBE_INFO: 'LOAD_YOUTUBE_INFO',
LOAD_YOUTUBE_INFO_SUCESS: 'LOAD_YOUTUBE_INFO_SUCESS',
LOAD_YOUTUBE_INFO_FAIL: 'LOAD_YOUTUBE_INFO_FAIL',
SET_LOADING_INDICATOR: 'SET_LOADING_INDICATOR'
}
Next, create a new file called reducer.js, and enter the following code:
.../store/reducers.jsimport types from './typeAction'
const initialState = {
loading: false,
youtubeInfo: null,
error: '' }
const reducer = (state = initialState, action) => {
switch (action.type) {
case types.LOAD_YOUTUBE_INFO_SUCESS:
return { ...state, youtubeInfo: action.payload, loading: false }
case types.LOAD_YOUTUBE_INFO_FAIL: return { ...state, loading: false, error: action.payload }
case types.SET_LOADING_INDICATOR: return { ...state, loading: true }
} } export { initialState, reducer }
If you have used Redux before, you should have to problem understanding the above code. Now lets create a new file to define our action
.../store/action.js import types from './typeAction' export const useActions = (state, dispatch) => ({
loadYoutubeInfo: data => {
dispatch({ type: types.SET_LOADING_INDICATOR })
dispatch({ type: types.LOAD_YOUTUBE_INFO, payload: data }) } })
We defined our reducer / store, and action, there is one more missing piece in the puzzle before we pass all of them to the component tree using ContextAPI. Create a new file called middleware with the following code:
.../store/middleware.js import types from './typeAction'
import axios from 'axios' export const applyMiddleware = dispatch => action => {
switch (action.type) {
case types.LOAD_YOUTUBE_INFO:
return http
.get( your endpoint here )
.then(res => dispatch({
type: types.LOAD_YOUTUBE_INFO_SUCESS,
payload: res.data }))
.catch(err => dispatch({
type: types.LOAD_YOUTUBE_INFO_FAIL,
payload: err.response.data }))
default: dispatch(action)
} }
As we discuss above, we will make request in the middleware function, and pass the status of the request to our store. Now we have everything we need, lets put them in use and pass them down to the component tree.
.../store/store.js import { createContext, useReducer } from 'react'
import { reducer, initialState } from './reducers'
import { useActions } from './action'
import { applyMiddleware } from './middleware' const initialState = {
loading: false,
youtubeInfo: null,
error: ''
} const StoreContext = createContext()
const StoreProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState)
const actions = useActions(state, applyMiddleware(dispatch))
return ( <StoreContext.Provider value={{ state, actions }}>
{children}
</StoreContext.Provider> )
}
export { StoreContext, StoreProvider }
As you can see, I don’t directly pass the dispatch function down the component tree, but the actions function. This will create another layer or middleware. Now lets test our new Redux-like store:
import React, { useContext, useEffect } from 'react'
const SomeComponent = () => {
const { state, actions } = useContext(StoreContext)
useEffect(() => {
actions.loadYoutubeInfo('what you want to search ? ')
}, [])
return ( <div> ... </div> ) }
export default SomeComponent
Now we need to put a logger to our action, middleware, and store to make sure what we did so far works. Here what we gets:
Yay !!!! It works. We have together replaced redux, and redux saga, and created a flux architecture using only React.
You can read the code for this post here: https://github.com/rossitrile/replace-redux-saga-by-react-hooks
Let’s me know what you think in the comment below ;).
Originally published at https://mastereact.com on June 30, 2019.