1. Redux¶
Predictable state container designed to help you write JavaScript apps that behave consistently across client, server, and native environments and are easy to test.
Mostly used as a state management tool with React, it can also be used with other frameworks or libraries.
1.1. What problem does it solve ?¶
React for example only flows in one direction, so if you want to pass data(props) from one component between multiple components, you can only pass it to one. You can solve it by moving up the state
in React components. But it can get very complex which at some point it will be very hard to justify moving up the state to a higher component.
Redux solves it by storing data separately, which can be accessed from each component independently
1.2. Setup¶
npm install redux react-redux
1.3. STORE¶
Holds all the information, like a global pool of data
1.4. Example¶
import { createStore } from "redux";
//STORE -> GLOBALIZED STATE
//ACTION INCREMENT
const increment = () => {
return {
type: "INCREMENT",
};
};
const decrement = () => {
return {
type: "DECREMENT",
};
};
//REDUCER -> CHECKS WHICH ACTION WAS USED, TO KNOW HOW TO MODIFY THE STORE
const counter = (state = 0, action) => {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
}
};
let store = createStore(counter);
//Display in the console
store.subscribe(() => console.log(store.getState()));
//DISPATCH -> WAY OF EXECUTING THE ACTION
store.dispatch(increment());
store.dispatch(decrement());
store.dispatch(decrement());
1.5. Actions¶
Events which send data from your application to your redux store.
Actions are sent using store.dispatch()
method. Actions are plain JSON, and they must have a type
property to indicate the type of action to be carried out. They must also have a payload
that contains the information that should be worked on by the action.
Here is an example of Action creator
const setLoginStatus = (name, password) => {
return {
type: "LOGIN",
payload: {
username: "foo",
password: "bar",
},
};
};
1.6. Reducers¶
Pure functions that take the current state of an application, perform an action and return a new state. Those states are stored as objects, and they specify how the state of an application changes in response to an action sent to the store.
const LoginComponent = (state = initialState, action) => {
switch (action.type) {
// This reducer handles any action with type "LOGIN"
case "LOGIN":
return state.map((user) => {
if (user.username !== action.username) {
return user;
}
if (user.password == action.password) {
return {
...user,
login_status: "LOGGED IN",
};
}
});
default:
return state;
}
};
Reducers take the previous state of the app and return a new state based on the action passed to it.
As pure functions they don't change the data in the object passed to them or preform any side effects in the application. Given the same objects, they should always produce the same result
1.7. Store¶
Holds the application state. There is only one store in any Redux application. You can access the state stored, update the state, and register or unregister listeners via helper methods
Creating store
1.8. Why use Redux?¶
- no need for states to e lifted up
- easier to trace which action causes any change
- simplifies the app
- easier to maintain
- state is predictable
- if the same state and action is passed into reducer it will always produce the same result
- state is immutable and is never changed
- can do infinite undo or redo, also possible to do time travel
- Debugging is easy in Redux
- Easy to test
- since it's using pure functions
1.9. Thunk Explained¶
Redux Thunk is middleware that allows you to return functions, rather than just actions, within Redux.
Use case: This middleware can handle actions that might not be synchronous, for example, using axios to send a GET request. Redux Thunk allows us to dispatch those actions asynchronously and resolve each promise that gets returned.
1.9.1. Installation¶
npm install redux-thunk --save
It will also need to be set up
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers/index";
const store = createStore(rootReducer, applyMiddleware(thunk));
1.9.2. How to use¶
After including Thunk with applyMiddleware(thunk)
, you can start dispatching actions asynchronously.
Simple increment counter
const INCREMENT_COUNTER = "INCREMENT_COUNTER";
function increment() {
return {
type: INCREMENT_COUNTER,
};
}
function incrementAsync() {
return (dispatch) => {
setTimeout(() => {
// You can invoke sync or async actions with `dispatch`
dispatch(increment());
}, 1000);
};
}
You can also set up success and failure actions after polling and API
const GET_CURRENT_USER = "GET_CURRENT_USER";
const GET_CURRENT_USER_SUCCESS = "GET_CURRENT_USER_SUCCESS";
const GET_CURRENT_USER_FAILURE = "GET_CURRENT_USER_FAILURE";
const getUser = () => {
return (dispatch) => {
//nameless functions
// Initial action dispatched
dispatch({ type: GET_CURRENT_USER });
// Return promise with success and failure actions
return axios.get("/api/auth/user").then(
(user) => dispatch({ type: GET_CURRENT_USER_SUCCESS, user }),
(err) => dispatch({ type: GET_CURRENT_USER_FAILURE, err }),
);
};
};
2. Redux Toolkit¶
Redux Toolkit package is intended to be the standard way to write Redux logic
2.1. What's included¶
Redux Toolkit includes these API's:
configureStore()
: wrapscreateStore
to provide simplified configuration options and good defaults. It can automatically combine your slice reducers, adds whatever Redux middleware you supply, includesredux-thunk
by default, and enables use of the Redux DevTools Extension.createReducer()
: that lets you supply a lookup table of action types to case reducer functions, rather than writing switch statements. In addition, it automatically uses the immer library to let you write simpler immutable updates with normal mutative code, likestate.todos[3].completed = true
.createAction()
: generates an action creator function for the given action type string. The function itself hastoString()
defined, so that it can be used in place of the type constant.createSlice()
: accepts an object of reducer functions, a slice name, and an initial state value, and automatically generates a slice reducer with corresponding action creators and action types.createAsyncThunk
: accepts an action type string and a function that returns a promise, and generates a thunk that dispatchespending/fulfilled/rejected
action types based on that promisecreateEntityAdapter
: generates a set of reusable reducers and selectors to manage normalized data in the store- The
createSelector
utility from the Reselect library, re-exported for ease of use
2.2. Installation¶
2.2.1. With Create React App¶
2.2.2. Existing App¶
2.3. Basic Tutorial: Counter Application¶
2.4. Library¶
2.4.1. configureStore
¶
Can be used in place of createStore
Accepts a single configuration object parameter, with the following options:
type ConfigureEnhancersCallback = (
defaultEnhancers: StoreEnhancer[]
) => StoreEnhancer[]
interface ConfigureStoreOptions<
S = any,
A extends Action = AnyAction,
M extends Middlewares<S> = Middlewares<S>
> {
/**
* A single reducer function that will be used as the root reducer, or an
* object of slice reducers that will be passed to `combineReducers()`.
*/
reducer: Reducer<S, A> | ReducersMapObject<S, A>
/**
* An array of Redux middleware to install. If not supplied, defaults to
* the set of middleware returned by `getDefaultMiddleware()`.
*/
middleware?: ((getDefaultMiddleware: CurriedGetDefaultMiddleware<S>) => M) | M
/**
* Whether to enable Redux DevTools integration. Defaults to `true`.
*
* Additional configuration can be done by passing Redux DevTools options
*/
devTools?: boolean | DevToolsOptions
/**
* The initial state, same as Redux's createStore.
* You may optionally specify it to hydrate the state
* from the server in universal apps, or to restore a previously serialized
* user session. If you use `combineReducers()` to produce the root reducer
* function (either directly or indirectly by passing an object as `reducer`),
* this must be an object with the same shape as the reducer map keys.
*/
preloadedState?: DeepPartial<S extends any ? S : S>
/**
* The store enhancers to apply. See Redux's `createStore()`.
* All enhancers will be included before the DevTools Extension enhancer.
* If you need to customize the order of enhancers, supply a callback
* function that will receive the original array (ie, `[applyMiddleware]`),
* and should return a new array (such as `[applyMiddleware, offline]`).
* If you only need to add middleware, you can use the `middleware` parameter instead.
*/
enhancers?: StoreEnhancer[] | ConfigureEnhancersCallback
}
function configureStore<S = any, A extends Action = AnyAction>(
options: ConfigureStoreOptions<S, A>
): EnhancedStore<S, A>
2.4.2. Usage¶
2.4.2.1. Basic Example¶
import { configureStore } from "@reduxjs/toolkit";
import rootReducer from "./reducers";
const store = configureStore({ reducer: rootReducer });
// The store now has redux-thunk added and the Redux DevTools Extension is turned on
2.4.2.2. Full Example¶
import { configureStore } from "@reduxjs/toolkit";
// We'll use redux-logger just as an example of adding another middleware
import logger from "redux-logger";
// And use redux-batch as an example of adding enhancers
import { reduxBatch } from "@manaflair/redux-batch";
import todosReducer from "./todos/todosReducer";
import visibilityReducer from "./visibility/visibilityReducer";
const reducer = {
todos: todosReducer,
visibility: visibilityReducer,
};
const preloadedState = {
todos: [
{
text: "Eat food",
completed: true,
},
{
text: "Exercise",
completed: false,
},
],
visibilityFilter: "SHOW_COMPLETED",
};
const store = configureStore({
reducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
devTools: process.env.NODE_ENV !== "production",
preloadedState,
enhancers: [reduxBatch],
});
// The store has been created with these options:
// - The slice reducers were automatically passed to combineReducers()
// - redux-thunk and redux-logger were added as middleware
// - The Redux DevTools Extension is disabled for production
// - The middleware, batch, and devtools enhancers were composed together