Esta é a maneira correta de excluir um item usando redux?

116

Eu sei que não devo alterar a entrada e devo clonar o objeto para alterá-lo. Eu estava seguindo a convenção usada em um projeto redux starter que usava:

ADD_ITEM: (state, action) => ({
  ...state,
  items: [...state.items, action.payload.value],
  lastUpdated: action.payload.date
})

para adicionar um item - eu uso o spread para anexar o item ao array.

para excluir usei:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: [...state.items.splice(0, action.payload), ...state.items.splice(1)],
  lastUpdated: Date.now() 
})

mas isso está alterando o objeto de estado de entrada - isso é proibido, embora eu esteja retornando um novo objeto?

CWright
fonte
1
Pergunta rápida. Splice retorna os itens que você removeu. É essa a sua intenção? Do contrário, você deve usar o slice, que também segue a lei de não mutações.
m0meni
Bem, neste exemplo, estou juntando as duas seções da matriz em uma nova matriz - com o item que eu queria remover deixado de fora. O Slice também retorna o item removido, certo? Só que ele faz isso sem alterar o array original, então essa seria a melhor abordagem?
CWright
@ AR7 de acordo com sua sugestão: items: [...state.items.slice(0, action.payload.value), ...state.items.slice(action.payload.value + 1 )]usar slice agora em vez de splice para não alterar a entrada - esse é o caminho a seguir ou existe um modo mais conciso?
CWright

Respostas:

206

Não. Nunca mude seu estado.

Mesmo que você esteja retornando um novo objeto, você ainda está poluindo o objeto antigo, o que você nunca quer fazer. Isso o torna problemático ao fazer comparações entre o antigo e o novo estado. Por exemplo, em shouldComponentUpdateque react-redux usa sob o capô. Também torna a viagem no tempo impossível (ou seja, desfazer e refazer).

Em vez disso, use métodos imutáveis. Sempre use Array#slicee nuncaArray#splice .

Presumo pelo seu código que action.payloadseja o índice do item que está sendo removido. A melhor maneira seria a seguinte:

items: [
    ...state.items.slice(0, action.payload),
    ...state.items.slice(action.payload + 1)
],
David L. Walsh
fonte
isso não funciona se estivermos lidando com o último elemento na matriz, também o uso de ...na segunda instrução dobrará o conteúdo do seu estado
Thaenor
4
Prove isso com um exemplo jsfiddle / codepen. O snippet arr.slice(arr.length)sempre deve produzir um array vazio, não importa qual seja o conteúdo arr.
David L. Walsh
1
@david-l-walsh desculpe pela confusão, devo ter cometido um erro de digitação ou algo assim ao testar este exemplo. Isso faz maravilhas. Minha única pergunta é: por que a necessidade do operador de spread ...na segunda parte -...state.items.slice(action.payload + 1)
Thaenor
5
Array#sliceretorna uma matriz. Para combinar as duas fatias em uma única matriz, usei o operador spread. Sem ele, você teria uma variedade de matrizes.
David L. Walsh
4
isso faz sentido. Muito obrigado por esclarecer (e desculpe a confusão no início).
Thaenor,
149

Você pode usar o método de filtro de matriz para remover um elemento específico de uma matriz sem alterar o estado original.

return state.filter(element => element !== action.payload);

No contexto do seu código, seria mais ou menos assim:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: state.items.filter(item => item !== action.payload),
  lastUpdated: Date.now() 
})
Steph M
fonte
1
O filtro produz uma nova matriz?
chenop
6
@chenop Sim, o método Array.filter retorna uma nova matriz. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Steph M
4
Observe que se houver duplicatas, isso removerá TODAS elas. Para usar o filtro para remover um índice específico, você pode usar, por exemploarr.filter((val, i) => i !== action.payload )
erich2k8
21

O Array.prototype.filtermétodo ES6 retorna uma nova matriz com os itens que correspondem aos critérios. Portanto, no contexto da pergunta original, seria:

DELETE_ITEM: (state, action) => ({
  ...state,
  items: state.items.filter(item => action.payload !== item),
  lastUpdated: Date.now() 
})
JoeTidee
fonte
.filter()não é um método ES2015, mas foi adicionado na versão anterior ES5.
morkro
7

Outra variação do redutor "DELETED" imutável para a matriz com objetos:

const index = state.map(item => item.name).indexOf(action.name);
const stateTemp = [
  ...state.slice(0, index),
  ...state.slice(index + 1)
];
return stateTemp;
romano
fonte
0

A regra de ouro é que não retornamos um estado de mutação, mas sim um novo estado. Dependendo do tipo de sua ação, pode ser necessário atualizar sua árvore de estados de várias formas quando ela atingir o redutor.

Neste cenário, estamos tentando remover um item de uma propriedade de estado.

Isso nos leva ao conceito de padrões imutáveis ​​de atualização (ou modificação de dados) do Redux. A imutabilidade é fundamental porque nunca queremos alterar diretamente um valor na árvore de estado, mas sempre fazer uma cópia e retornar um novo valor com base no valor antigo.

Aqui está um exemplo de como excluir um objeto aninhado:

// ducks/outfits (Parent)

// types
export const NAME = `@outfitsData`;
export const REMOVE_FILTER = `${NAME}/REMOVE_FILTER`;

// initialization
const initialState = {
  isInitiallyLoaded: false,
  outfits: ['Outfit.1', 'Outfit.2'],
  filters: {
    brand: [],
    colour: [],
  },
  error: '',
};

// action creators
export function removeFilter({ field, index }) {
  return {
    type: REMOVE_FILTER,
    field,
    index,
  };
}

export default function reducer(state = initialState, action = {}) {
  sswitch (action.type) {  
  case REMOVE_FILTER:
  return {
    ...state,
    filters: {
    ...state.filters,
       [action.field]: [...state.filters[action.field]]
       .filter((x, index) => index !== action.index)
    },
  };
  default:
     return state;
  }
}

Para entender melhor isso, certifique-se de verificar este artigo: https://medium.com/better-programming/deleting-an-item-in-a-nested-redux-state-3de0cb3943da

Kasra Khosravi
fonte