Cara memperbarui nilai tunggal di dalam item array tertentu di redux

106

Saya memiliki masalah di mana perenderan ulang keadaan menyebabkan masalah ui dan disarankan untuk hanya memperbarui nilai tertentu di dalam peredam saya untuk mengurangi jumlah perenderan ulang pada halaman.

ini adalah contoh negara saya

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

dan saya sedang memperbaruinya seperti ini

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

di mana action.payloadseluruh larik berisi nilai baru. Tapi sekarang saya sebenarnya hanya perlu memperbarui teks dari item kedua dalam larik konten, dan sesuatu seperti ini tidak berfungsi

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

di mana action.payloadsekarang ada teks yang saya butuhkan untuk pembaruan.

Ilja
sumber

Jawaban:

58

Anda bisa menggunakan pembantu React Immutability

import update from 'react-addons-update';

// ...    

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

Meskipun saya membayangkan Anda mungkin akan melakukan sesuatu yang lebih seperti ini?

case 'SOME_ACTION':
  return update(state, { 
    contents: { 
      [action.id]: {
        text: {$set: action.payload}
      }
    }
  });
Clarkie
sumber
memang, apakah saya perlu menyertakan pembantu ini dari bereaksi di dalam peredam saya?
Ilja
8
Meskipun paket telah dipindahkan ke kolodny/immutability-helper, jadi sekarang yarn add immutability-helperdanimport update from 'immutability-helper';
SacWebDeveloper
Kasus saya persis sama, tetapi ketika saya memperbarui objek di salah satu elemen dalam array, nilai yang diperbarui tidak tercermin di componentWillReceiveProps. Saya menggunakan konten langsung di mapStateToProps saya, apakah kita harus menggunakan sesuatu yang lain di mapStateToProps untuk mencerminkannya?
Srishti Gupta
173

Anda bisa menggunakan map. Berikut ini contoh penerapannya:

case 'SOME_ACTION':
   return { 
       ...state, 
       contents: state.contents.map(
           (content, i) => i === 1 ? {...content, text: action.payload}
                                   : content
       )
    }
Fatih Erikli
sumber
begitulah yang saya lakukan juga, saya hanya tidak yakin apakah itu pendekatan yang baik atau tidak karena map akan mengembalikan array baru. Ada pemikiran?
Thiago C. S Ventura
@Ventura ya, itu bagus karena itu membuat referensi baru untuk array dan objek baru yang polos untuk yang dimaksudkan untuk diperbarui (dan hanya yang itu), yang persis seperti yang Anda inginkan
ivcandela
3
Saya pikir ini adalah pendekatan terbaik
Jesus Gomez
12
Saya sering melakukan ini juga, tetapi tampaknya relatif mahal secara komputasi untuk mengulang semua elemen daripada memperbarui indeks yang diperlukan.
Ben Creasy
yang cantik ! Aku menyukainya !
Nate Ben
21

Anda tidak harus melakukan semuanya dalam satu baris:

case 'SOME_ACTION':
  const newState = { ...state };
  newState.contents = 
    [
      newState.contents[0],
      {title: newState.contnets[1].title, text: action.payload}
    ];
  return newState
Yuya
sumber
1
Anda benar, saya melakukan perbaikan cepat. btw, apakah panjang array tetap? dan apakah indeks yang ingin Anda ubah juga sudah diperbaiki? Pendekatan saat ini hanya dapat dijalankan dengan larik kecil dan dengan indeks tetap.
Yuya
2
membungkus tubuh kasus Anda dalam {}, karena const memiliki cakupan blok.
Benny Powers
15

Sangat terlambat untuk pesta tetapi ini adalah solusi umum yang bekerja dengan setiap nilai indeks.

  1. Anda membuat dan menyebarkan array baru dari array lama hingga yang indexingin Anda ubah.

  2. Tambahkan data yang Anda inginkan.

  3. Buat dan sebarkan array baru dari yang indexingin Anda ubah ke akhir 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)
         ]
    }

Memperbarui:

Saya telah membuat modul kecil untuk menyederhanakan kode, jadi Anda hanya perlu memanggil suatu fungsi:

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

Untuk contoh lainnya, lihat repositori

fungsi tanda tangan:

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

Saya percaya ketika Anda membutuhkan operasi semacam ini di negara bagian Redux Anda , operator penyebaran adalah teman Anda dan prinsip ini berlaku untuk semua anak.

Anggaplah ini negara bagian Anda:

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

Dan Anda ingin menambahkan 3 poin ke 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
      }
    }
  }

Dengan menggunakan operator penyebaran, Anda hanya dapat memperbarui status baru dengan membiarkan yang lainnya tetap utuh.

Contoh yang diambil dari artikel luar biasa ini , Anda dapat menemukan hampir setiap opsi yang memungkinkan dengan contoh yang bagus.

Luis Rodriguez
sumber
3

Dalam kasus saya, saya melakukan sesuatu seperti ini, berdasarkan jawaban 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,
  },
};
Erfan 226
sumber
0

Beginilah cara saya melakukannya untuk salah satu proyek saya:

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
sumber
0

Saya khawatir bahwa menggunakan map()metode array mungkin mahal karena seluruh array akan diiterasi. Sebagai gantinya, saya menggabungkan array baru yang terdiri dari tiga bagian:

  • head - item sebelum item yang dimodifikasi
  • item yang dimodifikasi
  • tail - item setelah item diubah

Berikut contoh yang saya gunakan dalam kode saya (NgRx, namun machanismenya sama untuk implementasi Redux lainnya):

// 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
sumber