From Novice to Pro: Master Redux with Typescript - Level 3
Master how Redux can be used as a singleton state manager instead of Context API, how redux-toolkit reduces boilerplate code.

Turjoy • February 25, 2024
Goals
In this article we will explore how redux can be used as a global state manager.
Here's the full app from outside....
Background Knowledge
This article skips concepts like basic react native setup and react navigation.
To understand those you can explore past articles:
What is Redux?
Redux is a global state management library.
It can be used with any Javascript library or framework.
It has five basic parts:
- Store
- Action
- Reducer
- Selector
- Dispatch
Store
Redux Store is the central place where all global data is stored.
Since we deal with state of the data, we say global state is stored.
It is a single source of truth for the application.
Actions
An action is a plain JavaScript object that has a type field.
Action describes an event that's about to happen.
It can pass data to the event too as 'payload'.
It mentions the event using 'type'.
Like...
const addTodoAction = {
type: 'todos/todoAdded',
payload: 'Buy milk'
}
Reducers
A reducer is a function that receives the current state and an action object.
Based on those it decides how to update the state if necessary.
Then it returns the new state.
(state, action) => newState
So reducer is an event listener which handles events based on the received action (event) type.
Selector
For a component to have access to the data it needs from store, selectors are used.
const selectCounterValue = state => state.value
const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
Dispatch
To update the store, redux provides store.dispatch().
It gets the 'type' and 'payload' from actions.
Then it tells the reducer make updates based on those.

React Redux
We can get the current data from the store, using store.getState().
But what happens when the state updates in real time?
Can the UI detect the change and update itself?
No.
So we need react-redux to let UI know whenever there's a change in the store state.
It lets us skip writing a lot of Observer Pattern boilerplate code.
Resource about Observer Pattern is provided in resources to read later.
Why use Redux?
Sometimes handling multiple states from multiple components efficiently, can become challenging when the application grows in size.
Redux comes to save us in this scenario.
It takes responsibility for all those kind of data.
Thus,
- improving code consistency
- providing better testable codes
- acting as a single source of truth
How to use Redux like a Pro
Here's the code that we will be using as reference.
After you are done understanding, you can implement yourself in this code.
Just replace context API with Redux-toolkit.
Using redux-toolkit
Redux asks a lot of boilerplate code.
The dispatch needs those to determine the reducer function.
The redux-toolkit provides special hooks that makes the boilerplate code unnecessary.
Like if you use plain redux, you need to define actions, reducers separately.
You would have to define in actions what the reducer must do after dispatch.
But with redux-toolkit you define reducer functions using 'createSlice()' hook.
Each reducer functions will have their respective actions generated.
Sometimes the action involves some logic before reducer does its job.
Like if the action handles API call.
We can define an action and add to the reducer.
This will be covered in the next article when we will learn to use REST APIs.
Folder structure
In the src directory (or root) we create a 'store' folder.
Then create the following structure of files and folders.
Defining reducers
We provide an initial state of the data we need.
Then we define the types of reducer functions we want on a data.
import {PayloadAction, createSlice} from '@reduxjs/toolkit';
import {Task} from '../../../types';
interface todoSliceState {
data: Task[];
}
// Define the initial state using that type
const initialState: todoSliceState = {
data: [],
};
interface updateProps {
index: number;
description: string;
}
export const todoSlice = createSlice({
name: 'todo',
initialState,
reducers: {
addTodo: (state, action: PayloadAction<string>) => {
state.data = [
...state.data,
{pending: true, description: action.payload},
];
},
updateTodoDetails: (state, action: PayloadAction<updateProps>) => {
const updatedTasks = [...state.data];
updatedTasks[action.payload.index].description =
action.payload.description;
state.data = updatedTasks;
},
updateTodoStatus: (state, action: PayloadAction<number>) => {
const updatedTasks = [...state.data];
updatedTasks[action.payload].pending =
!updatedTasks[action.payload].pending;
state.data = updatedTasks;
},
deleteTodo: (state, action: PayloadAction<number>) => {
const updatedTasks = [...state.data];
updatedTasks.splice(action.payload, 1);
state.data = updatedTasks;
},
},
});
// Other code such as selectors can use the imported `RootState` type
export default todoSlice.reducer;
Getting action functions
We get the action functions from the created slice.
import {todoSlice} from '../reducers/todo';
export const {addTodo, updateTodoStatus, updateTodoDetails, deleteTodo} =
todoSlice.actions;
Configuring the store
Then with the reducers in place, we configure the store.
Since we are using Typescript, we also get the types we will be using.
RootState and AppDispatch.
import {configureStore} from '@reduxjs/toolkit';
import todo from './reducers/todo';
const store = configureStore({
reducer: {
todo,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
Defining the hooks
We need two hooks.
One to get data (lets name it useAppSelector).
And other to dispatch actions to invoke reducers (lets name it useAppDispatch).
import {useDispatch, useSelector} from 'react-redux';
import type {TypedUseSelectorHook} from 'react-redux';
import type {RootState, AppDispatch} from '..';
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
Selectors
We also define selector to reuse code in different parts of the App.
import {useAppSelector} from '../hooks/redux-hooks';
const useTodoData = () => {
const todos = useAppSelector(state => state.todo.data);
return todos;
};
export default useTodoData;
We would have to repeat this logic every time we want to get data from store.
Instead we create a selector function that provides us with the updated store values.
Provider
Finally we encapsulate the <App/> with Provider form react-redux.
import React from 'react';
import {PaperProvider} from 'react-native-paper';
import AppNavigator from './src/navigation/AppNavigator';
import {Provider} from 'react-redux';
import store from './src/store';
function App(): React.JSX.Element {
return (
<PaperProvider>
<Provider store={store}>
<AppNavigator />
</Provider>
</PaperProvider>
);
}
Invoke current data
We simply call the useTodoData() to get current state.
const tasks = useTodoData();
Update state
We invoke the useDispatch() function and pass the action.
const CustomCheckBox = ({item, index}: {item: Task; index: number}) => {
const dispatch = useAppDispatch();
return (
<Checkbox.Android
status={!item.pending ? 'checked' : 'unchecked'}
onPress={() => dispatch(updateTodoStatus(index))}
color={Colors.text}
/>
);
};
Design concepts exploited
Redux - Singleton
The Singleton pattern is a creational design pattern that states:
- a class has only one instance
- provides a global point of access to that instance.
Redux is based on it.
Redux follows the principles of a single immutable state tree.
Redux provides global access to itself.
React-redux - Observer
The Observer Pattern defines a one-to-many dependency between objects.
When one object changes state, all of its dependents are notified.
React-Redux is a UI binding library based on that.
Last Words
Sometimes using redux is a overkill.
Like we were happy with just local storage if there was one screen.
It's also unnecessary if multiple components don't need the same data or functionality.
I have showed here a simplified reducer with everything required for scalability.
Use it to make complex ones with multiple reducers.
Keep me posted of your progress.