2
votes

I am learning React/Redux and came to a point where I am stuck. In the sample todo app I am working on when a new todo is added the addTodo action is taken and I can step through the store.dispatch logic. What fails is the haveStatePropsChanged value is calculated as false hence no child updates.

The code snippets follow:

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 }) => /* 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))
        }
    }
}

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

The AddTodoForm correctly dispatches addTodo action, the issue is the TodoList component does not render again even through the items array is a new array.

UPDATE: My reducer does return 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 };

Thanks,

Aaron

1
please add your reducer codeKyeotic
may as well add your action creators as well.azium
Let's debug it, Can you put a console.log into the reducer and into the component and post the results? Maybe something is wrong on the naming or something like that (if you've installed redux devtools it's way easier to check for this)Franco Risso

1 Answers

2
votes

Check that you return a new object as a state in your reducer. Ex: return Object.assign ({}, state, {items: [...oldItems, newItem]})

Pay attention here [...oldItems, newItem] this will create new array. In your case Object.assign is doing only shallow copy and actually items changed but holds the same reference. Have a look at 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')                                                                                         
)