Обновить одно значение в массиве элементов | реагировать на сокращение

У меня есть список дел, и я хочу установить состояние этого элемента в массиве на «завершение», если пользователь нажимает «завершить».

Вот мои действия:

export function completeTodo(id) {
    return {
        type: "COMPLETE_TASK",
        completed: true,
        id
    }
}

Вот мой редуктор:

case "COMPLETE_TASK": {
             return {...state,
                todos: [{
                    completed: action.completed
                }]
             }
        }

Проблема, с которой я сталкиваюсь, заключается в том, что в новом состоянии больше нет текста, связанного с этим элементом списка дел в выбранном элементе, и идентификатора больше нет. Это потому, что я перезаписываю состояние и игнорирую предыдущие свойства? Моя загрузка элемента объекта выглядит следующим образом:

Objecttodos: Array[1]
    0: Object
        completed: false
        id: 0
        text: "Initial todo"
    __proto__: Object
    length: 1
    __proto__: Array[0]
    __proto__: Object

Как видите, все, что я хочу сделать, это установить для завершенного значения значение true.


person Filth    schedule 07.12.2016    source источник


Ответы (3)


Вам нужно преобразовать массив todos, чтобы обновить соответствующий элемент. Array.map — самый простой способ сделать это:

case "COMPLETE_TASK":
    return {
        ...state,
        todos: state.todos.map(todo => todo.id === action.id ?
            // transform the one with a matching id
            { ...todo, completed: action.completed } : 
            // otherwise return original todo
            todo
        ) 
    };

Существуют библиотеки, которые помогут вам с таким обновлением глубокого состояния. Список таких библиотек можно найти здесь: https://github.com/markerikson/redux-ecosystem-links/blob/master/immutable-data.md#immutable-update-utilities

Лично я использую ImmutableJS (https://facebook.github.io/immutable-js/ ), который решает проблему своими методами updateIn и setIn (которые более эффективны, чем обычные объекты и массивы для больших объектов с большим количеством ключей и для массивов, но медленнее для маленьких).

person TomW    schedule 07.12.2016
comment
Спасибо за вашу помощь TomW и спасибо за ссылки на библиотеки, касающиеся обновления глубокого состояния. Большая помощь! - person Filth; 07.12.2016
comment
Я вижу, вы используете однострочный оператор if, хотя мне сложно его читать. Я знаю, что редукторы бомбят, когда есть операторы if. Можете ли вы объяснить, что здесь происходит, и, возможно, другой способ написать эту логику? - person Filth; 08.12.2016
comment
Редукторы «бомдают», когда вы не возвращаете состояние из каждой ветки — операторы if в порядке, просто вам нужно убедиться, что вы «возвращаете состояние»; после if. Если вы посмотрите на обработчик TOGGLE_TODO прямо здесь: redux.js .org/docs/basics/Reducers.html#handling-more-actions вы увидите очень похожий на мой пример, в котором используется оператор if вместо ?:?: имеет небольшое преимущество перед оператором if в что это заставляет вас возвращать значение из каждой ветки. - person TomW; 08.12.2016

Новое состояние больше не имеет текста, связанного с этим элементом списка дел в выбранном элементе, и идентификатора больше нет. Это потому, что я перезаписываю состояние и игнорирую предыдущие свойства?

Да, потому что при каждом обновлении вы назначаете новый массив с только одним ключом completed, и этот массив не содержит никаких предыдущих значений. Таким образом, после обновления в массиве не будет предыдущих данных. Вот почему текста и идентификатора нет после обновления.

Решения:

1- Используйте массив .map, чтобы найти правильный элемент, а затем обновить значение, например:

case "COMPLETE_TASK":
    return {
        ...state,
        todos: state.todos.map(todo => 
            todo.id === action.id ? { ...todo, completed: action.completed } : todo
        ) 
    };

2- Используйте array.findIndex для найдите индекс этого конкретного объекта, а затем обновите его, например:

case "COMPLETE_TASK":
    let index = state.todos.findIndex(todo => todo.id === action.id);
    let todos = [...state.todos];
    todos[index] = {...todos[index], completed: action.completed};
    return {...state, todos}

Проверьте этот фрагмент, чтобы лучше понять ошибку, которую вы делаете:

let state = {
  a: 1,
  arr: [
    {text:1, id:1, completed: true},
    {text:2, id:2, completed: false}
  ]
}

console.log('old values', JSON.stringify(state));

// updating the values

let newState = {
   ...state,
   arr: [{completed: true}]
}

console.log('new state = ', newState);

person Mayank Shukla    schedule 27.02.2018

Один из основополагающих принципов дизайна в React — «Не изменять состояние». Если вы хотите изменить данные в массиве, вы хотите создать новый массив с измененными значениями.

Например, у меня есть массив результатов в состоянии. Сначала я просто устанавливаю значения 0 для каждого индекса в моем конструкторе.

this.state = {           
       index:0,
        question: this.props.test[0].questions[0],
        results:[[0,0],[1,0],[2,0],[3,0],[4,0],[5,0]],
        complete: false         
};

Позже я хочу обновить значение в массиве. Но я не меняю его в объекте состояния. В ES6 мы можем использовать оператор распространения. Метод среза массива возвращает новый массив, он не изменит существующий массив.

updateArray = (list, index,score) => {
   // updates the results array without mutating it
      return [
        ...list.slice(0, index),
        list[index][1] = score,
       ...list.slice(index + 1)
     ];
};

Когда я хочу обновить элемент в массиве, я вызываю updateArray и сразу устанавливаю состояние:

this.setState({
        index:newIndex, 
        question:this.props.test[0].questions[newIndex],
        results:this.updateArray(this.state.results, index, score)
});
person Charles Owen    schedule 27.02.2018