Getting Started Redux with React

Amir Mohammad Fakhimi
17 min readJul 8, 2023

--

Hi everyone!

In this article, I want to describe what Redux does in React, its basic features, and how to use it to integrate with React. At last, I implement a simple project using React, Redux, and TypeScript. Hope you enjoy it! 😉

It’s one of the three parts of my React state managers collection:

Ok, What does Redux do in React?

As you know, there is a concept named state in React. In short, Redux is a state management tool with React. It is a library that you can add to your project easily using npm. Imagine there is a state in the left-down part of the DOM tree in your React project. If you want to use that state in the right-down of the DOM of your project, traditionally you should pass the state and some callbacks to achieve the goal. But using Redux, you just need an import in the right-down component! It’s the benefit and the magic of Redux.

Redux is more general than React projects. You can use it in any JavaScript project (I won’t support this part). So if you want to use it in the React project, you also need the React-Redux library which is the Redux official package.

Redux has some general APIs, but there is also Redux Toolkit which makes creating projects even easier. Redux official documentation about Redux Toolkit says:

Redux Toolkit is our recommended approach for writing Redux logic. It contains packages and functions that we think are essential for building a Redux app. Redux Toolkit builds in our suggested best practices, simplifies most Redux tasks, prevents common mistakes, and makes it easier to write Redux applications.

I’ll use React-Redux and Redux Toolkit in this article.

Redux Terminologies

I got help from Redux official documentation to write this part.

Actions

An action is a plain JavaScript object that has a type field. You can think of an action as an event that describes something that happened in the application.

The type field should be a string that gives this action a descriptive name, like 'todos/todoAdded'. We usually write that type string like 'domain/eventName', where the first part is the feature or category that this action belongs to, and the second part is the specific thing that happened.

An action object can have other fields with additional information about what happened. By convention, we put that information in a field called payload. The type of the payload is dependent on its usage in your project.

A typical action object might look like this:

type actionType = {
type: string,
payload: number
}

const incrementCounterAction: actionType = {
type: 'counter/increment',
payload: 2
}

Action Creators

An action creator is a function that creates and returns an action object. We typically use these so we don’t have to write the action object by hand every time:

type actionType = {
type: string,
payload: number
}

const addTodo = (value: number): actionType => {
return {
type: 'counter/increment',
payload: value
}
}

Reducers

A reducer is a function that receives the current state and an action object, decides how to update the state if necessary, and returns the new state: (state, action) => newState. You can think of a reducer as an event listener which handles events based on the received action (event) type.

Reducers must always follow some specific rules:

  • They should only calculate the new state value based on the state and action arguments.
  • They are not allowed to modify the existing state. Instead, they must make immutable updates, by copying the existing state and making changes to the copied values. (Later we’ll see Redux Toolkit’s createSlice function which lets us mutate a state in a reducer and there, using it is recommended.)
  • They must not do any asynchronous logic, calculate random values, or cause other “side effects”. (You can achieve asynchronous logic using Thunks. We won’t support that in this article. Read more here.)

The logic inside reducer functions typically follows the same series of steps:

  • Check to see if the reducer cares about this action. If so, make a copy of the state, update the copy with new values, and return it.
  • Otherwise, return the existing state unchanged

Here’s a small example of a reducer, showing the steps that each reducer should follow:

type actionType = {
type: string
}

type stateType = {
value: number
}

const initialState: stateType = {
value: 0
}

function counterReducer(state: stateType = initialState, action: actionType) {

// Check to see if the reducer cares about this action
if (action.type === 'counter/increment') {
// If so, make a copy of `state`
return {
...state,
// and update the copy with the new value
value: state.value + 1
}
}

// otherwise return the existing state unchanged
return state
}

Slices

There is a concept in Redux named Slice. A slice is a collection of Redux reducer logics and actions for a single feature in your app. The name comes from splitting up the root Redux state object (store) into multiple slices of state.

createSlice is a function that accepts an initial state, an object of reducer functions, and a slice name, and automatically generates action creators and action types that correspond to the reducers and state.

This API is the standard approach for writing Redux logic.

You can define a slice as follow (complying immutability):

import { createSlice } from "@reduxjs/toolkit";

const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: state => {
return {
...state,
value: state.value + 1
}
},
decrement: state => {
return {
...state,
value: state.value - 1
}
},
incrementByAmount: (state, action: PayloadAction<number>) => {
return {
...state,
value: state.value + action.payload
}
}
}
})

Internally, createSlice uses createAction and createReducer, so you may also use Immer to write mutating immutable updates as below:

import { createSlice } from "@reduxjs/toolkit";

const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: state => {
state.value += 1
},
decrement: state => {
state.value -= 1
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
}
}
})

Please pay attention that the type of action in the incrementByAmount function is PayloadAction<number> which is equal to the following type:

type actionType = {
type: string,
payload: number
}

createSlice has three main parameters:

  • name: A string name for this slice of the state. Generated action type constants (type field in actionType) will use this as a prefix. It should be a unique name between all slices otherwise it may produce bugs!
  • initialState: The initial state value for this slice of the state.
  • reducers: An object containing Redux case reducer functions (functions intended to handle a specific action type, equivalent to a single case statement in a switch). The keys in the object will be used to generate string action type constants. Also, if any other part of the application happens to dispatch (tell the application that a specific change happened) an action with the same type string, the corresponding reducer will be run.

So there are these actions corresponding to the above slice:

counterSlice.actions.increment()
counterSlice.actions.decrement()

// number 2 is for an example. You can use any other number!
counterSlice.actions.incrementByAmount(2)

or in another way:

const {increment, decrement, incrementByAmount} = counterSlice.actions

And if you want to get its reducer, you can use:

counterSlice.reducer

Store

Any Redux application has just one main state. The state lives in an object called the store.

The store is created by passing in a reducer, and has a method called getState that returns the current state value:

import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
})

console.log(store.getState())
// {counter: {value: 0}}

Or if you have two reducers, it is something like this:

import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({
reducer: {
counter: counterSlice.reducer,
searchInput: inputSlice.reducer
}
})

console.log(store.getState())
// {counter: {value: 0}, searchInput: {input: "my desired search"}}

As told, Redux has just one main state, named store! Please do not get confused. All states of the app are aggregated in one main state which name is store. Each real React state is named a slice in a Redux app.

Dispatch

The React-Redux library has a custom hook called useDispatch. The way to update the state and UI is to call useDispatch and pass in an action object. The Redux’s store will run its corresponding reducer function and save the new state value inside, and we can call getState() to retrieve the updated value:

import { useDispatch } from "react-redux";

const dispatch = useDispatch()
dispatch(counterSlice.actions.increment())

console.log(store.getState())
// {counter: {value: 1}}

You can think of dispatching actions as “triggering an event” in the application. Something happened, and we want the store to know about it. Reducers act like event listeners, and when they hear an action they are interested in, they update the state in response.

Selectors

Selectors are functions that know how to extract specific pieces of information from a store state value. As an application grows bigger, this can help avoid repeating logic as different parts of the app need to read the same data. For this purpose, there is a hook called useSelector. The selector will be called with the entire Redux store state as its only argument. The selector may return any value as a result, including directly returning a value that was nested inside the state, or deriving new values. The return value of the selector will be used as the return value of the useSelector() hook:

type RootState = ReturnType<typeof store.getState>

const count = useSelector(state: RootState => {
return state.counter.value
})

RootState is the type of whole application’s state which is the type of store.getState()’s return value.

Ok, Great! 🥳

Now you know the basic and main features of Redux in a React project.

Now let’s implement a simple search engine to learn Redux in practice. 🔥

Definition of The Project (Search Engine)

We have some bus Ids ranging from 1 to 10. These buses move from an origin to a destination in a specific day number. We search (filter) between all available buses and return the bus ids which match the search. I won’t pay attention to the UI and my focus is in Redux. The UI is like this:

There are three fields at the top, Origin, Destination and the Day. The search result will appear at the bottom as a table. It also shows the number of found results.
The UI of the search engine

Project Introduction

First, the project is available here.

Next, there are some fake results. The type of each result is:

type resultType = {
busId: number,
origin: string,
destination: string,
day: number,
}

And the type of all results is:

type resultsType = resultType[]

Example of a fake result:

const results: resultsType = [
{
"busId": 7,
"origin": "Claremont",
"destination": "Calistoga",
"day": 11
},
{
"busId": 8,
"origin": "Compton",
"destination": "Barstow",
"day": 26
}
]

You can find used fake results here.

There is also a fake results generator which is written in Python if you are interested:

import json
import numpy as np

np.random.seed(42)
cities = ['Alameda', 'Alhambra', 'Anaheim', 'Antioch', 'Arcadia', 'Bakersfield', 'Barstow', 'Belmont', 'Berkeley',
'Beverly Hills', 'Brea', 'Buena Park', 'Burbank', 'Calexico', 'Calistoga', 'Carlsbad', 'Carmel', 'Chico',
'Chula Vista', 'Claremont', 'Compton', 'Concord', 'Corona', 'Coronado', 'Costa Mesa', 'Culver City',
'Daly City']
class Bus:
def __init__(self):
self.busId = np.random.randint(1, 11)
self.origin = np.random.choice(cities)
self.destination = np.random.choice(cities)
self.day = np.random.randint(1, 31)

buses = []
for i in range(100):
buses.append(Bus())

with open('data.json', 'w') as f:
json.dump(buses, f, indent=4, default=lambda x: x.__dict__)

In the continuation of the introduction, our project has two main components, The first one is SearchInput which is containing our inputs and search button. The next one is SearchResult which is showing the number of found results and the results, itself.

Preparing a React Project

First, open the directory in which you want to create the project. Then run the below command to create the React project with TypeScript:

npx create-react-app redux-tutorial --template typescript

Then run cd redux-tutorial to open the project directory. After, run the below commands to install Redux in your project:

npm install @reduxjs/toolkit
npm install react-redux

At the end open the redux-tutorial directory with any IDE or editor you want.

If you wish to run the project, easily run the below command:

npm start

Implementing The Project

Result

We put our fake results and the corresponding types in the results.ts file in the redux-tutorial/src/components/searchResult directory. The defined types are:

export type resultType = {
busId: number,
origin: string,
destination: string,
day: number,
}

export type resultsType = resultType[]

You can download the complete file here.

App

As I said, There are two main components named SearchInput and SearchResult. So we change the App component as below, easily:

import React from 'react';
// import {SearchInput} from "./components/searchInput/SearchInput";
// import {SearchResult} from "./components/searchResult/SearchResult";


function App() {
return (
<div className="App">
{/*<SearchInput/>*/}
{/*<SearchResult/>*/}
</div>
);
}

export default App;

We comment on those lines because those components do not exist, now. We will uncomment them.

Store

We need to define Redux’s only store. It’s better to create a file named store.ts in redux-tutorial/src/ . So the file redux-tutorial/src/store.ts is like this:

import {configureStore} from "@reduxjs/toolkit";
import {TypedUseSelectorHook, useDispatch, useSelector} from "react-redux";


export const store = configureStore({
reducer: {
}
})

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
export const useAppDispatch: () => AppDispatch = useDispatch

The two first lines are imports. Then we configure and create a store easily.

Because we are writing TypeScript, we need to use typed useSelector and useDispatch. So we need RootState and AppDispatch types. The RootState is the type of return value of store.getState(). The AppDispatch is the type of store.dispatch. In the end, using TypedUseSelectorHook which is defined at react-redux package, we define useAppSelector and useAppDispatch. useAppSelector is the type of useSelector and useAppDispatch is the type of useDispatch. We’ll use useAppSelector and useAppDispatch instead of their originals in our project.

Then open redux-tutorial/src/index.tsx. We need to wrap theApp tag in the Provider tag which is available in the react-redux package in order to use Redux.

To do so, first import the below lines:

import {Provider} from "react-redux";
import {store} from "./store";

Then this part of the index.tsx has changed:

root.render(
<React.StrictMode>
<Provider store={store}>
<App/>
</Provider>
</React.StrictMode>
);

As you see, we need to pass the store to the Provider tag. This is needed to use Redux.

Search Input

First, create redux-tutorial/src/components/searchInput directory. We created a components folder to have a cleaner project!

Then create SearchInput.tsx file in the above directory. We need three labels, three inputs for each label, and a search button in it:

import React from "react";


export function SearchInput() {
return (
<div id={'search-input-div'}>
<label htmlFor='origin'>Origin</label>
<input type='text' id='origin'/>

<label htmlFor='destination'>Destination</label>
<input type='text' id='destination'/>

<label htmlFor='day'>Day</label>
<input type='number' min={1} max={30} step={1} id={'day'}/>

<button id={'search-button'}>
Search
</button>
</div>
)
}

Now you can add the SearchInput component to your App component (Just uncomment 2 corresponding lines).

Ok, well done. Now we are going to dive into Redux.

One of our React states, or in other words, one of our Redux slices is the input of the origin field. We need to create a slice for it. To do so, we create originInputSlice.ts file in the same directory. It contains:

import {createSlice, PayloadAction} from "@reduxjs/toolkit";


export const originInputSlice = createSlice({
name: 'originInput',
initialState: '',
reducers: {
setOriginInput: (state, action: PayloadAction<string>) => action.payload
}
})

export const originInputReducer = originInputSlice.reducer

We created a slice, with our desired name. The initial state is an empty string. Pay attention that the type of the initial state is a string which is a primitive type. There’s a point about it. We need just one reducer which gets the string and set the state equal to the string. The input string is embedded at action.payload. We return the new state, so the state sets with the function return value.

The point is if you use state = action.payload to set the new state, then the state’s reference changes, so Redux can’t track your change and the new state does not apply. If you do, you may get the A case reducer on a non-draftable value must not return undefined error.

The other point is if you define the originInputSlice in SearchInput.tsx file, you may get Cannot access uninitialized variable error. So try to define each slice in a separate file.

At last, we export the reducer to use it in the store and we’ll export the originInputSlice to use its actions. It’s good to see here about exporting and using slices.

The first part of store.ts changes as below (originInputReducer added):

import {configureStore} from "@reduxjs/toolkit";
import {TypedUseSelectorHook, useDispatch, useSelector} from "react-redux";
import {originInputReducer} from "./components/searchInput/originInputSlice";


export const store = configureStore({
reducer: {
originInput: originInputReducer,
}
})

Let’s go back to the SearchInput.tsx. We’ll want to use originInputSlice there. It changes as below:

import React from "react";
import {useAppDispatch, useAppSelector} from "../../store";
import {originInputSlice} from "./originInputSlice";


export function SearchInput() {
const dispatch = useAppDispatch()

const originInput = useAppSelector(state => state.originInput)

return (
<div id={'search-input-div'}>
<label htmlFor='origin'>Origin</label>
<input type='text' id='origin' value={originInput} onChange={e =>
dispatch(originInputSlice.actions.setOriginInput(e.target.value))}/>

<label htmlFor='destination'>Destination</label>
<input type='text' id='destination'/>

<label htmlFor='day'>Day</label>
<input type='number' min={1} max={30} step={1} id={'day'}/>

<button id={'search-button'}>
Search
</button>
</div>
)
}

First, we added dispatch so we can dispatch our action in the project. Next, we define originInput to get the user’s input because we want to search using it. Then, obviously, we set the input value equal to originInput. In the next step, in the onChange attribute, we run dispatch on originInputSlice.actions.setOriginInput action. The input of the action, or in other words, the payload of the action, is the user’s input.

Done! Step into the next Redux Slice.

The next Slice is destinationInputSlice. Create destinationInputSlice.ts file in redux-tutorial/src/components/searchInput directory. The file is:

import {createSlice, PayloadAction} from "@reduxjs/toolkit";


export const destinationInputSlice = createSlice({
name: 'destinationInput',
initialState: {
value: ''
},
reducers: {
setDestinationInput: (state, action: PayloadAction<string>) => {
state.value = action.payload
}
}
})

export const destinationInputReducer = destinationInputSlice.reducer

Overall it’s the same as originInputSlice. There are some differences between the initial state and its reducer.

Now, the initial state is an object. So we can mutate the state instead of returning the new one. The other approach is to return the new state from the reducer, but this isn’t suggested:

return {
...state,
value: action.payload
}

The last input slice is dayInputSlice. The redux-tutorial/src/components/searchInput/dayInputSlice.ts contains:

import {createSlice, PayloadAction} from "@reduxjs/toolkit";


export const dayInputSlice = createSlice({
name: 'dayInput',
initialState: 1,
reducers: {
setDayInput: (state, action: PayloadAction<number>) => action.payload
}
})

export const dayInputReducer = dayInputSlice.reducer

It is the same as two other slices. I use it to just see the use of number type.

Finally input slices end!

We can add these new slices to the store as below:

export const store = configureStore({
reducer: {
originInput: originInputReducer,
destinationInput: destinationInputReducer,
dayInput: dayInputReducer,
}
})

Let’s come back to the SearchInput.tsx. It is the same as below until now:

import React from "react";
import {useAppDispatch, useAppSelector} from "../../store";
import {originInputSlice} from "./originInputSlice";
import {destinationInputSlice} from "./destinationInputSlice";
import {dayInputSlice} from "./dayInputSlice";


export function SearchInput() {
const dispatch = useAppDispatch()

const originInput = useAppSelector(state => state.originInput)
const destinationInput = useAppSelector(state => state.destinationInput.value)
const dayInput = useAppSelector(state => state.dayInput)

return (
<div id={'search-input-div'}>
<label htmlFor='origin'>Origin</label>
<input type='text' id='origin' value={originInput} onChange={e =>
dispatch(originInputSlice.actions.setOriginInput(e.target.value))}/>

<label htmlFor='destination'>Destination</label>
<input type='text' id='destination' value={destinationInput} onChange={e =>
dispatch(destinationInputSlice.actions.setDestinationInput(e.target.value))}/>

<label htmlFor='day'>Day</label>
<input type='number' min={1} max={30} step={1} id={'day'} value={dayInput} onChange={e =>
dispatch(dayInputSlice.actions.setDayInput(Number(e.target.value)))}/>

<button id={'search-button'}>
Search
</button>
</div>
)
}

In the file, we added destinationInput and dayInput. We also add value and onChang attributes for the two other input tags. The way of adding new codes is as before and there isn’t any new point. Just please pay attention when we want to get destinationInput because it was an object, we wrote state.destinationInput.value, not state.destinationInput!

Now we have just one more slice left. A slice to store search results. create redux-tutorial/src/components/searchResult/searchResultSlice.ts file. We’ll define searchResultSlice in it. The file looks like this:

import {createSlice, PayloadAction} from "@reduxjs/toolkit";
import {resultsType} from "./results";


export const searchResultSlice = createSlice({
name: 'searchResult',
initialState: [] as resultsType,
reducers: {
setResults: (state: resultsType, action: PayloadAction<resultsType>) => action.payload
}
})

export const searchResultReducer = searchResultSlice.reducer

As you can see, the initial state is empty and its type is resultsType. The payload of setResults reducer is a complete array of results, or in other words, its type as we wrote in the code, is resultsType. There are two correct approaches to setting the new state and one wrong approach:

  • Correct approach 1: Returning the new state. It means returning the action.payload same as what we did in the above code.
  • Wrong approach 1: Setting the state equals to action.payload:
setResults: (state: resultsType, action: PayloadAction<resultsType>) => {
state = action.payload
}

As you know this way changes the state reference without returning the new reference. So The change is untraceable and Redux can’t update the UI.

  • Correct approach 2: We need to don’t change the reference. So we can delete all items of the state array, and then push the new items in it (This approach is just told for learning, and obviously it’s not good to use.):
setResults: (state: resultsType, action: PayloadAction<resultsType>) => {
for (let i = 0; i < state.length; i++) {
state.pop()
}

for (let i = 0; i < action.payload.length; i++) {
state.push(action.payload[i])
}
}

Now the store.ts looks like this:

import {configureStore} from "@reduxjs/toolkit";
import {TypedUseSelectorHook, useDispatch, useSelector} from "react-redux";
import {originInputReducer} from "./components/searchInput/originInputSlice";
import {destinationInputReducer} from "./components/searchInput/destinationInputSlice";
import {dayInputReducer} from "./components/searchInput/dayInputSlice";
import {searchResultReducer} from "./components/searchResult/searchResultSlice";


export const store = configureStore({
reducer: {
originInput: originInputReducer,
destinationInput: destinationInputReducer,
dayInput: dayInputReducer,
searchResult: searchResultReducer,
}
})

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
export const useAppDispatch: () => AppDispatch = useDispatch

The store.ts completed.

Come back to SearchInput.tsx again. Two things are left. One of those things is a search function, and the other one is onClick attribute for the button tag.

Let’s first implement the search function. It’s so easy. Just a filter on all results as below:

import {results, resultsType} from "../searchResult/results";

function search(originInput: string, destinationInput: string, dayInput: number): resultsType {
return results.filter(result => result.origin.toLowerCase() === originInput.toLowerCase() &&
result.destination.toLowerCase() === destinationInput.toLowerCase() && result.day === dayInput)
}

We search by ignoring case equality between what the user entered and our all results.

Then the onClick attribute gets a function that dispatches the searchResultSlice.actions.setResults action. The function is:

() => dispatch(searchResultSlice.actions.setResults(search(originInput, destinationInput, dayInput)))

The input of the action is our search result.

So the final code of SearchInput.tsx is:

import React from "react";
import {useAppDispatch, useAppSelector} from "../../store";
import {originInputSlice} from "./originInputSlice";
import {destinationInputSlice} from "./destinationInputSlice";
import {dayInputSlice} from "./dayInputSlice";
import {searchResultSlice} from "../searchResult/searchResultSlice";
import {results, resultsType} from "../searchResult/results";


export function SearchInput() {
const dispatch = useAppDispatch()

const originInput = useAppSelector(state => state.originInput)
const destinationInput = useAppSelector(state => state.destinationInput.value)
const dayInput = useAppSelector(state => state.dayInput)

function search(originInput: string, destinationInput: string, dayInput: number): resultsType {
return results.filter(result => result.origin.toLowerCase() === originInput.toLowerCase() &&
result.destination.toLowerCase() === destinationInput.toLowerCase() && result.day === dayInput)
}

return (
<div id={'search-input-div'}>
<label htmlFor='origin'>Origin</label>
<input type='text' id='origin' value={originInput} onChange={e =>
dispatch(originInputSlice.actions.setOriginInput(e.target.value))}/>

<label htmlFor='destination'>Destination</label>
<input type='text' id='destination' value={destinationInput} onChange={e =>
dispatch(destinationInputSlice.actions.setDestinationInput(e.target.value))}/>

<label htmlFor='day'>Day</label>
<input type='number' min={1} max={30} step={1} id={'day'} value={dayInput} onChange={e =>
dispatch(dayInputSlice.actions.setDayInput(Number(e.target.value)))}/>

<button id={'search-button'} onClick={() =>
dispatch(searchResultSlice.actions.setResults(search(originInput, destinationInput, dayInput)))}>
Search
</button>
</div>
)
}

Hooray! This part is finished. Let’s step into Search Result.

Search Result

First, create redux-tutorial/src/components/searchInput/SearchResult.tsx file. We want to show our results in a form of a table.

Then we need our results here, so we’ll use useAppSelector to get the results:

import React from "react";
import {useAppSelector} from "../../store";
import {resultsType} from "./results";


export function SearchResult() {
const searchResults: resultsType = useAppSelector(state => state.searchResult)

return (
<div id={'search-result-div'}>
<table>
<thead>
<tr>
<th>Bus Id</th>
<th>Origin</th>
<th>Destination</th>
<th>Day</th>
</tr>
</thead>
<tbody>
{searchResults.map((result, index) => {
return (
<tr key={index}>
<td>{result.busId}</td>
<td>{result.origin}</td>
<td>{result.destination}</td>
<td>{result.day}</td>
</tr>
)
})}
</tbody>
</table>
</div>
)
}

We get our results in the searchResults variable. Then we show the results in a table to be more beautiful and informative.

Now you can add the SearchResult component to your App component (Just uncomment 2 corresponding lines).

I want to have the number of found results in a variable named numberOfResults. There are two approaches:

  • We can use another useAppSelector same as below:
const numberOfResults = useAppSelector(state => state.searchResult.length)

This approach is used when we have the same complex logic many times in our code.

  • We also can use the length attribute on the searchResults variable:
const numberOfResults = searchResults.length

So, at last SearchResult.tsx is:

import React from "react";
import {useAppSelector} from "../../store";
import {resultsType} from "./results";


export function SearchResult() {
const searchResults: resultsType = useAppSelector(state => state.searchResult)
const numberOfResults = useAppSelector(state => state.searchResult.length)

return (
<div id={'search-result-div'}>
<h2>{numberOfResults} results found</h2>
<table>
<thead>
<tr>
<th>Bus Id</th>
<th>Origin</th>
<th>Destination</th>
<th>Day</th>
</tr>
</thead>
<tbody>
{searchResults.map((result, index) => {
return (
<tr key={index}>
<td>{result.busId}</td>
<td>{result.origin}</td>
<td>{result.destination}</td>
<td>{result.day}</td>
</tr>
)
})}
</tbody>
</table>
</div>
)
}

We added numberOfResults as a h2 tag.

CSS

If you want some CSS to make your search engine more beautiful, you can download App.css, SearchInput.css, and SearchResult.css stylesheets, and import them to App.tsx, SearchInput.tsx, and SearchResult.tsx, respectively.

Final Word

Now, You have a beautiful and powerful search engine. 🎉🔥

If you want to get deep into Redux, you can use Redux’s official document.

Hope you enjoyed this journey and learned Redux very well. If you have any problem or suggestion, I’ll get so happy if I hear from you.

--

--

Amir Mohammad Fakhimi
Amir Mohammad Fakhimi

Written by Amir Mohammad Fakhimi

I'm Computer Engineering student, now. I'm also interested in Software Engineering and AI. My website is amfakhimi.com

Responses (1)