The document discusses the implementation of a Redux-based notes application, detailing the creation of a note reducer and a filter reducer to manage the state of notes and their visibility based on user-selected filters. It introduces the use of Redux Toolkit to simplify the store configuration and reducer creation, highlighting the benefits of using createSlice for defining reducers and action creators. The document also includes code snippets for setting up the application and handling state changes effectively.
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0 ratings0% found this document useful (0 votes)
13 views22 pages
Fullstack Part6 of 2
The document discusses the implementation of a Redux-based notes application, detailing the creation of a note reducer and a filter reducer to manage the state of notes and their visibility based on user-selected filters. It introduces the use of Redux Toolkit to simplify the store configuration and reducer creation, highlighting the benefits of using createSlice for defining reducers and action creators. The document also includes code snippets for setting up the application and handling state changes effectively.
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 22
{() => fs}
b Many reducers Let's continue our work with the simplified Redux version of our notes application.
To ease our development, let's change our reducer so that the store gets initialized with a state that contains a couple of notes:
const initialState = [ copy
{ content: 'reducer defines how redux store works' , important: true, id: 1, }, { content: 'state of store can contain any data', important: false, id: 2, }, ]
Since the name attribute of all the radio buttons is the same, they form a button group where only one option can be selected.
The buttons have a change handler that currently only prints the string associated with the clicked button to the console. In the following section, we will implement filtering by storing both the notes as well as the value of the filter in the redux store. When we are finished, we would like the state of the store to look like this:
{ copy notes: [ { content: 'reducer defines how redux store works' , important: true, id: 1}, { content: 'state of store can contain any data', important: false, id: 2} ], filter : 'IMPORTANT' }
Only the array of notes was stored in the state of the previous implementation of our application. In the new implementation, the state object has two properties, notes that contains the array of notes and filter that contains a string indicating which notes should be displayed to the user.
Combined reducers
We could modify our current reducer to deal with the new shape of the state. However, a better solution in this situation is to define a new separate reducer for the state of the filter:
Since our application breaks completely at this point, we render an empty div element instead of the App component. The state of the store gets printed to the console:
As we can see from the output, the store has the exact shape we wanted it to!
Let's take a closer look at how the combined reducer is created:
const reducer = combineReducers({ copy
notes: noteReducer, filter : filterReducer , })
The state of the store defined by the reducer above is an object with two properties: notes and filter. The value of the notes property is defined by the noteReducer, which does not have to deal with the other properties of the state. Likewise, the filter property is managed by the filterReducer.
Before we make more changes to the code, let's take a look at how different actions change the state of the store defined by the combined reducer. Let's add the following to the main.jsx file:
import { createNote } from './reducers/noteReducer' copy
import { filterChange } from './reducers/filterReducer' //... store.subscribe(() => console.log(store.getState())) store.dispatch(filterChange ('IMPORTANT')) store.dispatch(createNote('combineReducers forms one reducer from many simple reducers'))
By simulating the creation of a note and changing the state of the filter in this fashion, the state of the store gets logged to the console after every change that is made to the store: At this point, it is good to become aware of a tiny but important detail. If we add a console log statement to the beginning of both reducers:
Based on the console output one might get the impression that every action gets duplicated:
Is there a bug in our code? No. The combined reducer works in such a way that every action gets handled in every part of the combined reducer, or in other words, every reducer "listens" to all of the dispatched actions and does something with them if it has been instructed to do so. Typically only one reducer is interested in any given action, but there are situations where multiple reducers change their respective parts of the state based on the same action. Finishing the filters
Let's finish the application so that it uses the combined reducer. We start by changing the rendering of the application and hooking up the store to the application in the main.jsx file:
There is a slight cosmetic flaw in our application. Even though the filter is set to ALL by default, the associated radio button is not selected. Naturally, this issue can be fixed, but since this is an unpleasant but ultimately harmless bug we will save the fix for later.
The current version of the application can be found on GitHub , branch part6-2. Exercise 6.9
6.9 Better Anecdotes, step 7
Implement filtering for the anecdotes that are displayed to the user.
Store the state of the filter in the redux store. It is recommended to create a new reducer, action creators, and a combined reducer for the store using the combineReducers function.
Create a new Filter component for displaying the filter. You can use the following code as a template for the component:
const Filter = () => { copy
const handleChange = (event) => { // input-field value is in variable event.target.value } const style = { marginBottom: 10 }
As we have seen so far, Redux's configuration and state management implementation requires quite a lot of effort. This is manifested for example in the reducer and action creator-related code which has somewhat repetitive boilerplate code. Redux Toolkit is a library that solves these common Redux-related problems. The library for example greatly simplifies the configuration of the Redux store and offers a large variety of tools to ease state management.
Let's start using Redux Toolkit in our application by refactoring the existing code. First, we will need to install the library:
npm install @reduxjs/toolkit copy
Next, open the main.jsx file which currently creates the Redux store. Instead of Redux's createStore function, let's create the store using Redux Toolkit's configureStore function:
import ReactDOM from 'react-dom/client' copy
import { Provider } from 'react-redux' import { configureStore } from '@reduxjs/toolkit' import App from './App'
import noteReducer from './reducers/noteReducer'
import filterReducer from './reducers/filterReducer'
We already got rid of a few lines of code, now we don't need the combineReducers function to create the store's reducer. We will soon see that the configureStore function has many additional benefits such as the effortless integration of development tools and many commonly used libraries without the need for additional configuration.
Let's move on to refactoring the reducers, which brings forth the benefits of the Redux Toolkit. With Redux Toolkit, we can easily create reducer and related action creators using the createSlice function. We can use the createSlice function to refactor the reducer and action creators in the reducers/noteReducer.js file in the following manner:
import { createSlice } from '@reduxjs/toolkit' copy
const initialState = [ { content: 'reducer defines how redux store works' , important: true, id: 1, }, { content: 'state of store can contain any data', important: false, id: 2, }, ]
The createSlice function's name parameter defines the prefix which is used in the action's type values. For example, the createNote action defined later will have the type value of notes/createNote . It is a good practice to give the parameter a value which is unique among the reducers. This way there won't be unexpected collisions between the application's action type values. The initialState parameter defines the reducer's initial state. The reducers parameter takes the reducer itself as an object, of which functions handle state changes caused by certain actions. Note that the action.payload in the function contains the argument provided by calling the action creator:
dispatch(createNote('Redux Toolkit is awesome!')) copy
This dispatch call is equivalent to dispatching the following object:
dispatch({ type: 'notes/createNote', payload: 'Redux Toolkit is awesome!' }) copy
If you followed closely, you might have noticed that inside the createNote action, there seems to happen something that violates the reducers' immutability principle mentioned earlier:
We are mutating state argument's array by calling the push method instead of returning a new instance of the array. What's this all about?
Redux Toolkit utilizes the Immer library with reducers created by createSlice function, which makes it possible to mutate the state argument inside the reducer. Immer uses the mutated state to produce a new, immutable state and thus the state changes remain immutable. Note that state can be changed without "mutating" it, as we have done with the toggleImportanceOf action. In this case, the function directly returns the new state. Nevertheless mutating the state will often come in handy especially when a complex state needs to be updated.
The createSlice function returns an object containing the reducer as well as the action creators defined by the reducers parameter. The reducer can be accessed by the noteSlice.reducer property, whereas the action creators by the noteSlice.actions property. We can produce the file's exports in the following way: const noteSlice = createSlice(/* ... */) copy
The imports in other files will work just as they did before:
import noteReducer, { createNote, toggleImportanceOf } from copy
'./reducers/noteReducer'
We need to alter the action type names in the tests due to the conventions of ReduxToolkit:
import noteReducer from './noteReducer' copy
import deepFreeze from 'deep-freeze'
describe('noteReducer', () => { test('returns new state with action notes/createNote', () => { const state = [] const action = { type: 'notes/createNote', payload: 'the app state is in redux store', }
test('returns new state with action notes/toggleImportanceOf', () => {
const state = [ { content: 'the app state is in redux store', important: true, id: 1 }, { content: 'state changes are made with actions', important: false, id: 2 }]
The output is interesting but not very useful. This is about the previously mentioned Immer library used by the Redux Toolkit internally to save the state of the Store.
The status can be converted to a human-readable format by using the current function from the immer library.
Let's update the imports to include the "current" function from the immer library:
import { createSlice, current } from '@reduxjs/toolkit' copy
Then we update the console.log function call:
console.log(current(state)) copy
Console output is now human readable
Redux DevTools
Redux DevTools is a Chrome addon that offers useful development tools for Redux. It can be used for example to inspect the Redux store's state and dispatch actions through the browser's console. When the store is created using Redux Toolkit's configureStore function, no additional configuration is needed for Redux DevTools to work.
Once the addon is installed, clicking the Redux tab in the browser's developer tools, the Redux DevTools should open:
You can inspect how dispatching a certain action changes the state by clicking the action: It is also possible to dispatch actions to the store using the development tools:
You can find the code for our current application in its entirety in the part6-3 branch of this GitHub repository .
Exercises 6.10.-6.13.
Let's continue working on the anecdote application using Redux that we started in exercise 6.3.
6.10 Better Anecdotes, step 8
Install Redux Toolkit for the project. Move the Redux store creation into the file store.js and use Redux Toolkit's configureStore to create the store.
Change the definition of the filter reducer and action creators to use the Redux Toolkit's createSlice function.
Also, start using Redux DevTools to debug the application's state easier.
6.11 Better Anecdotes, step 9
Change also the definition of the anecdote reducer and action creators to use the Redux Toolkit's createSlice function.
Implementation note: when you use the Redux Toolkit to return the initial state of anecdotes, it will be immutable, so you will need to make a copy of it to sort the anecdotes, or you will encounter the error "TypeError: Cannot assign to read only property". You can use the spread syntax to make a copy of the array. Instead of:
anecdotes.sort() copy
Write:
[...anecdotes].sort() copy
6.12 Better Anecdotes, step 10
The application has a ready-made body for the Notification component:
Extend the component so that it renders the message stored in the Redux store, making the component take the following form: import { useSelector } from 'react-redux' copy
You will have to make changes to the application's existing reducer. Create a separate reducer for the new functionality by using the Redux Toolkit's createSlice function.
The application does not have to use the Notification component intelligently at this point in the exercises. It is enough for the application to display the initial value set for the message in the notificationReducer.
6.13 Better Anecdotes, step 11
Extend the application so that it uses the Notification component to display a message for five seconds when the user votes for an anecdote or creates a new anecdote:
It's recommended to create separate action creators for setting and removing notifications. Propose changes to material