Implementation of React Redux data store with React Hooks and functional components

With the release of React 16.8 there is a new feature that will replace the redundant and hard to maintain React life cycle methods like ComponentDidMount , ComponentWillMount etc.React has moved from Class to functional components.In this tutorial ill be explaining how you can get data from redux store in your functional component.

The Redux Architecture

The concepts of redux is exactly the same as class with React Hooks and functional components.We create a centralized data store (A collection of reducers).Actions are there which act as a communication layer between your component and store and finally reducers which practically holds the data.

The packages required

axios : For your API calls.

react-redux : Core functionality for redux.

redux: For creation of store.

Simply execute this command from your root folder
npm i axios react-redux redux --save-dev

What im trying to achieve is to get a list of users through an API call , store it in store and retrieve the data in one of the component.

The code part

In your index.js root file add these changes highlighted in bold text

import React from 'react';import ReactDOM from 'react-dom';import './index.css';import { createStore } from 'redux';import { Provider } from 'react-redux';import UserComponent from './App';import rootReducers from './reducers';const store = createStore(rootReducers)ReactDOM.render(<Provider store={store}><UserComponent /></Provider>,document.getElementById('root'))

Usercomponent : Our functional component where we are getting this data.

createStore: This helps to create our store. we need to pass a reducer or a collection of reducer to it as a parameter.

Provider: A higher order component (HOC) responsible for making the store available across the entire app.

rootReducers: your list of reducers , we will come to this later

Configuration

Lets create a config.js file and store our url end points (Config.js)

const config = {baseUrl: 'https://jsonplaceholder.typicode.com/',endPoints:{users: 'users',}
};
export default config;

A Communication layer responsible for API (Communication.js)

import axios from 'axios';
import config from '../config';
const Communication = {getMethod(endpoint) {
return axios.get(config.baseUrl + endpoint).then(response => { return response.data; })
}};
export default Communication;

A service layer (Userservice.js)

As we only have one API , we need only one service file (Userservice.js).Ill explain the ‘dispatch’ in detail in the next section.So basically this is a Service Object which uses Axios (Communication.js) for API call.

import Communication from './Communication';import config from '../config';const UserService = { loadUsers(dispatch) {dispatch({type: 'LOAD_USERS',payload: null})Communication.getMethod(config.endPoints.users).then(users => {dispatch({type: 'GET_USERS',payload: users})}).catch(() => {
dispatch({type: 'ERROR_USERS',payload: null})
}).finally(() => {})
}}
export default UserService;

The reducers (userReducer.js)

As we are managing only one api call , we need only one reducer.Basically reducer is a function that returns an object based on a switch statement.

const initialState = {usersList : [],loading : true,error: false,};function usersReducer(state = initialState, action) {switch (action.type) {case 'LOAD_USERS':return { ...state, usersList:[], error: false, loading: true }case 'GET_USERS':return { ...state, usersList: action.payload, error: false, loading: false }case 'ERROR_USERS':return { ...state, usersList: [], error: true, loading: false }default:return state;}};export default usersReducer;

We need to combine this reducers with other reducers.Here we only have one reducer.But ideally we have more than one API call in any project so we can use like this

import {combineReducers} from 'redux';
import usersReducer from './usersReducer';
// other reducers needs to import hereconst rootReducers = combineReducers({usersData : usersReducer
// if there are other reducers , we can add here one by one
});export default rootReducers;

So rootReducers hold your entire list of reducers in a key value pair.

Finally The folder structure will look like

So we created API layer (Communication.js),Service layer (userService.js).The last step is from the component we need to call the API, store it in store and populate in UI.

import the required packages in UserComponent

import { useDispatch, useSelector } from 'react-redux';
import UserService from './services/Userservice';

Call the userService from useEffect hook

export const UserComponent = () => {
const dispatch = useDispatch();
// Slice the data from the store using useSelector
const usersInfo = useSelector((state) => state.usersData);
useEffect(() => {
UserService.loadUsers(dispatch);
}, [dispatch])

So what we did was we accessed data from userReducer using useSelector.The usersData key is what we have given in reducers/index.js

Finally we call the userService in the useEffect hook and pass the dispatch function to it.In the user service we keep certain checkpoints and during this checkpoint we dispatch a call that will alert the userReducer about its current status.For eg:

dispatch({type: 'LOAD_USERS',payload: null})

This will alert the reducer that the API call is about to make.In the userReducer, the switch statement will return the object corresponding to ‘LOAD_USERS’.The user component will render based on that data supplied by the reducer.

dispatch({type: 'GET_USERS',payload: users})

This will return the data that we are getting from the API back to reducer.The userReducer in turn returns the corresponding object for ‘GET_USERS’

case 'GET_USERS':
return { ...state, usersList: action.payload, error: false, loading: false }

The additional keys error, loading can be used to determine the state of the component (loading : show a progress bar, error : show an error status etc)

Finally the component will look like this

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import loader from './assets/loader.gif';
import UserService from './services/Userservice';
import './App.css';
export const UserComponent = () => {const dispatch = useDispatch();const usersInfo = useSelector((state) => state.usersData);useEffect(() => { UserService.loadUsers(dispatch); }, [dispatch])
const errorContainer = () => {return <div>ERROR IN API</div>;}
const showLoader = () => {return <div><img src={loader} alt="loading ..." title ="loading ..."/></div>;}const renderData = (usersInfo) => { return usersInfo.error ? errorContainer():<div className="container">
<div className="header"><div>NAME</div><div>EMAIL</div><div>PHONE</div><div>WEBSITE</div></div>
{usersInfo.usersList.map((user, index) =>
<div className="row" key={index}><div> { user.name } </div>
<div>{ user.email }</div>
<div>{ user.phone } </div>
<div>{ user.website } </div>
</div>)}
</div>}
return (
usersInfo.loading ? showLoader() : renderData(usersInfo)
)}
export default UserComponent;

Summarizing the implementation
What we did is we created a component and from that component we called a service which communicates the data back to one of the reducers.The components binded to that reducer will automatically reloads when ever a new data comes in the reducer.At any given point the data in userReducer is same across all the components.In this way data consistency is maintained.

The entire example is available in github.

https://github.com/ubercooluk/redux-with-hooks.git

Just another geek

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store