cinnaroll45 cinnaroll45 - 4 years ago 97
React JSX Question

Objects in array turn into undefined after using a particular reducer/action

I'm working with a classic to do list project to learn Redux and I'm having a strange issue.

Basically, I have a to-do list with checkboxes and when the user clicks on a checkbox, an action gets dispatched which should mark that object's completed property as true and the component should update.

However... When this action fires, the object which is supposed to be marked as complete successfully returns with all of it's properties but the rest of the todo list (the other objects in the array) get corrupted, losing all their properties and they turn into 'undefined' thus causing problems in the render.

I've tried to include all the code that I think is relevant but I think that I'm doing something wrong in my reducer, but I can't find seem to find the issue.

Todo List Component

class TodoList extends Component {

render(){
const {todos, showCompleted, searchTerm} = this.props;
const renderTodos = () => {
if (todos.length === 0) {
return (
<p className="container__message">Nothing to do.</p>
);
}
return TodoAPI.filterTodos(todos, showCompleted, searchTerm).map((todo) => {
return (
<Todo key={todo.id} {...todo}/>
);
});
};
return (
<div>
{renderTodos()}
</div>
);
}
}
export default connect((state) => {
return state;
})(TodoList);


Todo Component

class Todo extends Component {

render() {
const {id, text, completed, createdAt, completedAt, dispatch} = this.props;
const todoClass = completed
? 'todo todo-completed'
: 'todo';

const renderDate = () => {
let displayMessage = 'Created ';
let timestamp = createdAt;

if (completed) {
displayMessage = 'Completed ';
timestamp = completedAt;
}
return displayMessage + moment.unix(timestamp).format('MMM Do YYYY @ h:mm a');
};

return (
<div className={todoClass}
onClick={event => dispatch(actions.toggleTodo(id)) }>
<input type="checkbox" checked={completed} readOnly/>
<div>
<p>{text}</p>
<p className="todo__subtext">{renderDate()}</p>
</div>
</div>
);
}
}
export default connect()(Todo);


Action

export const toggleTodo = (id) => {
return {
type: 'TOGGLE_TODO',
id: id
};
};


Reducer

export const todosReducer = (state = [], action) => {
switch (action.type) {
case 'TOGGLE_TODO':
return state.map((todo) => {
if (todo.id === action.id) {
let nextCompleted = !todo.completed;

return {
...todo,
completed: nextCompleted,
completedAt: todo.completed ? moment().unix() : 0
};
}
});
default:
return state;
}
};

Answer Source

Issue is you are not returning anything if the condition todo.id === action.id fail. With the map if you don't return anything, by default it will return undefined, Try this:

return state.map((todo) => {
    if (todo.id === action.id) {
        let nextCompleted = !todo.completed;

        return {
            ...todo,
            completed: nextCompleted,
            completedAt: todo.completed ? moment().unix() : 0
        };
    }else{
        return todo;
    }
});

Check this:

a=[1,2,3,4,5,6];
b = a.map ( i => { if(i % 2 == 0) return i;})
console.log(b);

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download