Cómo actualizar un valor único dentro de un elemento de matriz específico en redux

106

Tengo un problema en el que la reproducción del estado causa problemas de interfaz de usuario y se sugirió que solo actualice un valor específico dentro de mi reductor para reducir la cantidad de reproducción en una página.

este es un ejemplo de mi estado

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

y actualmente lo estoy actualizando así

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

donde action.payloades una matriz completa que contiene nuevos valores. Pero ahora solo necesito actualizar el texto del segundo elemento en la matriz de contenido, y algo como esto no funciona

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

¿Dónde action.payloadestá ahora un texto que necesito para actualizar?

Ilja
fuente

Respuestas:

58

Podrías usar los ayudantes de React Immutability

import update from 'react-addons-update';

// ...    

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

Aunque me imagino que probablemente estarías haciendo algo más como esto.

case 'SOME_ACTION':
  return update(state, { 
    contents: { 
      [action.id]: {
        text: {$set: action.payload}
      }
    }
  });
Clarkie
fuente
de hecho, ¿necesito incluir estos ayudantes de reaccionar dentro de mi reductor de alguna manera?
Ilja
8
Aunque el paquete se ha trasladado a kolodny/immutability-helper, ahora es yarn add immutability-helperyimport update from 'immutability-helper';
SacWebDeveloper
Mi caso es exactamente el mismo, pero cuando actualizo el objeto en uno de los elementos de la matriz, el valor actualizado no se refleja en el componente WillReceiveProps. Utilizo contenidos directamente en mi mapStateToProps, ¿tenemos que usar algo más en mapStateToProps para que se refleje?
Srishti Gupta
171

Puede utilizar map. Aquí hay una implementación de ejemplo:

case 'SOME_ACTION':
   return { 
       ...state, 
       contents: state.contents.map(
           (content, i) => i === 1 ? {...content, text: action.payload}
                                   : content
       )
    }
Fatih Erikli
fuente
así es como lo estoy haciendo también, simplemente no estoy seguro de si es un buen enfoque o no, ya que el mapa devolverá una nueva matriz. ¿Alguna idea?
Thiago C. S Ventura
@Ventura sí, es bueno porque está creando una nueva referencia para la matriz y un objeto nuevo y simple para el que se pretende actualizar (y solo ese), que es exactamente lo que quieres
ivcandela
3
Creo que este es el mejor enfoque
Jesus Gomez
12
También hago esto a menudo, pero parece relativamente costoso computacionalmente iterar sobre todos los elementos en lugar de actualizar el índice necesario.
Ben Creasy
bonita ! Me encanta !
Nate Ben
21

No tienes que hacer todo en una sola línea:

case 'SOME_ACTION':
  const newState = { ...state };
  newState.contents = 
    [
      newState.contents[0],
      {title: newState.contnets[1].title, text: action.payload}
    ];
  return newState
Yuya
fuente
1
Tienes razón, hice una solución rápida. Por cierto, ¿la matriz tiene una longitud fija? y ¿el índice que desea modificar también es fijo? El enfoque actual solo es viable con una matriz pequeña y con un índice fijo.
Yuya
2
envuelve el cuerpo de tu caso en {}, ya que const tiene un alcance de bloque.
Benny Powers
14

Muy tarde para la fiesta, pero aquí hay una solución genérica que funciona con todos los valores de índice.

  1. Usted crea y difunde una nueva matriz desde la matriz anterior hasta la indexque desea cambiar.

  2. Agregue los datos que desee.

  3. Cree y difunda una nueva matriz desde la indexque desea cambiar hasta el final de la matriz

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

Actualizar:

He creado un pequeño módulo para simplificar el código, por lo que solo necesita llamar a una función:

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

Para ver más ejemplos, eche un vistazo al repositorio.

firma de función:

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

Creo que cuando necesita este tipo de operaciones en su estado de Redux, el operador de propagación es su amigo y este principio se aplica a todos los niños.

Supongamos que este es tu estado:

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

Y quieres sumar 3 puntos a 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
      }
    }
  }

Al usar el operador de propagación, puede actualizar solo el nuevo estado dejando todo lo demás intacto.

Ejemplo tomado de este increíble artículo , puede encontrar casi todas las opciones posibles con excelentes ejemplos.

Luis Rodríguez
fuente
3

En mi caso hice algo como esto, basándome en la respuesta de 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
fuente
0

Así lo hice para uno de mis proyectos:

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

Me temo que usar el map()método de una matriz puede ser costoso ya que se debe iterar la matriz completa. En su lugar, combino una nueva matriz que consta de tres partes:

  • head : elementos antes del elemento modificado
  • el artículo modificado
  • cola : elementos posteriores al elemento modificado

Aquí el ejemplo que he usado en mi código (NgRx, pero el mecanismo es el mismo para otras implementaciones de 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
fuente