Child component not re-rendering in React/Redux when state is updated

Currently stuck in my React/Redux learning journey. I encountered an issue while working on a sample todo app where the addTodo action is triggered upon adding a new todo, and I can step through the store.dispatch logic. However, the problem arises when the haveStatePropsChanged value is calculated as false, resulting in no child updates.

Below are the relevant code snippets:

import React from 'react';
import { connect } from 'react-redux';
import { store, addTodo, completeTodo, deleteTodo, clearTodo } from './TodoState.jsx';

class AddTodoForm extends React.Component {
    ...
}

class TodoItem extends React.Component {
    ....
}

let TodoList = ({items}) => (
    <ul>
        {items.map((item,index) =>
            <TodoItem key={index} index={index} message={item.message} completed={item.completed}/>
        )}
    </ul>
)

let TodoComponent = ({ items, onAddTodo, onCompleteTodo, onDeleteTodo, onClearTodo }) => 
    (
        <div>
            <h1>Todo</h1>
            <AddTodoForm onAddTodo={onAddTodo} message/>
            <TodoList items={items} onCompleteTodo={onCompleteTodo} onDeleteTodo={onDeleteTodo} onClearTodo={onClearTodo}/>
        </div>
    )

const mapStateToProps = (state) => {
    return {
        items: state.todo.items
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        onAddTodo(message) {
            dispatch(addTodo(message))
        },
        onCompleteTodo(index) {
            dispatch(completeTodo(index))
        },
        onDeleteTodo(index) {
            dispatch(deleteTodo(index))
        },
        onClearTodo(index) {
            dispatch(clearTodo(index))
        }
    }
}

export default connect(mapStateToProps,mapDispatchToProps)(TodoComponent); 

The addTodo action is dispatched correctly by the AddTodoForm component, but the issue lies in the TodoList component not rendering again even though the items array is new.

UPDATE: The reducer indeed returns a new state.

Here is the reducer and action code:

import { createStore } from 'redux';
var defaultState = { todo: { items: [] } } 

const ADD_TODO = 1;
const COMPLETE_TODO = 2;
const DELETE_TODO = 3;
const CLEAR_TODO = 4;

const addTodo = (message) => { return {type: ADD_TODO, message: message, completed: false} };
const completeTodo = (index) => { return {type: COMPLETE_TODO, index:index} };
const deleteTodo = (index) => { return {type: DELETE_TODO, index:index} };
const clearTodo = (index) => { return {type: CLEAR_TODO, index:index} };

function todoReducer(state,action) {
    switch(action.type) {
        case ADD_TODO:
            var newState = Object.assign({},state);
            newState.todo.items.push({message:action.message,completed:false});
            return newState;
        case COMPLETE_TODO:
            var newState = Object.assign({},state);
            newState.todo.items[action.index].completed = true;
            return newState;
        case DELETE_TODO:
            var items = [].concat(state.todo.items);
            items.splice(action.index,1);
            return Object.assign({},state,{
                todo: {
                    items:items
                }
            });
        case CLEAR_TODO:
            return Object.assign({},state,{
                todo: {
                    items: []
                }
            });
        default:
            return state;
    }
}

var store = createStore(todoReducer,defaultState);

export { store, addTodo, completeTodo, deleteTodo, clearTodo };

Thank you,

Aaron

Answer №1

Make sure to return a new object as the state in your reducer function. For example:

return Object.assign({}, state, {items: [...oldItems, newItem]})

It's crucial to note that [...oldItems, newItem] creates a new array. In this case, while Object.assign only results in a shallow copy and the items get changed but maintain the same reference. Here is a working example:

import React from 'react';                                                                                               
import { render } from 'react-dom';                                                                                      
import { connect, Provider } from 'react-redux';                                                                         

import { createStore } from 'redux';                                                                                     

var defaultState = { todo: { items: [] } }                                                                               

const ADD_TODO = 1;                                                                                                      
const COMPLETE_TODO = 2;                                                                                                 
const DELETE_TODO = 3;                                                                                                   
const CLEAR_TODO = 4;                                                                                                    

const addTodo = (message) => { return {type: ADD_TODO, message: message, completed: false} };                            
const completeTodo = (index) => { return {type: COMPLETE_TODO, index:index} };                                           
const deleteTodo = (index) => { return {type: DELETE_TODO, index:index} };                                               
const clearTodo = (index) => { return {type: CLEAR_TODO, index:index} };                                                 

function todoReducer(state,action) {                                                                                     
    switch(action.type) {                                                                                                
        case ADD_TODO:                                                                                                   
            var newItem = {message:action.message,completed:false};                                                      
            return Object.assign({},state, {todo: {items: [...state.todo.items, newItem]}});                             
        case COMPLETE_TODO:                                                                                              
            var newState = Object.assign({},state);                                                                      
            newState.todo.items[action.index].completed = true;                                                          
            return newState;                                                                                             
        case DELETE_TODO:                                                                                                
            var items = [].concat(state.todo.items);                                                                     
            items.splice(action.index,1);                                                                                
            return Object.assign({},state,{                                                                              
                todo: {                                                                                                  
                    items:items                                                                                          
                }                                                                                                        
            });                                                                                                          
        case CLEAR_TODO:                                                                                                 
            return Object.assign({},state,{                                                                              
                todo: {                                                                                                  
                    items: []                                                                                            
                }                                                                                                        
            });                                                                                                          
        default:                                                                                                         
            return state;                                                                                                
    }                                                                                                                    
}                                                                                                                        

var store = createStore(todoReducer,defaultState);                                                                       

class AddTodoForm extends React.Component {                                                                              
    render() {                                                                                                           
        return <button onClick={this.props.onAddTodo}>test</button>                                                      
    }                                                                                                                    
}                                                                                                                        

class TodoItem extends React.Component {                                                                                 
    render() {                                                                                                           
        return <span>item</span>                                                                                         
    }                                                                                                                    
}                                                                                                                        

let TodoList = ({items}) => (                                                                                            
  <ul>                                                                                                                   
      {items.map((item,index) =>                                                                                         
        <TodoItem key={index} index={index} message={item.message} completed={item.completed}/>                          
      )}                                                 
  </ul>                                              
)                                                    

let TodoComponent = ({ items, onAddTodo, onCompleteTodo, onDeleteTodo, onClearTodo }) => /* expand's props */            
  (                                                       
    <div>                                                
        <h1>Todo</h1>                                                
        <AddTodoForm onAddTodo={onAddTodo} message/>   
        <TodoList items={items} onCompleteTodo={onCompleteTodo} onDeleteTodo={onDeleteTodo} onClearTodo={onClearTodo}/>  
    </div>                                            
  )                                                      

const mapStateToProps = (state) => {                       
    return {                                             
        items: state.todo.items                          
    }                                                    
}                                                    

const mapDispatchToProps = (dispatch) => {                  
    return {                                             
        onAddTodo(message) {                             
            dispatch(addTodo(message))                   
        },                                               
        onCompleteTodo(index) {                          
            dispatch(completeTodo(index))                
        },                                               
        onDeleteTodo(index) {                            
            dispatch(deleteTodo(index))                  
        },                                               
        onClearTodo(index) {                             
            dispatch(clearTodo(index))                   
        }                                                
    }                                                    
}                                                    

var Wrapper = connect(mapStateToProps,mapDispatchToProps)(TodoComponent);                                                

render(                                               
  <Provider store={store}>                               
      <Wrapper />                                        
  </Provider>,                                          
  document.getElementById('app')                        
)

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Issue rendering React.useEffect and mapStateToProps in React Native

I'm currently working on a project that involves displaying a registration modal when the user is null. However, I've noticed that sometimes it doesn't work properly. The issue arises when I check the state element to see if the user exists ...

Issue with Firebase V9 addDoc: No indication of success or failure, and data is not being written to the

I am encountering an issue where the authentication related functions are working fine, but I am unable to make any progress with Firestore. Despite no errors or successes being returned by the try-catch block, nothing seems to happen in the Firestore data ...

Connect the backend with the frontend using React.js

I am currently faced with the task of developing the front end for an existing backend project. The original front end was built using Angular, but I am opting to recreate it using React. However, I am unsure about how to establish a connection between t ...

Exploring the Depths of Deep Linking with ReactJS

As a React beginner, I am facing an issue with deep linking in my project. Whenever I directly access the full URL (like where 'Impressum' is considered a "new site"), I encounter a 404 error. However, when I navigate through the menu bar, ever ...

Updating the hostname in the systemFile does not result in the desired change being reflected in Next

Operating System : Windows 11 Next Version : 13.5.0 Issue: I am trying to set up my local development environment to run on instead of http://localhost:3000 I have made changes to the host file located in Windows\System32\drivers\etc- hos ...

Redirect the URL in react-snap to a new URL with a forward slash at the end

I recently implemented react-snap to generate static HTML for my website. However, I encountered some SEO issues after implementing react-snap. The old URLs (which were without slashes) are now redirecting to new URLs with slashes. For example: This URL: ...

Kindly make sure that the package.json file contains a valid "main" entry

Just added Bootstrap to my React project using npm. Imported Bootstrap with the following code: import 'bootstrap/dist/css/bootstrap.min.css'; However, I encountered an error that says: Cannot find module 'D:\project\my-app\n ...

Image showcase with React

Apologies for what may seem like a simple question, but I am new to working with React and would appreciate some guidance. I am trying to create an image array gallery by pulling images from a folder within my project directory. However, despite multiple a ...

There was a hiccup encountered while trying to follow the steps for creating a

Despite others' attempts to solve the errors quickly, the search continues and the symptoms persist. > <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="046c6168686b29766165677044342a352a34">[email protected]< ...

What is the best way to initiate a function from within another function while working with ReactJS?

Seeking guidance on how to trigger a Redux state change by calling a function from another function using the onClick event. Currently, I am able to invoke the Jammingmenu upon clicking an icon, however, no action is performed or alert displayed. Any assis ...

Tips for resizing all elements in Material-UI to fit your design requirements

I have been working on developing an app and encountering a frustrating issue. When I visit the library page (https://material-ui.com/components/buttons/#button), all the components are displayed in a desirable size. https://i.stack.imgur.com/DbqS6.png ht ...

Component that wraps around children elements and passes props to their render function

I'm currently working on coding a wrapper component that takes a title prop and a children prop which must be a function. All the other props passed to the wrapper should be used as arguments when rendering the children: import React, { ReactNode, Inp ...

What are the best practices for utilizing isForwardRef in ReactJS?

I attempted to utilize isForwardRef, however it did not produce the expected outcome. Could someone provide guidance on how to properly use it? Visit this link for more information import React, { forwardRef } from "react"; import { isForwardRe ...

Getting rid of the border on a material-ui v4 textbox

I am currently using Material-UI version 4 and have not upgraded to the latest mui v5. I have experimented with various solutions, but so far none have been successful. My goal is simply to eliminate or hide the border around the textfield component. < ...

Avoiding external variable reference through Jest.mock

For snapshot testing, I need to create a simple dummy mock of 1 react component. When attempting to use React.Component within the mock function, an error is thrown: The second argument of jest.mock() cannot reference external variables. However, usin ...

Redeeming tokens from different origins is only allowed for clients classified as the 'Single-Page Application' type

I've encountered an issue while trying to obtain an access token from the MS Graph API within a Next.js application. Everything works perfectly in Postman, but when attempting it in my app, I receive the error message: "AADSTS9002326: Cross-origin tok ...

The issue with displaying Fontawesome icons using @import in CSS-in-JS was not resolved

I recently changed how I was importing Fontawesome icons: src/App.css @import "@fortawesome/fontawesome-free/css/all.css";` After shifting the @import to CSS-in-Js using emotion: src/App.js // JS: const imports = css` @import "@fortawes ...

Dealing with various menu states using Material-UI Menu component: Tips and Tricks

I've encountered an issue with my dynamically generated dropdowns and accordions that appear at the client-side render (based on validated user purchases from the database). The error seems to stem from my menu anchor element not being able to pinpoi ...

What strategies can I employ to manage browser compatibility issues while utilizing contemporary JS and CSS frameworks?

I have been working on a project that utilizes the most recent versions of Angular (version 5) and Bootstrap (4). However, after a few weeks of development, I noticed that some features are not functioning properly on Safari, and I am uncertain if all fea ...

Error encountered while attempting to install material-ui v3.0.3 due to an unexpected termination of the JSON input

I'm currently in the process of installing the most recent stable version of material-ui(v3.03) by running: npm install @material-ui/core. However, I encountered an issue with npm ERR! Unexpected end of JSON input while parsing near '...-/brcast- ...