Actualización de un objeto con setState en React

265

¿Es posible actualizar las propiedades del objeto setState?

Algo como:

this.state = {
   jasper: { name: 'jasper', age: 28 },
}

Yo he tratado:

this.setState({jasper.name: 'someOtherName'});

y esto:

this.setState({jasper: {name: 'someothername'}})

El primero produce un error de sintaxis y el segundo simplemente no hace nada. ¿Algunas ideas?

JohnSnow
fuente
55
el segundo código habría funcionado, sin embargo, habría perdido la agepropiedad dentro jasper.
giorgim

Respuestas:

579

Hay varias formas de hacerlo, ya que la actualización de estado es una operación asíncrona , por lo que para actualizar el objeto de estado, necesitamos usar la función de actualización con setState.

1- El más simple:

Primero cree una copia de jasperluego haga los cambios en eso:

this.setState(prevState => {
  let jasper = Object.assign({}, prevState.jasper);  // creating copy of state variable jasper
  jasper.name = 'someothername';                     // update the name property, assign a new value                 
  return { jasper };                                 // return new object jasper object
})

En lugar de usar Object.assign, también podemos escribirlo así:

let jasper = { ...prevState.jasper };

2- Uso del operador de propagación :

this.setState(prevState => ({
    jasper: {                   // object that we want to update
        ...prevState.jasper,    // keep all other key-value pairs
        name: 'something'       // update the value of specific key
    }
}))

Nota: Object.assign y Spread Operatorcrea solo una copia superficial , por lo que si ha definido un objeto anidado o una matriz de objetos, necesita un enfoque diferente.


Actualizando objeto de estado anidado:

Suponga que ha definido el estado como:

this.state = {
  food: {
    sandwich: {
      capsicum: true,
      crackers: true,
      mayonnaise: true
    },
    pizza: {
      jalapeno: true,
      extraCheese: false
    }
  }
}

Para actualizar el objeto extraCheese of pizza:

this.setState(prevState => ({
  food: {
    ...prevState.food,           // copy all other key-value pairs of food object
    pizza: {                     // specific object of food object
      ...prevState.food.pizza,   // copy all pizza key-value pairs
      extraCheese: true          // update value of specific key
    }
  }
}))

Actualización de matriz de objetos:

Supongamos que tiene una aplicación de tareas pendientes y está administrando los datos de esta forma:

this.state = {
  todoItems: [
    {
      name: 'Learn React Basics',
      status: 'pending'
    }, {
      name: 'Check Codebase',
      status: 'pending'
    }
  ]
}

Para actualizar el estado de cualquier objeto de tareas pendientes, ejecute un mapa en la matriz y verifique si hay algún valor único de cada objeto, en caso de que condition=truedevuelva el nuevo objeto con valor actualizado, de lo contrario, el mismo objeto.

let key = 2;
this.setState(prevState => ({

  todoItems: prevState.todoItems.map(
    el => el.key === key? { ...el, status: 'done' }: el
  )

}))

Sugerencia: si el objeto no tiene un valor único, utilice el índice de matriz.

Mayank Shukla
fuente
La forma en que lo intentaba ahora funciona ... la segunda forma: S
JohnSnow
77
@JohnSnow en tu segundo eliminará las otras propiedades del jasperobjeto, console.log(jasper)verás solo una clave name, la edad no estará allí :)
Mayank Shukla
1
@ JohnSnow, sí, de esta manera es apropiado si jasperes un object, no sé si es mejor o no, puede haber otras soluciones posibles :)
Mayank Shukla
66
Es bueno mencionar aquí que Object.assignni el operador de propagación ni las propiedades de copia profunda. De esta manera, debe usar soluciones alternativas como lodash, deepCopyetc.
Alex Vasilev
1
¿Qué tal let { jasper } = this.state?
Brian Le
37

Esta es la forma más rápida y fácil de leer:

this.setState({...this.state.jasper, name: 'someothername'});

Incluso si this.state.jasperya contiene una propiedad de nombre, name: 'someothername'se utilizará el nuevo nombre .

mannok
fuente
38
Esta solución tiene una gran desventaja: para optimizar las actualizaciones de estado, React podría agrupar varias actualizaciones. Como consecuencia this.state.jasper, no necesariamente contiene el último estado. Mejor use la notación con el prevState.
pecó el
33

Use el operador de propagación y algunos ES6 aquí

this.setState({
    jasper: {
          ...this.state.jasper,
          name: 'something'
    }
})
Nikhil
fuente
Esto actualiza jasper pero otras propiedades se eliminan del estado.
iaq
11

Usé esta solución.

Si tiene un estado anidado como este:

this.state = {
  formInputs:{
    friendName:{
      value:'',
      isValid:false,
      errorMsg:''
    },
    friendEmail:{
      value:'',
      isValid:false,
      errorMsg:''
    }
  }
}

puede declarar la función handleChange que copia el estado actual y la reasigna con valores cambiados

handleChange(el) {
    let inputName = el.target.name;
    let inputValue = el.target.value;

    let statusCopy = Object.assign({}, this.state);
    statusCopy.formInputs[inputName].value = inputValue;

    this.setState(statusCopy);
  }

aquí el html con el oyente de eventos. Asegúrese de utilizar el mismo nombre utilizado en el objeto de estado (en este caso, 'nombreDeAmigo')

<input type="text" onChange={this.handleChange} " name="friendName" />
Alberto Piras
fuente
Esto funcionó para mí, excepto que tuve que usar esto en su lugar:statusCopy.formInputs[inputName] = inputValue;
MikeyE
9

Sé que hay muchas respuestas aquí, pero me sorprende que ninguna de ellas cree una copia del nuevo objeto fuera de setState, y luego simplemente setState ({newObject}). Limpio, conciso y confiable. Entonces en este caso:

const jasper = { ...this.state.jasper, name: 'someothername' }
this.setState(() => ({ jasper }))

O para una propiedad dinámica (muy útil para formularios)

const jasper = { ...this.state.jasper, [VarRepresentingPropertyName]: 'new value' }
this.setState(() => ({ jasper }))

colemerrick
fuente
7

prueba esto, debería funcionar bien

this.setState(Object.assign(this.state.jasper,{name:'someOtherName'}));
Burak Kahraman
fuente
44
Estás mutando el objeto de estado directamente. Para usar este enfoque, agregue un nuevo objeto como fuente:Object.assign({}, this.state.jasper, {name:'someOtherName'})
Albizia
3

El primer caso es de hecho un error de sintaxis.

Como no puedo ver el resto de su componente, es difícil ver por qué está anidando objetos en su estado aquí. No es una buena idea anidar objetos en estado componente. Intente configurar su estado inicial para que sea:

this.state = {
  name: 'jasper',
  age: 28
}

De esa manera, si desea actualizar el nombre, simplemente puede llamar:

this.setState({
  name: 'Sean'
});

¿Logrará eso lo que busca?

Para almacenes de datos más grandes y complejos, usaría algo como Redux. Pero eso es mucho más avanzado.

La regla general con el estado del componente es usarlo solo para administrar el estado de la interfaz de usuario del componente (por ejemplo, activo, temporizadores, etc.)

Echa un vistazo a estas referencias:

Mccambridge
fuente
1
Yo sólo se utiliza esto como un ejemplo, el objeto debe estar anidada .. Probablemente debería usar Redux, pero estoy tratando de entender Reaccionar fundementals ..
JohnSnow
2
Sí, me mantendría alejado de Redux por ahora. Mientras aprende los fundamentos, trate de mantener sus datos simples. Eso te ayudará a evitar casos extraños que te hagan tropezar. 👍
mccambridge
2

Otra opción: defina su variable fuera del objeto Jasper y luego simplemente llame a una variable.

Operador extendido: ES6

this.state = {  jasper: { name: 'jasper', age: 28 } } 

let foo = "something that needs to be saved into state" 

this.setState(prevState => ({
    jasper: {
        ...jasper.entity,
        foo
    }
})
Luis Martins
fuente
2

Forma simple y dinámica.

Esto hará el trabajo, pero debe establecer todos los identificadores para el padre para que apunte al nombre del objeto, siendo id = "jasper" y nombre el nombre del elemento de entrada = propiedad dentro del objeto jasper .

handleChangeObj = ({target: { id , name , value}}) => this.setState({ [id]: { ...this.state[id] , [name]: value } });
Alejandro Sanchez Duran
fuente
Estoy de acuerdo con la primera línea, ya que esto es lo único que funcionó para mi objeto de objetos en estado. También se aclaró dónde estaba atascado al actualizar mis datos. Con mucho, la forma más simple y dinámica ... ¡Gracias!
Smit
2

Puede intentar con esto : ( Nota : nombre de la etiqueta de entrada === campo del objeto)

<input name="myField" type="text" 
      value={this.state.myObject.myField} 
     onChange={this.handleChangeInpForm}>
</input>

-----------------------------------------------------------
handleChangeInpForm = (e) => {
   let newObject = this.state.myObject;
   newObject[e.target.name] = e.target.value;
   this.setState({
     myObject: newObject 
   })
}
Xuanduong Ngo
fuente
Bienvenido a Stack Overflow. Si bien este código puede responder la pregunta, proporcionar un contexto adicional con respecto a por qué y / o cómo responde la pregunta mejora su valor a largo plazo. Cómo responder
Elletlar
2

Esta es otra solución que utiliza la utilidad immer immutabe, muy adecuada para objetos anidados profundamente con facilidad, y no debe preocuparse por la mutación

this.setState(
    produce(draft => {
       draft.jasper.name = 'someothername
    })
)
samehanwar
fuente
1

Además, siguiendo la solución de Alberto Piras, si no desea copiar todo el objeto "estado":

handleChange(el) {
    let inputName = el.target.name;
    let inputValue = el.target.value;

    let jasperCopy = Object.assign({}, this.state.jasper);
    jasperCopy[inputName].name = inputValue;

    this.setState({jasper: jasperCopy});
  }
Marc LaQuay
fuente
1

Puedes probar con esto:

this.setState(prevState => {
   prevState = JSON.parse(JSON.stringify(this.state.jasper));
   prevState.name = 'someOtherName';
   return {jasper: prevState}
})

o para otra propiedad:

this.setState(prevState => {
   prevState = JSON.parse(JSON.stringify(this.state.jasper));
   prevState.age = 'someOtherAge';
   return {jasper: prevState}
})

O puede usar la función handleChage:

handleChage(event) {
   const {name, value} = event.target;
    this.setState(prevState => {
       prevState = JSON.parse(JSON.stringify(this.state.jasper));
       prevState[name] = value;
       return {jasper: prevState}
    })
}

y código HTML:

<input 
   type={"text"} 
   name={"name"} 
   value={this.state.jasper.name} 
   onChange={this.handleChange}
/>
<br/>
<input 
   type={"text"} 
   name={"age"} 
   value={this.state.jasper.age} 
   onChange={this.handleChange}
/>
Nemanja Stojanovic
fuente
Esto funciona sin JSON.stringify .. this.setState (prevState => {prevState = this.state.document; prevState.ValidDate = event.target.value; prevState.ValidDateFormat = dayFormat; return {document: prevState}}); ..Where documento es de tipo estado del objeto ..
Oleg Borodko
1

Sin usar Async y Await Use esto ...

funCall(){    
     this.setState({...this.state.jasper, name: 'someothername'});
}

Si usa Async And Await, use esto ...

async funCall(){
      await this.setState({...this.state.jasper, name: 'someothername'});
}
Shantanu Sharma
fuente
0

Esta configuración funcionó para mí:

let newState = this.state.jasper;
newState.name = 'someOtherName';

this.setState({newState: newState});

console.log(this.state.jasper.name); //someOtherName
LaZza
fuente