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.