Redux is one of the most popular state management libraries. It is simply a store that contains the state of the variables in your app. Redux creates a streamlined process to interact with the store so that components will not just update or read the store randomly. It integrates well with other javascript libraries like React, Vue or Angular. While creating complex applications, managing the state among several components is challenging. Redux aims at solving this exact problem.
Redux Terms and Concepts
Let us have a look at some of the terms and concepts you'll need to know to use Redux.
State Management
Let's start by looking at a small React counter component. It tracks a number in component state, and increments the number when a button is clicked:
function Counter() {
// State: a counter value
const [counter, setCounter] = useState(0)
// Action: code that causes an update to the state when something happens
const increment = () => {
setCounter(prevCounter => prevCounter + 1)
}
// View: the UI definition
return (
<div>
Value: {counter} <button onClick {increment}>Increment</button>
</div>
)
}
The app contains the following parts:
- The state, the source of truth that drives our app.
- The view, a representation of the UI based on the current state.
- The actions, the events that occur in the app based on user input, and trigger updates in the state
State describes the condition of the app at a specific point in time. The UI is rendered based on that state.
When something happens (such as a user clicking a button), the state is updated based on what occurred.The UI re-renders based on the new state.
However, it can be tricky when we have multiple components that need to share and use the same state. Sometimes this can be solved by "lifting state up" to parent components, but even that is not the most clean solution in all scenarios.
Another way to solve this is to extract the shared state from the components, and put it into a centralised location outside the component tree. Any component can access the state or trigger actions, no matter where they are in the tree. This is the basic idea behind Redux.
Immutability
JavaScript objects and arrays are all mutable by default. In order to update values immutably, your code must make copies of existing objects/arrays, and then modify the copies. We can do this by hand using JavaScript's array/object spread operators, as well as array methods that return new copies of the array instead of mutating the original array.
Terminology
Below are some of the important react terms that you need to be familiar with.
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 "tasks". 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.
A typical action object might look like this:
const addTaskAction = {
type: 'tasks/tasksAdded',
payload: 'Read a book'
}
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:
const addTask = task => {
return {
type: 'tasks/tasksAdded',
payload: task
}
}
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.
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.
- They must not do any asynchronous logic, calculate random values, or cause other "side effects".
Here's a small example of a reducer, showing the steps that each reducer should follow:
const initialState = { value: 0 }
function counterReducer(state = initialState, action) {
// Check to see if the reducer cares about this action
if (action.type === 'tasks/addTasks') {
// 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
}
Store
The current Redux application 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: counterReducer })
console.log(store.getState())
// {value: 0}
Dispatch
The Redux store has a method called dispatch. The only way to update the state is to call store.dispatch() and pass in an action object. The store will run its reducer function and save the new state value inside, and we can call getState() to retrieve the updated value:
store.dispatch({ type: 'counter/increment' })
console.log(store.getState())
// {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.
We typically call action creators to dispatch the right action.
const increment = () => {
return {
type: 'counter/increment'
}
}
store.dispatch(increment())
console.log(store.getState())
// {value: 2}
Selectors
Selectors are functions that extract specific pieces of information from a store state value. As an application grows bigger, this can help avoid repeating logic.
const selectCounterValue = state => state.value
const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2
Redux Application Data Flow
How Redux works?
Redux has a centralised store to store all the states of the applications that are easily accessible by all the app components. Each component does not have to pass the props around within the component tree to access those states, as displayed below.
To trigger the updation of the state, a user must start interacting with the view. The view will dispatch a required action if you want to update a state. The action will reach the reducers and update the state in the store as per the action. A view is subscribed to the store that will listen about the change in any state. Changes are notified using the subscription method that will reflect in the UI.
Summary
We have discussed the major features of Redux and why Redux is beneficial to your app. While Redux has its benefits, that does not mean you should go about adding Redux in all of your apps. Your application might still work well without Redux.
One major benefit of Redux is that it allows you to manage your app's state in a single place and keep changes in your app more predictable and traceable.
Top comments (0)