Jak zaktualizować pojedynczą wartość w określonym elemencie tablicy w redux

107

Mam problem polegający na tym, że ponowne renderowanie stanu powoduje problemy z interfejsem użytkownika i zasugerowano, aby zaktualizować tylko określoną wartość w moim reduktorze, aby zmniejszyć ilość ponownego renderowania na stronie.

to jest przykład mojego stanu

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

i obecnie aktualizuję to w ten sposób

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

gdzie action.payloadjest całą tablicą zawierającą nowe wartości. Ale teraz muszę tylko zaktualizować tekst drugiej pozycji w tablicy content, a coś takiego nie działa

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

gdzie action.payloadjest teraz tekst, którego potrzebuję do aktualizacji.

Ilja
źródło

Odpowiedzi:

59

Możesz użyć pomocników React Immutability

import update from 'react-addons-update';

// ...    

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

Chociaż wyobrażam sobie, że prawdopodobnie będziesz robić coś więcej takiego?

case 'SOME_ACTION':
  return update(state, { 
    contents: { 
      [action.id]: {
        text: {$set: action.payload}
      }
    }
  });
Clarkie
źródło
rzeczywiście, czy muszę jakoś uwzględnić te pomocniki z reagowania wewnątrz mojego reduktora?
Ilja,
8
Chociaż pakiet został przeniesiony do kolodny/immutability-helper, więc teraz jest yarn add immutability-helperiimport update from 'immutability-helper';
SacWebDeveloper
Mój przypadek jest dokładnie taki sam, ale kiedy aktualizuję obiekt w jednym z elementów tablicy, zaktualizowana wartość nie jest odzwierciedlana w komponencie componentWillReceiveProps. Używam zawartości bezpośrednio w moim mapStateToProps, czy musimy użyć czegoś innego w mapStateToProps, aby to odzwierciedlało?
Srishti Gupta
173

Możesz użyć map. Oto przykładowa realizacja:

case 'SOME_ACTION':
   return { 
       ...state, 
       contents: state.contents.map(
           (content, i) => i === 1 ? {...content, text: action.payload}
                                   : content
       )
    }
Fatih Erikli
źródło
ja też to robię, po prostu nie jestem pewien, czy jest to dobre podejście, czy nie, ponieważ map zwróci nową tablicę. jakieś pomysły?
Thiago C. S Ventura
@Ventura tak, jest dobrze, ponieważ tworzy nowe odniesienie dla tablicy i zwykły nowy obiekt dla tego, który ma być zaktualizowany (i tylko ten), co jest dokładnie tym, czego chcesz
ivcandela,
3
Myślę, że to najlepsze podejście
Jesus Gomez
12
Robię to często, ale iteracja wszystkich elementów wydaje się stosunkowo kosztowna obliczeniowo, zamiast aktualizować niezbędny indeks.
Ben Creasy,
ładna ! Kocham to !
Nate Ben
21

Nie musisz robić wszystkiego w jednej linii:

case 'SOME_ACTION':
  const newState = { ...state };
  newState.contents = 
    [
      newState.contents[0],
      {title: newState.contnets[1].title, text: action.payload}
    ];
  return newState
Yuya
źródło
1
Masz rację, zrobiłem szybką naprawę. btw, czy tablica ma stałą długość? i czy indeks, który chcesz zmodyfikować, również jest naprawiony? Obecne podejście jest możliwe tylko w przypadku małej tablicy i ze stałym indeksem.
Yuya,
2
zawiń treść sprawy w {}, ponieważ const ma zasięg blokowy.
Benny Powers
15

Bardzo późno na imprezę, ale tutaj jest ogólne rozwiązanie, które działa z każdą wartością indeksu.

  1. Tworzysz i rozprzestrzeniasz nową tablicę od starej do tej, indexktórą chcesz zmienić.

  2. Dodaj żądane dane.

  3. Utwórz i rozprowadź nową tablicę od tej, indexktórą chciałeś zmienić, do końca tablicy

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

Aktualizacja:

Zrobiłem mały moduł, aby uprościć kod, więc wystarczy wywołać funkcję:

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

Więcej przykładów znajdziesz w repozytorium

podpis funkcji:

insertIntoArray(originalArray,insertionIndex,newData)
Ivan V.
źródło
4

Wierzę, że kiedy potrzebujesz tego rodzaju operacji na swoim stanie Redux, operator spreadu jest twoim przyjacielem i ta zasada dotyczy wszystkich dzieci.

Udawajmy, że to twój stan:

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

I chcesz dodać 3 punkty do Ravenclaw

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

Używając operatora spreadu, możesz zaktualizować tylko nowy stan, pozostawiając wszystko inne nienaruszone.

Przykład zaczerpnięty z tego niesamowitego artykułu , możesz znaleźć prawie każdą możliwą opcję ze świetnymi przykładami.

Luis Rodriguez
źródło
3

W moim przypadku zrobiłem coś takiego, bazując na odpowiedzi Luisa:

...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
źródło
0

Tak to zrobiłem dla jednego z moich projektów:

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
źródło
0

Obawiam się, że użycie map()metody tablicowej może być kosztowne, ponieważ cała tablica ma być iterowana. Zamiast tego łączę nową tablicę, która składa się z trzech części:

  • head - pozycje przed modyfikowaną pozycją
  • zmodyfikowany element
  • tail - elementy po zmodyfikowanym elemencie

Oto przykład, którego użyłem w moim kodzie (NgRx, ale machanizm jest taki sam dla innych implementacji 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
źródło