Tutorial: Redux and React-Redux from Scratch

Redux is simple storage that stores the value of different variables into one place, and the entire app will read up-to-date values from there. You can use it in react, react-native, or any javascript project.

Chaudhry Talha 🇵🇸
14 min readFeb 9, 2022

This tutorial will take you from a simple hello world example and by the end you’ll be implementing CRUD functionality using redux.

Prerequisites — JavaScript Syntax understanding and understanding of why state management is important and understanding of the hooks. On a side note, when I started learning redux I first learnt what is a ContextAPI which helped me understand the concept of the need to one store where all the states are (a single source of truth) and it help me wrap my mind around redux. So, it’s not mandatory that you known ContextAPI but giving the article a quick read might help.

This tutorial is composed of the following sections:

  • Reading: Quick Overview of Redux
    Theory plus code mixed. Highly encouraged to read as it’s important. Redux uses javascript, so if you know javascript you’ll find it easy to grasp the concept.
  • Redux Hello World (Code)
    A step by step approach to doing a HelloWorld example using redux. After you have the concepts from the first section this would be a perfect example to follow to get hands-on experience
  • Extending Hello World Example (Code)
    We’ll see how we can make small changes to clean our redux code. As a bonus, I’ll be sharing how to use Firebase Firestore & Redux, which will be easy for you to understand if you were able to grasp the concept of the hello world example.

Reading: Quick Overview of Redux

Before getting into anything you need to have this very basic understanding of the three basic concepts in redux. I’ve tried to explain them as simply as I could, but feel free to drop a comment if you have any questions.

  1. Actions
  2. Reducer
  3. Store

Below is a very short to the point theory that I would encourage you to read and follow along. A basic understanding of the redux framework can be scraped off this diagram below:

A very good basic model I found at https://medium.com/@sreedevr/understanding-the-basic-redux-concepts-8e1d210c200d. Read the TLDR; for description.

TLDR; A component dispatches an action into the reducer. The reducer then knows what to do with that action and updates the values in the store accordingly.

We’ll take a CRUD approach to hello world to give you a basic understanding of how Redux works. Based on the diagram above the symbols shows exactly the meaning of what everything does. Like reducers are used as a filter. Action is click, press, etc actions upon which you dispatch an action to the reducer. The reducer then filters out the data and updates the value in store from where our UI read and updates.

Let’s quickly go through the main terms. Encourage you to read this theory if you have been avoiding it.

ACTION: An action is a plain javascript object. An action contains two things, the type field should be a string that gives this action a descriptive name like "counter/increment". We usually write that type string like "domain/eventName". Next, we need payload field that can be anything i.e. int, string, object, nested objects, arrays, etc. (Don’t worry if you don’t know how to do it, we’ll go to the syntax shortly.) A typical action object looks like this:

const addTodoAction = {
type: 'todos/todoAdded',
payload: 'But milk'
}

Action Creators — Since we’re talking about actions, it’s worth mentioning that we make functions that create and returns the action object. These functions are called action creators and it looks something like this:

function addTodo(text) {
return {
type: 'todos/todoAdded',
payload: text
}
}

So, this is basically what actions will look like, and up to this point, we’ve only written vanilla JS.

REDUCER: A reducer is a function that receives two things:

  • current state
  • action object

You can think of a reducer as an event listener which handles events based on the received action (event) type. Inside a reducer function, this is what goes:

See if the reducer cares about the provided action? If yes, then make a copy of the state, update the copy with new values and return the updated state. If not, then return the existing state unchanged.

Confused? Here is an example that exactly interprets the above text to code:

Reducer in JavaScript

STORE: Store is the database where the state of your entire application exists. It has some built-in functions like getState() that is used to return the current state value. A store takes in a reducer as a parameter. Another important method that the store has is dispatch().

Dispatch —The only way to update the state in the store is to call store.dispatch() and pass it an action object, for example:

store.dispatch({ type: 'counter/increment' })
console.log(store.getState()) //{count: 1}

You can think of dispatch as “triggering an event” like pressing a button, or an API call is finished, etc.

Something happened, we wat the store to know about it, we dispatch an action. A reducer act as an event listener basically, and when they hear an action they are interested in, they update the state in response. A small example would be:

const increment = () => {
return {
type: 'counter/increment'
}
}
store.dispatch(incremet())
console.log(store.getState()); //{value: 2}

So, you should now have the basic idea of what action, action creator, reducer, store, getState, and dispatch is.

Next, we’ll put all this knowledge into action by creating our hello world application.

Redux Hello World

In your javascript project install redux:

npm i redux@4.1.2

If you’re using react-native you’ll also need to install npm i react-redux@7.2.6

I’m using a React Native project for demonstration so I’ll use that syntax. Create these files accordingly to your project.

  • Create a file in your project named ReduxExample.js in src/screens.
  • Create another file named reducers.js in src/utils/redux folder. (Create a redux folder in utils)

As a hello world application in react-redux, we’ll build a counter. Take this as an example to understand the basic structure.

For a counter, we could only have two possible actions:

  • Increment
  • Decrement

Let’s first create a reducer and add these two actions to it. So add this code in reducer.js

const InitialState = { count: 0 }function rootReducer(state = InitialState, action) {switch (action.type) {case 'counter/increment': return { ...state, count: state.count.value + 1 };case 'counter/decrement': return { ...state, count: state.count.value - 1 };default: return state;}};export { rootReducer };

Does this look familiar? In our reducer example, we have used the same line o code with increment action in it. The above code will work when an action will be dispatched and the reducer will then make the change and return us the updated initial state.

We would want the value of count to be accessible through the app, o matter which screen we’re on. To do that open the App.js which is basically where all the navigation is (For some javascript projects, it can be index.js). Add this code in it:

import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { rootReducer } from './src/utils/redux/reducers';
import ReduxExample from './src/screens/ReduxExample';

We’ll be needing these three items to wrap our application around redux.

Provider — A Provider is a component that makes the store available to any nested components that need to access the store.

createStore — Use to create a store. Takes a reducer as an argument.

Next, in App.js we’ll wrap our NavigationContainer inside a Provider.

return ( <Provider store={store}>
<NavigationContainer theme={MyTheme}>
<Stack.Navigator>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="ReduxExample" component={ReduxExample} />
</Stack.Navigator>
</NavigationContainer>
</Provider>
);

In the code above we have wrapped our NavigationContainer in a Provider. A provider needs a store value and we have provided it as store we have to createStore. So, just above the export default App add this line:

export const store = createStore(rootReducer);

This will create the store and make it accessible to different levels of the app.

We have implemented actions, reducer, and the store. So, we’re now ready to use this redux setup and create our app.

Next, we’ll perform CRUD operations-basically. We’ll first read the value count to be displayed on some UI, so since I’m using react-native my ReduxExample.js I've added:

import React from 'react';
import { store } from '../../App';
import { View, Text, Button } from 'react-native';
function ReduxExample(props) {return (
<View>
<Text>Redux Hello World</Text>
<Text>Counter {store.getState().count}</Text>
</View>
);
}export default ReduxExample;

We’ve imported our store and got the value from redux. You can also do <Text>Counter {JSON.stringify(store.getState())}</Text> to print the complete state object which would right now be equal to theInitialState.

Read Operation of Redux

You’ve successfully finished the read operation. For the next step, we’ll need two buttons one to increment and one to decrement.

Now, we’re about to perform the update function. We’re already done with our create a part with the initial state that we've provided when we initialized the createStore.

To perform the update we first need to add two buttons in our UI.

//...<View style={{ flexDirection: 'row', justifyContent: 'space-between', margin: 24 }} >     <Button title='Increment' onPress={() => { store.dispatch({ type: 'counter/increment' }) }} />    <Button title='Decrement' onPress={() => { store.dispatch({ type: 'counter/decrement' }) }} /></View>//...

In the code change, we have called dispatch on button press. If you press the button now you’ll not see the UI being changed but if you console.log(state.count); in reducer.js you’ll see the values are updating in the console but not on the UI.

All the render is triggered by the dispatch (after the first render). So store.dispatch does resolve the store data, but someone needs to notify the React this is done, so we use useSelector. Also, we usually never use things like store.dispatch or anything store. hence it’s not a good practice.

So, we’re about to change a few things inside ReduxExample.js which will solve the following problem:

  1. Update the UI as soon as the increment button is pressed.

Since react-redux is a package specifically to handle react related behaviors with redux so we have two methods i.e. useSelector, useDispatch

useSelector(selector: function, equalityFn: function) — Allows you to extract data from the Redux store state. A selector will subscribe to the Redux store and run our selector whenever an action is dispatched.

useDispatch() — Allows you to dispatch an action to the store by simply adding an action object as an argument to the new variable. This hook returns a reference to the dispatch function from the Redux store. You may use it to dispatch actions as needed.

Make the following changes in the ReduxExample.js

import React from 'react';
// import { store } from '../../App';
import { View, Text, Button } from 'react-native';
import { useSelector, useDispatch } from 'react-redux'
import { rootReducer } from '../utils/redux/reducers';
function ReduxExample(props) {const counter = useSelector(rootReducer);
const dispatch = useDispatch();
return ( <View>
<Text>Redux Hello World</Text>
<Text>Counter {counter.count}</Text>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', margin: 24 }} >
<Button title='Increment' onPress={() => { dispatch({ type: 'counter/increment' }) }} />
<Button title='Decrement' onPress={() => { dispatch({ type: 'counter/decrement' }) }} />
</View>
</View>
);
}
export default ReduxExample;

We have commented on the line that was importing the store from App.js and imported three things useSelector, useDispatch from react-redux and our rootReducer. Then we have created a selector named counter and provided it with our reducer function as an argument. Then we have dispatch which is a useDispatch hook we can use to dispatch actions. Later in the UI, you can see we have counter.count which we’re using to read data and then use dispatc(action) to update the values.

If you run the app now you’ll see it working accordingly as we needed.

Final Output of the hello world example

That’s it for a redux hello world. You have successfully initialized, stored, retrieved, and update the values using the redux framework and this is it, the redux framework allows us to follow certain rules and now we can store anything in the store and use it at any point of our application. You can get the code that we did up to this point here: https://gist.github.com/thechaudharysab/59a6f69ed7a7fcce5ac6857a0ef42678.

Extending Hello World Example

We’ll extend the hello world example to be able to store full objects and not just one property like count in our hello world example. We’ll see some snippets like fetching data from firestore and storing it in the state to be used by another screen.

I have two screens in my Navigation Controller i.e. Home and ReduxExample. We’ll use Home to fetch some data from Firebase Firestore and then store it and use it on the ReduxExample screen.

But first, the code that we did, we’re going to follow exactly the same approach. Before we’ve written down the name of the action manually like 'counter/increment'. If we were to change this action same we have to change it from two places based on the example we just did but in a more complex project, you can see how this is a problem. So, we put actions in a simple javascript file from where we import them and use them.

So, create a new file named actions.js in untils/redux folder and add this code in it:

export const INCREMENT = 'counter/increment';
export const DECREMENT = 'counter/decrement';

Same actions that we have done, we’re only putting in variables and exporting them. We’ve used these names in reducers.js and ReduxExample.js so we’ll change it in both files. For reducers.js

import { INCREMENT, DECREMENT } from "./actions";//... other codecase INCREMENT: return { ...state, count: state.count + 1 };case DECREMENT: return { ...state, count: state.count - 1 };default: return state;//... other code

Similarly, in ReduxExample.js we make the following changes:

//... other codeimport { INCREMENT, DECREMENT } from '../utils/redux/actions';//... other code<Button title='Increment' onPress={() => { dispatch({ type: INCREMENT }) }} />
<Button title='Decrement' onPress={() => { dispatch({ type: DECREMENT }) }} />

If you run the app it’ll work in the same way. So, we haven't done anything out of the ordinary we just took some strings placed them in a separate file, and then read them from that one file.

Bonus: Firebase Firestore & Redux

Assuming you have already configured the firebase app for your project. I’ve implemented https://rnfirebase.io/ and here are links to quick tutorials on configuring firebase and firestore for react native project (if you need).

Since I've two screens in my app i.e. Home.js and ReduxExample.js. I’ll be using Home.js to fetch the data from firestore and update it in the store using redux. Then we’ll use ReduxExample.js to read that data and display it accordingly.

So, first, we’ll add the actions we’re about to do i.e. fetch all documents from the firestore. So, in actions.js add:

export const FETCH_ALL_SERIES = 'series/fetchAll';

I’ve named my action to fetch all series. I have a Series collection on firebase.

Next, in the reducer, we’ll add this action. Replace all the code you might have from the yellow wold example and replace it with this in reducers.js

import { FETCH_ALL_SERIES } from "./actions";
const InitialState = { allSeriesData: [] }function rootReducer(state = InitialState, action) {switch (action?.type) case FETCH_ALL_SERIES: return { ...state, allSeriesData: action.payload }default: return state;}};export { rootReducer };

In the above code, we have updated the initialState to have allSeriesData which is going to be an empty array initially. Then in the FETCH_ALL_SERIES case, we’re returning the current state with updated allSeriesData that we’ll pass via action.payload which you’ll see shortly.

The difference between this example and the hello world is the action.payload. Which can be anything like action.data or something else it’s totally up to you but as a tradition we use payload.

Now, open the Home.js. This is the file where we’ll be calling firestore to fetch all the docs of a collection and then we’ll dispatch the received data to redux.

//... other code
import firestore from '@react-native-firebase/firestore';
import { FETCH_ALL_SERIES } from '../utils/redux/actions';
import { useDispatch } from 'react-redux'
function Home(props) {const dispatch = useDispatch();function getAllSeries() {firestore().collection('ADD_YOUR_COLLECTION_NAME_HERE').get().then((queySnap) => { // console.log(queySnap.docs); // [{}, {}] //This is where we should update the redux as well as where you'll update the state
dispatch({ type: FETCH_ALL_SERIES, payload: queySnap.docs });
}).catch((e) => {
console.log('Some error occured: ' + e);
})}
useEffect(() => {
getAllSeries()
}, [])
//... other code

Same as before we’re going to useDispatch to dispatch actions. I’ve created a function named getAllSeries in which I’m fetching all documents from a collection and then upon the successful response we’re dispatching the FETCH_ALL_SERIES along with the payload which will have all the queySnap.docs.

Now, the last thing that is remaining is to read values from redux. We’ll display it in ReduxExample.js so let’s open that and add the following code:

//... other code
import { useSelector } from 'react-redux'
import { rootReducer } from '../utils/redux/reducers';
function ReduxExample(props) {const selector = useSelector(rootReducer);return (<View>
<Text>Firebase & Redux Example</Text>
<Text>Value retrived fom redux is:
{JSON.stringify(selector.allSeriesData[0]._data.series_name)}</Text>
</View>
);}export default ReduxExample;

We’re simply using a selector to access our array allSeriesData[0] first element. You can loop through it as you want to render it but for the sake of this example I’m hard coding the 0. Then _data is where all the fields of this document are as firebase returned data in this form. FInally, I have a field named series_name so my final output is:

So, you see how simple it is to access the object anywhere in the app. You can use the same approach for Axios response if you are using that. You can fin all the code we just did here: https://gist.github.com/thechaudharysab/e29dad4be86a58c03e3bd3a9f93a5017

You can extend your initial state to have multiple objects like:

const InitialState = { allEmployeeData: [], allPayRollData: [], allResumeData: [], allApparisalData: [] }

You can also make multiple reducers, but that's an advanced topic so search for things like what is combineReducers and some other advanced topics of redux do and how they make things more efficient for you. What we did is the very basics of redux and there are more to it like redux-saga, redux persist, and many more.

Feel free to add comments to keep this conversation going. Have a method to share for redux? Write it in the comments and I’ll add it to the main article with credits. Highlight and suggest changes, I’ll be happy to keep this up to date.

As always if you find this helpful share and press the 👏🏻 button so that others can find it too. If you see a typo feel free to highlight it or if you’re stuck drop a comment and I’ll try my best to help you.

buymeacoffee.com/chaudhrytalha

All my tutorials are free but if you feel like supporting you can buymeacoffee.com/chaudhrytalha

Happy Coding 👨🏻‍💻

--

--