@LebedevImagine

Почему Redux не перерисовывает компонент, если обновилось свойство объекта?

Есть простейший todo лист с двумя кнопками - удалить запись и пометить её выполненной.
Todo-лист получает заранее подготовленный массив объектов со свойствами title и done, а затем их рендерит, здесь проблем нет.

На обе кнопки поставлены 2 even слушателя: на удаление мы делаем копию массива объектов, удаляем нужный элемент, возвращаем модифицированный массив. Здесь проблем нет.
На пометить как выполненную тоже ставится прослушка. Если событие срабатывает, мы делаем копию массива, ищем нужный элемент по id, меняем свойство, возвращаем мод. версию массива.

Проблема:
Если удалять элементы элементы - проблем не возникает. Список отлично перерисовывается.
Если помечать данные как выполненные - элемент не перерисовывается, как будто не видит, что store изменился (хотя проверки на store.getState говорят об обратном). В чем может быть проблема? Может, у меня не правильный подход?

Удалил почти все детали приложения, не касающиеся проблемы
github: https://github.com/LebeDEVBoris/todolist-react-red...

actions.js (Экшен крейторы)
const fetchPosts = (PostService, dispatch, state) => () => {
    dispatch(postRequested());
    //PostService.getPosts().then((res) => dispatch(postsLoaded(res)));
    const data = PostService.getPosts();
    dispatch(postsLoaded(data));
}

const deleteItem = (id) => {
    return {
        type: 'TURN_DELETE_POST',
        payload: id
    }
}

const doneItem = (id) => {
    return {
        type: 'TURN_DONE_POST',
        payload: id
    }
}

const updatePostsForDelete = (id, posts) => {

    const idx = posts.findIndex((el) => el.id === id);
    const newArray = [
        ...posts.slice(0, idx),
        ...posts.slice(idx + 1)
    ];
    
    return newArray;
} 

const updatePostsForDone = (id, posts) => {
    const arr = posts;
    arr.forEach((elem) => {
        if (elem.id === id) elem.done = !elem.done;
    });
    return arr;
}
// внизу экспорт крейторов

reducer
// сверху подключение крейторов
const initialState = {
    data: null,
    loading: true,
}


const reducer = (state = initialState, action) => {

    switch(action.type) {

        case 'FETCH_POSTS_LOAD_REQUEST':
            return {
                ...state,
                loading: true
            }

        case 'FETCH_POSTS_LOAD_SUCCESS':
    
            return {
                data: action.payload,
                loading: false
            }
        
        case 'TURN_DELETE_POST':
            {const newData = updatePostsForDelete(action.payload, state.data);
            return {
                ...state,
                data: newData
            }}
        
        case 'TURN_DONE_POST':
            const newData = updatePostsForDone(action.payload, state.data);
            return {
                ...state,
                data: newData
            }
        
        case 'TURN_SHOW_STATE':
            console.log('store = ', state);
            return state;


        default:
            return state;
    }
export default reducer;
}


TaskBar.js
import React, {Component} from 'react';

import './TaskBar.sass';

import {connect} from 'react-redux';
import {fetchPosts, deleteItem, doneItem, showState} from './../../actions/actions';
import withPostService from './../hocs/withPostService';

import Spinner from './../Spinner/Spinner';
import TaskBarItem from './../TaskBarItem/TaskBarItem';

const TaskBar = (props) => {
    
    const {data, onDelete, onDone} = props;

    const tasks = data.map((task) => {
        return(
            <TaskBarItem 
            data={task}
            onDelete={onDelete}
            onDone={onDone}
            key={task.id}
            />
        );
    });

    return(
        <div className="taskbar">
            { tasks }
        </div>
    );   
}

class TaskBarContainer extends Component {

    componentDidMount() {
        this.props.fetchPosts();
    }

    render() {
        const {data, loading, onDelete, onDone} = this.props;
        
        if (loading) {
            return <Spinner />
        }

        //console.log(this.props);
        return(
            <TaskBar 
                data={data}
                onDelete={onDelete}
                onDone={onDone}
            />
            
        );
    }
}

const mapStateToProps = ({data, loading}) => {
    
    return {
        data, loading
    }
}

const mapDispatchToProps = (dispatch, {PostService}) => {
    return {
        fetchPosts: fetchPosts(PostService, dispatch),
        onDelete: (id) => dispatch(deleteItem(id)),
        onDone: (id) => dispatch(doneItem(id)),
    };
}

export default withPostService()(
    connect(mapStateToProps, mapDispatchToProps)(TaskBarContainer)
);


TaskBarItem.js
import React, {Component}  from 'react';
import './TaskBarItem.sass';

class TaskBarItem extends Component {

    onDelete = this.props.onDelete;
    onDone = this.props.onDone;

    render() {

    // если есть done в классе - меняются стили, текст становится зачёркнутым
    let onImportantClasses = 'taskbar__task';

    if (this.props.data.done) {
        onImportantClasses += ' done';
    }
   
    return(
        <div className={onImportantClasses}>
            <div className="taskbar__title">{this.props.data.title}</div>
            <div className="taskbar__buttons">
                <div className="taskbar__trash" onClick={() => this.onDelete(this.props.data.id)}>
                    <i className="fa fa-trash-o"></i>
                </div>
                <div className="taskbar__exclamation" onClick={() => this.onDone(this.props.data.id)}>
                    <i className="fa fa-exclamation"></i>
                </div>
            </div>
        </div>
    );
    }

}

export default TaskBarItem;


Сам вид данных:
data = [
        {title: 'Drink Coffee', important: false, done: false, id: 1},
        {title: 'Create application on pure React', important: false, done: false, id: 2},
        {title: 'Have a lunch', important: false, done: false, id: 3}
    ];
  • Вопрос задан
  • 86 просмотров
Решения вопроса 1
0xD34F
@0xD34F
Свойство обновилось, а объект тот же остался. Вместо изменения свойств надо объект копировать:

case 'TURN_DONE_POST':
  return {
    ...state,
    data: state.data.map(n => n.id === action.payload
      ? { ...n, done: !n.done }
      : n
    ),
  };
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы