Day 11: Redux
Today I read about redux, some core concepts and when it is necessary to use. I shall create a small project for day 12 and 13 to explain the concepts that I have learnt.
Day 12: Redux (Basics)
Today I read the redux documentation and followed along with the tutorial provided. I will try to summarize all I learnt in two parts as it took two days to read through the documentation. I will then attempt to create a small project to practice what I learnt.
So what is redux? It is basically a library for managing application state. It serves as a centralized store for state that needs to be used by the entire application with certain rules that govern how state should be updated.
What makes redux different from Context api (another React state management feature covered in day 1 to 10) is that it has tooling that makes it easier when you have many state types in large applications, it makes it easier when working in teams because of its clear rules and has state slices to prevent a bloated store file, state slices help manage applications with multiple states.
Note: Using redux means writing more code and having to learn more, hence it does not need to be used for all applications. One should take the time to think about their application and the best tools to use.
Redux Terminology
Actions
This is a JavaScript object that defines something that happens in the application. It looks similar to the actions dispatched in context api
// action
const addUserAction = {
type: "users/userAdded",
payload: { username: "John", email: "john@example.com" },
};
// action creator
const addUser = (data) => {
return {
type: "users/userAdded",
payload: data,
};
};
Redux has action creators that creates and returns an action object, this prevents us from writing action objects by hand every time.
Reducers
A reducer is a function that receives the current state
of the application and an action
object, decides whether to update the state and returns the new state.
Reducers have rules that must be followed, some include not including operations that cause side effects like asynchronous logic, state must remain immutable and should only calculate the new state based on state
and action
arguments. These rules will be seen in action in future examples.
const counterReducer = (state = { value: 0 }, action) => {
switch (action.type) {
case "counter/increment":
return {
// state should be immutable
...state,
value: state.value + 1,
};
default:
break;
}
};
Store
The entire applications state lives in an object called the store. Redux store accepts a reducer function as its arguments
import { configureStore } from "@reduxjs/toolkit";
const store = configureStore({ reducer: counterReducer });
Dispatch
Dispatch is a function in redux that is used to update the state, it receives an action object as its arguments or action creators to trigger a specific state update.
// dispatch an action
store.dispatch({
type: "users/userAdded",
payload: { username: "John", email: "john@example.com" },
});
// dispatch an action creator
store.dispatch(addUser());
Selector
These are functions that extract specific pieces of information from a store. They are mostly used in the view part of the application. Multiple selectors can be used if you have many state slices.
const selectCounterValue = (state) => state.value;
const currentValue = selectCounterValue(store.getState());
Day 13 & 14: Redux Basics (Practice project)
To practice the concepts learnt in day 12, I will be refactoring the to-do list application made in day 1 that used the context api to use redux and redux tool kit.
The main action types in the to-do list will be adding a task, deleting a task, editing a task and toggling the state of a task as complete and incomplete. The original source code (using context api) is linked in day 1 of this challenge with the live demo.
To add Redux to the to-do list, I first created a store with Redux's configureStore
function that takes in reducer functions as its parameter and nested the entire App component with Provider from react-redux, with the store as a prop.
import { configureStore } from "@reduxjs/toolkit";
import tasksReducer from "../tasks/tasksSlice";
export default configureStore({
reducer: {
tasks: tasksReducer,
},
});
import store from "../src/app/store";
import { Provider } from "react-redux";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Next I created a tasksSlice file with the createSlice
function, this allows you to create a state slice making large state more managable. createSlice
accepts an initial state, an object full of reducer functions and a slice name. It creates action creators and action types that correspond to the reducers state.
import { createSlice } from "@reduxjs/toolkit";
const initialState = [];
export const taskSlice = createSlice({
name: "tasks",
initialState,
reducers: {
addTask(state, action) {
state.push(action.payload);
},
deleteTask(state, action) {
const { id } = action.payload;
console.log(id);
return state.filter((task) => task.id !== id);
},
// ... more actions available in source code provided
}
});
export const { addTask, deleteTask, editTask, toggleComplete } =
taskSlice.actions;
export default taskSlice.reducer;
Finally, to access the actions in the views, we use useDispatch
to call the action creators and pass in payloads. useSelector
is also used to read current state.
import { useSelector } from "react-redux";
// Get all tasks
const tasks = useSelector((state) => state.tasks);
import { useDispatch } from "react-redux";
const dispatch = useDispatch();
// add task on form submit by calling add task with a payload of id, name and completed
dispatch(
addTask({
id: nanoid(),
name: newTask,
completed: false,
})
);
This is a summary of what I did, the source code can be found here
Day 15: Asynchronous code in Redux
To demonstrate how to handle async code in redux, I will use the starwars api to fetch a list of films. Below is my redux store with an initial state holding an empty array of films and a loading state to show the state of requests.
import { createSlice } from "@reduxjs/toolkit";
export const LOADING_STATE = {
WAITING: "waiting",
REQUESTED: "requested",
ERROR: "error",
DONE: "done",
};
const initialState = { film: [], status: LOADING_STATE.WAITING };
export const movieSlice = createSlice({
initialState,
name: "movies",
reducers: {
uploadMovies(state, action) {
state.film.push(action.payload);
},
loadingState(state, action) {
state.status = action.payload;
},
},
});
export const { uploadMovies, loadingState} = movieSlice.actions;
export default movieSlice.reducer;
NOTE: One of the rules of reducers states that they must not do any asynchronous logic or other "side effects".
In the component, a useEffect hook can be used to dispatch the actions and fetch data from the api.
useEffect(() => {
dispatch(loadingState(LOADING_STATE.REQUESTED));
fetch("https://swapi.dev/api/films")
.then((res) => res.json())
.then((data) => {
data.results.forEach((el) => {
dispatch(uploadMovies(el));
});
dispatch(loadingState(LOADING_STATE.DONE));
})
.catch((err) => dispatch(LOADING_STATE.ERROR));
}, [dispatch]);
This method works alright but has some drawbacks, it puts too much logic in the component, it is not reusable as this code has to be written in multiple controllers or you might have to call the component every time.
This can be solved using redux middleware.
Day 16: Redux Middleware
For simple applications, the above approach in day 15 would work but when apps get larger you may have difficulties with that approach, for example you may have to repeat certain chunks of code just to carry out the same functionality.
Redux middleware works by first handling an event, like a user click or page reload, an object or function is then dispatched, once dispatched, the value looks for a middleware to make an async call to. Finally when the async call completes, a real action object is dispatched. A better explanation can be found here, I probably butchered it 😬😂.
Anyway, thunk middlweare allows us to write functions in our reducers that get dispatch
and getState
as arguments
A thunk is a piece of code that does some delayed work. Link
Since I used the official redux toolkit package, I did not have to configure the store to add thunkMiddleware. In a different scenario, you have to install redux-thunk
and enable it in the store as shown below.
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './reducers/index'
const store = createStore(rootReducer, applyMiddleware(thunk))
All I did was add a thunk function in my reducer that now fetches all the movies. This is in the movie-slice.js
file.
export const fetchMovies = () => {
return (dispatch, getState) => {
dispatch(loadingState(LOADING_STATE.REQUESTED));
return fetch("https://swapi.dev/api/films")
.then((res) => res.json())
.then((data) => {
data.results.forEach((el) => {
dispatch(uploadMovies(el));
});
dispatch(loadingState(LOADING_STATE.DONE));
})
.catch((err) => dispatch(LOADING_STATE.ERROR));
};
};
Now my component is much more lean as I can now dispatch the fetchMovies
function in a useEffect
hook.
import {fetchMovies} from "./movies/movie-slice";
useEffect(() => {
dispatch(fetchMovies());
}, [dispatch]);
This approach makes fetching movies reusable and separates concerns which can be really beneficial in large projects. I probably did not explain the concept too well and went on rambling but this is how I understood it, I shall do another practice project to make the concept stick. The code can be found in this branch
Day 17 to 19: Shopping cart in react-redux
I have been quite busy with a job and commuting, but I will try to push this small project soon and continue to day 20.
UPDATE
I finally managed to finish the shopping cart, here is the link to the source code.
Day 20: What I learnt
Day 11 to 20 of this challenge were the hardest for me because I lost motivation on day 19, It took me 2 months to complete the shopping cart project. This was not because of its complexity but because of a little bit of laziness combined with the daily struggles of working a 9 to 5 and commuting.
This is what I have learnt from day 11 to 20
- How to use redux 😁😀
- Showing up is better than not doing anything at all.
- The final product doesn't have to be perfect (This is just a coding and learning challenge)
- One loses motivation but you should never give up.
Top comments (0)