Como atualizar um valor único dentro de um item de array específico em redux

106

Eu tenho um problema em que a re-renderização do estado causa problemas de interface do usuário e foi sugerido apenas atualizar um valor específico dentro do meu redutor para reduzir a quantidade de re-renderização em uma página.

este é um exemplo do meu estado

{
 name: "some name",
 subtitle: "some subtitle",
 contents: [
   {title: "some title", text: "some text"},
   {title: "some other title", text: "some other text"}
 ]
}

e atualmente estou atualizando assim

case 'SOME_ACTION':
   return { ...state, contents: action.payload }

onde action.payloadé uma matriz inteira contendo novos valores. Mas agora eu só preciso atualizar o texto do segundo item na matriz de conteúdo, e algo como isso não funciona

case 'SOME_ACTION':
   return { ...state, contents[1].text: action.payload }

onde action.payloadestá agora um texto que preciso para atualização.

Ilja
fonte

Respostas:

58

Você pode usar os ajudantes React Immutability

import update from 'react-addons-update';

// ...    

case 'SOME_ACTION':
  return update(state, { 
    contents: { 
      1: {
        text: {$set: action.payload}
      }
    }
  });

Embora eu imagine que você provavelmente estaria fazendo algo mais parecido com isso?

case 'SOME_ACTION':
  return update(state, { 
    contents: { 
      [action.id]: {
        text: {$set: action.payload}
      }
    }
  });
Clarkie
fonte
de fato, preciso incluir esses auxiliares de reação dentro do meu redutor de alguma forma?
Ilja
8
Embora o pacote tenha mudado para kolodny/immutability-helper, agora é yarn add immutability-helpereimport update from 'immutability-helper';
SacWebDeveloper
Meu caso é exatamente o mesmo, mas quando eu atualizo o objeto em um dos elementos do array, o valor atualizado não reflete no componentWillReceiveProps. Eu uso o conteúdo diretamente em meu mapStateToProps, precisamos usar algo mais em mapStateToProps para que ele reflita?
Srishti Gupta
171

Você pode usar map. Aqui está um exemplo de implementação:

case 'SOME_ACTION':
   return { 
       ...state, 
       contents: state.contents.map(
           (content, i) => i === 1 ? {...content, text: action.payload}
                                   : content
       )
    }
Fatih Erikli
fonte
é assim que estou fazendo também, só não tenho certeza se é uma boa abordagem ou não, já que o mapa retornará um novo array. Alguma ideia?
Thiago C. S Ventura
@Ventura sim, é bom porque está criando uma nova referência para o array e um novo objeto simples para aquele que se pretende atualizar (e apenas aquele), que é exatamente o que você quer
ivcandela
3
Acho que esta é a melhor abordagem
Jesus Gomez
12
Eu também faço isso com frequência, mas parece relativamente caro computacionalmente iterar todos os elementos em vez de atualizar o índice necessário.
Ben Creasy,
linda! Eu amo isso !
Nate Ben
21

Você não precisa fazer tudo em uma linha:

case 'SOME_ACTION':
  const newState = { ...state };
  newState.contents = 
    [
      newState.contents[0],
      {title: newState.contnets[1].title, text: action.payload}
    ];
  return newState
Yuya
fonte
1
Você está certo, eu fiz uma solução rápida. btw, o comprimento do array é fixo? e o índice que você deseja modificar também é fixo? A abordagem atual só é viável com pequeno array e com índice fixo.
Yuya
2
envolva o corpo do seu caso em {}, uma vez que const tem escopo de bloco.
Benny Powers
13

Muito tarde para a festa, mas aqui está uma solução genérica que funciona com todos os valores de índice.

  1. Você cria e espalha um novo array, do antigo até o indexque deseja alterar.

  2. Adicione os dados que você deseja.

  3. Crie e espalhe um novo array a partir do que indexvocê deseja alterar até o final do array

let index=1;// probabbly action.payload.id
case 'SOME_ACTION':
   return { 
       ...state, 
       contents: [
          ...state.contents.slice(0,index),
          {title: "some other title", text: "some other text"},
         ...state.contents.slice(index+1)
         ]
    }

Atualizar:

Fiz um pequeno módulo para simplificar o código, então você só precisa chamar uma função:

case 'SOME_ACTION':
   return {
       ...state,
       contents: insertIntoArray(state.contents,index, {title: "some title", text: "some text"})
    }

Para mais exemplos, dê uma olhada no repositório

assinatura da função:

insertIntoArray(originalArray,insertionIndex,newData)
Ivan V.
fonte
4

Eu acredito que quando você precisa deste tipo de operação em seu estado Redux, o operador spread é seu amigo e este princípio se aplica a todas as crianças.

Vamos fingir que este é o seu estado:

const state = {
    houses: {
        gryffindor: {
          points: 15
        },
        ravenclaw: {
          points: 18
        },
        hufflepuff: {
          points: 7
        },
        slytherin: {
          points: 5
        }
    }
}

E você deseja adicionar 3 pontos à Corvinal

const key = "ravenclaw";
  return {
    ...state, // copy state
    houses: {
      ...state.houses, // copy houses
      [key]: {  // update one specific house (using Computed Property syntax)
        ...state.houses[key],  // copy that specific house's properties
        points: state.houses[key].points + 3   // update its `points` property
      }
    }
  }

Ao usar o operador de propagação, você pode atualizar apenas o novo estado, deixando todo o resto intacto.

Exemplo retirado deste artigo incrível , você pode encontrar quase todas as opções possíveis com ótimos exemplos.

Luis Rodriguez
fonte
3

No meu caso fiz algo assim, com base na resposta do Luis:

...State object...
userInfo = {
name: '...',
...
}

...Reducer's code...
case CHANGED_INFO:
return {
  ...state,
  userInfo: {
    ...state.userInfo,
    // I'm sending the arguments like this: changeInfo({ id: e.target.id, value: e.target.value }) and use them as below in reducer!
    [action.data.id]: action.data.value,
  },
};
Erfan226
fonte
0

Foi assim que fiz para um dos meus projetos:

const markdownSaveActionCreator = (newMarkdownLocation, newMarkdownToSave) => ({
  type: MARKDOWN_SAVE,
  saveLocation: newMarkdownLocation,
  savedMarkdownInLocation: newMarkdownToSave  
});

const markdownSaveReducer = (state = MARKDOWN_SAVED_ARRAY_DEFAULT, action) => {
  let objTemp = {
    saveLocation: action.saveLocation, 
    savedMarkdownInLocation: action.savedMarkdownInLocation
  };

  switch(action.type) {
    case MARKDOWN_SAVE:
      return( 
        state.map(i => {
          if (i.saveLocation === objTemp.saveLocation) {
            return Object.assign({}, i, objTemp);
          }
          return i;
        })
      );
    default:
      return state;
  }
};
TazExprez
fonte
0

Temo que usar o map()método de um array pode ser caro, uma vez que o array inteiro deve ser iterado. Em vez disso, eu combino uma nova matriz que consiste em três partes:

  • cabeça - itens antes do item modificado
  • o item modificado
  • cauda - itens após o item modificado

Aqui está o exemplo que usei no meu código (NgRx, mas o macanismo é o mesmo para outras implementações do Redux):

// toggle done property: true to false, or false to true

function (state, action) {
    const todos = state.todos;
    const todoIdx = todos.findIndex(t => t.id === action.id);

    const todoObj = todos[todoIdx];
    const newTodoObj = { ...todoObj, done: !todoObj.done };

    const head = todos.slice(0, todoIdx - 1);
    const tail = todos.slice(todoIdx + 1);
    const newTodos = [...head, newTodoObj, ...tail];
}
Damian Czapiewski
fonte