React-redux had always been a mystery, in that I have never really had an opportunity to work on it, to the extent of fully understanding it. Hence, this time when I am working on my document sharing app, I thought I should set aside some time to fully understand how to use it. For the purpose of this post and to help readers better understand, I have created an open-source Github repository for a react app using react-redux. In this post you, using simple code samples you will learn how to use a react-redux store for state management in your react app as well as how to unit test react-redux app. For the Github repository mentioned earlier, you will see a link to it later in the post.
What is react-redux?
React-redux is a state management library for react. Now, without redux, if you had to pass state between components, you would declare the state in the parent component and pass it down to child components. After which each time a state change occurs in a child component, it will be applied to all other child components that use that state. This method would work very well for smaller applications but once your application grows, then managing state changes across a large app would be very challenging. At this point, even your code will be very hard to read.
React-redux solves this problem by providing centralised state management for your application. In react-redux once you declare the initial state, the components in the app that need to access the state, can do so by subscribing to it. Furthermore, the components using can also dispatch actions indicating a state change which is then picked by a reducer function that would determine how state changes are applied.
You can think of it as something that follows the publisher subscriber model. A publisher will publish an event that would be picked up by the subscribers that have subscribed for those changes.
Code samples
Now is a good for you to see how to react-redux works via come code samples for news feed react app.
Components
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
Notice, how the <App /> component is wrapped in the provider component. Let’s create the redux store component mentioned on line 7.
import { createStore } from 'redux';
import { NewsArchive } from './NewsFeedStore';
const initialState = {
stories: NewsArchive
};
export const storiesReducer = (state = initialState, action) => {
switch(action.type) {
case 'ADD_STORY':
return {...state, stories: [...state.stories, action.payload]};
case 'REMOVE_STORY':
const newArr = state.stories.filter(s => s.id !== action.payload);
return {...state, stories:[...newArr]};
default:
return state;
}
}
const store = createStore(storiesReducer);
export default store;
App.js
import './App.css';
import { NewsFeed } from './News';
function App() {
return (
<div className="App">
<h1 className='Side-margins News-header'> News Feed </h1>
<hr />
<NewsFeed />
</div>
);
}
export default App;
For the NewsFeed declaration on line 9, let’s define that component,
import { NewsArchive } from "./NewsFeedStore"
import './NewsFeed.css';
import './App.css';
import { useDispatch, useSelector } from "react-redux";
/**
* populate data from an array
* of news objects
*/
export const NewsFeed = () => {
const news = useSelector((state) => state.stories);
const dispatch = useDispatch();
const addStory = () => {
const newStory = {
id: news[news.length - 1].id + 1,
title: "Chatgpt5.0",
description: "OpenAI to launch their newest version of the chat gpt worldwide"
};
dispatch({type: 'ADD_STORY', payload: newStory});
}
const removeStory = (id) => {
console.log(`About to delete story ${id}`);
dispatch({type: 'REMOVE_STORY', payload: id});
}
return (
<div>
<h1> Your latest news </h1>
{news.map(story => {
return (
<div className="Side-margins News-feed"
key={story.id}>
<div className="Side-margins News-story">
<img alt="Image placeholder" src="#" width='100' height='100' />
</div>
<div className="Side-margins News-story">
<h3 className="Story-header">{story.title}</h3>
<p className="Story-content"> {story.description} </p>
</div>
<div>
<button onClick={() => removeStory(story.id)}>Delete</button>
</div>
</div>
)
})}
<div>
<button onClick={addStory} > Add Story </button>
</div>
</div>
)
}
Unit test react-redux
Now, you can define tests by starting with the test that’s bundled when starting react. The objective was just to highlight how to render a react app, extract a certain element and validate it.
import { render, screen } from '@testing-library/react';
import App from './App';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { storiesReducer } from './store';
const renderWithReduxProvider = (
component,
{initialState, store = createStore(storiesReducer, initialState)} = {}
) => {
return {
...render(<Provider store = {store}> {component} </Provider>),
store,
}
}
test('renders learn react link', () => {
renderWithReduxProvider(<App />);
const linkElement = screen.getByText(/News Feed/i);
expect(linkElement).toBeInTheDocument();
});
Ok, now that you are a bit more comfortable with this, you can write tests for a system that is highly complex.
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { NewsFeed } from "./News";
import '@testing-library/jest-dom'
import { createStore } from 'redux';
import { storiesReducer } from "./store";
import { Provider } from 'react-redux';
const renderWithReduxProvider = (
component,
{initialState, store = createStore(storiesReducer, initialState)} = {}
) => {
return {
...render(<Provider store = {store}> {component} </Provider>),
store,
}
}
test('test news story renders', async () => {
renderWithReduxProvider(<NewsFeed />);
// const storyElement = await waitFor(() => screen.getByText(/Super intense heat wave/i))
const storyElement = screen.getByText(/Super intense heat wave/i);
expect(storyElement).toBeInTheDocument();
});
test('test add new story', async () => {
renderWithReduxProvider(<NewsFeed />);
// const storyElement = await waitFor(() => screen.getByText(/Super intense heat wave/i))
fireEvent.click(screen.getByText(/Add Story/i));
const storyElement = screen.getByText(/Chatgpt5.0/i);
expect(storyElement).toBeInTheDocument();
});
test('test remove story', async () => {
renderWithReduxProvider(<NewsFeed />);
// const storyElement = await waitFor(() => screen.getByText(/Super intense heat wave/i))
fireEvent.click(screen.getByText(/Add Story/i));
const removeBtns = screen.getAllByText(/Delete/i);
fireEvent.click(removeBtns[0]);
const storyElement = screen.queryByText(/Super intense heat wave/i);
expect(storyElement).not.toBeInTheDocument();
});
Conclusion
In this tutorial you learnt how to create and manage state with react-redux in a react ap and also how to write unit tests for react-redux. You can find the entire source code for the app in the Github link below.
https://github.com/cptdanko/react-redux-testing
If you find any of my posts useful and want to support me, you can buy me a coffee 🙂
https://www.buymeacoffee.com/bhumansoni
While you are here, maybe try one of my apps for the iPhone.
Products – My Day To-Do (mydaytodo.com)
Here are some of my other bloposts on Java
How to build a full stack Spring boot API with ReactJS frontend – My Day To-Do (mydaytodo.com)
How to call REST API with WebClient – My Day To-Do (mydaytodo.com)
How to build a jokes client in Java Spring Boot with RestTemplate – My Day To-Do (mydaytodo.com)
Have a read of some of my other posts on AWS
Upload to AWS S3 bucket from Java Spring Boot app – My Day To-Do (mydaytodo.com)
Deploy NodeJS, Typescript app on AWS Elastic beanstalk – (mydaytodo.com)
How to deploy spring boot app to AWS & serve via https – My Day To-Do (mydaytodo.com)
2 Comments
How to build a blog engine with React & Spring Boot - Part 1 - My Day To-Do · September 21, 2024 at 12:27 am
[…] How to unit test react-redux app – My Day To-Do (mydaytodo.com) […]
How to build a blog engine with React & Spring Boot – Part 3 CI/CD pipeline · October 4, 2024 at 9:08 pm
[…] How to unit test react-redux app – My Day To-Do (mydaytodo.com) […]