Come aggiornare un singolo valore all'interno di uno specifico elemento dell'array in redux


106

Ho un problema in cui il ri-rendering dello stato causa problemi all'interfaccia utente e mi è stato suggerito di aggiornare solo un valore specifico all'interno del mio riduttore per ridurre la quantità di ri-rendering su una pagina.

questo è un esempio del mio stato

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

e attualmente lo sto aggiornando in questo modo

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

dove action.payloadè un intero array contenente nuovi valori. Ma ora devo solo aggiornare il testo del secondo elemento nell'array dei contenuti, e qualcosa del genere non funziona

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

dove si action.payloadtrova ora un testo di cui ho bisogno per l'aggiornamento.

Risposte:


58

Potresti usare gli helper React Immutability

import update from 'react-addons-update';

// ...    

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

Anche se immagino che probabilmente faresti qualcosa di più simile a questo?

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

anzi, devo includere in qualche modo questi aiutanti da reagire all'interno del mio riduttore?
Ilja

8
Anche se il pacchetto è stato spostato kolodny/immutability-helper, quindi ora è yarn add immutability-helpereimport update from 'immutability-helper';
SacWebDeveloper

Il mio caso è esattamente lo stesso, ma quando aggiorno l'oggetto in uno degli elementi nell'array, il valore aggiornato non si riflette nel componentWillReceiveProps. Uso i contenuti direttamente nel mio mapStateToProps, dobbiamo usare qualcos'altro in mapStateToProps perché rifletta?
Srishti Gupta

171

Puoi usare map. Ecco un esempio di implementazione:

case 'SOME_ACTION':
   return { 
       ...state, 
       contents: state.contents.map(
           (content, i) => i === 1 ? {...content, text: action.payload}
                                   : content
       )
    }

è così che sto facendo anch'io, solo che non sono sicuro se sia un buon approccio o meno poiché map restituirà un nuovo array. qualche idea?
Thiago C. S Ventura

@Ventura sì, va bene perché sta creando un nuovo riferimento per l'array e un nuovo oggetto semplice per quello destinato ad essere aggiornato (e solo quello), che è esattamente quello che vuoi
ivcandela

3
Penso che questo sia l'approccio migliore
Jesus Gomez

12
Lo faccio spesso, ma sembra relativamente costoso dal punto di vista computazionale iterare su tutti gli elementi piuttosto che aggiornare l'indice necessario.
Ben Creasy

carino! Lo adoro !
Nate Ben

21

Non devi fare tutto in una riga:

case 'SOME_ACTION':
  const newState = { ...state };
  newState.contents = 
    [
      newState.contents[0],
      {title: newState.contnets[1].title, text: action.payload}
    ];
  return newState

1
Hai ragione, ho fatto una soluzione rapida. a proposito, la lunghezza dell'array è fissa? e anche l'indice che desideri modificare è fisso? L'attuale approccio è praticabile solo con array piccoli e con indice fisso.
Yuya

2
racchiudi il corpo del tuo caso in {}, poiché const ha un ambito di blocco.
Benny Powers

14

Molto tardi alla festa, ma ecco una soluzione generica che funziona con ogni valore di indice.

  1. Si crea e si diffonde un nuovo array dal vecchio array fino a quello che indexsi desidera modificare.

  2. Aggiungi i dati che desideri.

  3. Crea e diffondi un nuovo array da quello indexche desideri modificare alla fine dell'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)
         ]
    }

Aggiornare:

Ho creato un piccolo modulo per semplificare il codice, quindi devi solo chiamare una funzione:

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

Per ulteriori esempi, dai un'occhiata al repository

firma della funzione:

insertIntoArray(originalArray,insertionIndex,newData)

4

Credo che quando hai bisogno di questo tipo di operazioni sul tuo stato Redux, l'operatore di diffusione è tuo amico e questo principio si applica a tutti i bambini.

Facciamo finta che questo sia il tuo stato:

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

E vuoi aggiungere 3 punti a Corvonero

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
      }
    }
  }

Utilizzando l'operatore spread è possibile aggiornare solo il nuovo stato lasciando intatto tutto il resto.

Esempio tratto da questo fantastico articolo , puoi trovare quasi tutte le opzioni possibili con ottimi esempi.


3

Nel mio caso ho fatto qualcosa del genere, sulla base della risposta di 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,
  },
};

0

Ecco come l'ho fatto per uno dei miei progetti:

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;
  }
};

0

Temo che l'utilizzo del map()metodo di un array possa essere costoso poiché l'intero array deve essere iterato. Invece, combino un nuovo array che consiste di tre parti:

  • head - elementi prima dell'elemento modificato
  • l'elemento modificato
  • coda : elementi dopo l'elemento modificato

Ecco l'esempio che ho usato nel mio codice (NgRx, ma il meccanismo è lo stesso per altre implementazioni 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];
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.