Basics
All components states are stored in the global state. Components can read information from it, but cannot directly change something in it.
To change the global state, components create Actions objects and submit them to the Redux Store. Such operations are called dispatches.
The Redux Store in turn uses the Reducer function to update the state.
Reducer
This is a function that is used to store logic. Accepts state
and action
(simple object).
const reducer = (state = 0, action) => {
switch (action.type) {
case "INC":
return state + 1;
default:
return state;
}
};
let state = reducer(undefined, {});
state = reducer(state, { type: "INC" });
If state
is undefined
, then you need to return the original initialState
.
If the action
type is unknown, then you need to return state
unchanged.
Reducer must be a pure function! This means that:
- The return value depends only on the arguments (you can only work with arguments)
- The function has no side effects (changing external data or changing arguments)
Redux Store
The redux store's job is to coordinate data in a react app.
import { createStore } from "redux";
const reducer = (state = 0, action) => {
switch (action.type) {
case "INC":
return state + 1;
default:
return state;
}
};
// Creating store (by default — 0)
const store = createStore(reducer);
// Subscriptions on changes in store
store.subscribe(() => [console.log(`store changed: ${store.getState()}`)]);
// Handling new actions
store.dispatch({ type: "INC" });
store.dispatch({ type: "INC" });
Counter example:
import { createStore } from "redux";
const reducer = (state = 0, action) => {
switch (action.type) {
case "INC":
return state + 1;
case "DEC":
return state - 1;
default:
return state;
}
};
const store = createStore(reducer);
const dec = document.getElementById("dec");
const inc = document.getElementById("inc");
dec.addEventListener("click", () => store.dispatch({ type: "DEC" }));
inc.addEventListener("click", () => store.dispatch({ type: "INC" }));
const update = () => {
const counter = document.getElementById("counter");
counter.textContent = store.getState();
};
store.subscribe(update);
Together with the type of action in reduce through action, you can also transfer additional information to perform this very action:
import { createStore } from "redux";
const reducer = (state = 0, action) => {
const { type, payload } = action;
switch (type) {
case "INC":
return state + 1;
case "DEC":
return state - 1;
case "RND":
return state + payload;
default:
return state;
}
};
const store = createStore(reducer);
const dec = document.getElementById("dec");
const inc = document.getElementById("inc");
const rnd = document.getElementById("rnd");
dec.addEventListener("click", () => store.dispatch({ type: "DEC" }));
inc.addEventListener("click", () => store.dispatch({ type: "INC" }));
// ---
rnd.addEventListener("click", () => {
const payload = Math.floor(Math.random() * 10 + 1);
store.dispatch({ type: "RND", payload });
});
// ---
const update = () => {
const counter = document.getElementById("counter");
counter.textContent = store.getState();
};
store.subscribe(update);
Simplifying your code: Action Creator & bindActionCreators
Action Creator
Action Creator is a separate function that creates action objects, making it easy to write code:
const inc = () => ({ type: "INC" });
const dec = () => ({ type: "DEC" });
const rnd = (payload) => ({ type: "RND", payload });
bindActionCreators
bindActionCreator is binds any accepted actionCreator to dispatch:
const store = createStore(reducer);
const { dispatch } = store;
const incDispatch = () => dispatch(inc());
const decDispatch = () => dispatch(dec());
const rndDispatch = (payload) => dispatch(rnd(payload));
// bindActionCreator working like that
const bindActionCreator = (creator, dispatch) => (...args) => {
dispatch(creator(...args));
};
// Code is shorter now
const incDispatch = bindActionCreator(inc, dispatch);
const decDispatch = bindActionCreator(dec, dispatch);
const rndDispatch = bindActionCreator(rnd, dispatch);
Redux has its own bindActionCreators
function:
import { createStore, bindActionCreators } from "redux";
const incDispatch = bindActionCreator(inc, dispatch);
const decDispatch = bindActionCreator(dec, dispatch);
const rndDispatch = bindActionCreator(rnd, dispatch);
The difference from a self-written function is that bindActionCreators
can take an object of a set of actions as the first argument:
const { incDispatch, decDispatch, rndDispatch } = bindActionCreators(
{
incDispatch: inc,
decDispatch: dec,
rndDispatch: rnd,
},
dispatch
);
The keys of such an object will be the names of the functions that we want to receive. In the case of a set of actions, bindActionCreators returns an object with ready-made functions that can be destructed:
import { createStore, bindActionCreators } from "redux";
import reducer from "./reducer";
import * as actions from "./actions"; // Import all actions in object format
const update = () => {
const counter = document.getElementById("counter");
counter.textContent = store.getState();
};
const store = createStore(reducer);
const { dispatch } = store;
const { inc, dec, rnd } = bindActionCreators(actions, dispatch);
store.subscribe(update);
document.getElementById("inc").addEventListener("click", inc);
document.getElementById("dec").addEventListener("click", dec);
document.getElementById("rnd").addEventListener("click", () => {
const payload = Math.floor(Math.random() * 10);
rnd(payload);
});
Thanks for reading! ❤️
You can also check out my JavaScript Basic Type Conversions Cheat Sheet
If you have something to add, please, describe in the comments 👇🏻
Top comments (2)
Great info here thanks for sharing it.
Your'e welcome!