Atualizando um objeto com setState em React

265

É possível atualizar as propriedades do objeto setState?

Algo como:

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

Eu tentei:

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

e isto:

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

O primeiro resulta em um erro de sintaxe e o segundo simplesmente não faz nada. Alguma ideia?

JohnSnow
fonte
5
segundo código teria funcionado no entanto, você teria perdido a agepropriedade dentro jasper.
giorgim

Respostas:

577

Existem várias maneiras de fazer isso, já que a atualização de estado é uma operação assíncrona ; portanto, para atualizar o objeto de estado, precisamos usar a função atualizador com setState.

1- Um mais simples:

Primeiro, crie uma cópia jaspere faça as alterações:

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

Em vez de usar Object.assign, também podemos escrever assim:

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

2- Usando o operador de propagação :

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 e Spread Operatorcria apenas uma cópia superficial , portanto, se você definiu um objeto aninhado ou uma matriz de objetos, precisará de uma abordagem diferente.


Atualizando objeto de estado aninhado:

Suponha que você definiu o estado como:

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

Para atualizar o objeto extra de queijo de 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
    }
  }
}))

Atualizando matriz de objetos:

Vamos supor que você tenha um aplicativo de tarefas pendentes e que esteja gerenciando os dados neste formulário:

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

Para atualizar o status de qualquer objeto todo, execute um mapa na matriz e verifique se há algum valor exclusivo de cada objeto; no caso condition=true, retorne o novo objeto com valor atualizado, caso contrário, o mesmo objeto.

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

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

}))

Sugestão: Se o objeto não tiver um valor exclusivo, use o índice da matriz.

Mayank Shukla
fonte
A maneira que eu estava tentando agora trabalha embora ... A segunda maneira: S
JohnSnow
7
@JohnSnow em você segundo ele irá remover as outras propriedades do jasperobjeto, faça o console.log(jasper)que você vai ver apenas uma chave name, idade não vai estar lá :)
Mayank Shukla
1
@JohnSnow, sim desta forma é adequada, se jasperé um object, não o seja melhor ou não, pode haver outras soluções melhores são possíveis :)
Mayank Shukla
6
É bom mencionar aqui que Object.assignnem espalham propriedades profundas de cópia do operador. Desta forma, você deve usar soluções como lodash deepCopyetc.
Alex Vasilev
1
Que tal let { jasper } = this.state?
Brian Le
37

Esta é a maneira mais rápida e mais legível:

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

Mesmo que this.state.jasperjá contenha uma propriedade name, o novo nome name: 'someothername'será usado.

mannok
fonte
38
Essa solução tem uma grande desvantagem: para otimizar as atualizações de estado, o React pode agrupar várias atualizações. Como conseqüência this.state.jasper, não contém necessariamente o estado mais recente. Melhor usar a notação com o prevState.
pecou em
33

Use o operador spread e alguns ES6 aqui

this.setState({
    jasper: {
          ...this.state.jasper,
          name: 'something'
    }
})
Nikhil
fonte
Isso atualiza o jasper, mas outras propriedades são removidas do estado.
iaq 13/04
11

Eu usei esta solução.

Se você tiver um estado aninhado como este:

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

você pode declarar a função handleChange que copia o status atual e o atribui novamente com valores alterados

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

aqui o html com o ouvinte de evento. Certifique-se de usar o mesmo nome usado no objeto de estado (neste caso 'friendName')

<input type="text" onChange={this.handleChange} " name="friendName" />
Alberto Piras
fonte
Isso funcionou para mim, exceto que eu tive que usar isso:statusCopy.formInputs[inputName] = inputValue;
MikeyE
9

Sei que há muitas respostas aqui, mas estou surpreso que nenhuma delas crie uma cópia do novo objeto fora do setState e, em seguida, simplesmente setState ({newObject}). Limpo, conciso e confiável. Então, neste caso:

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

Ou para uma propriedade dinâmica (muito útil para formulários)

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

colemerrick
fonte
7

tente isso, deve funcionar bem

this.setState(Object.assign(this.state.jasper,{name:'someOtherName'}));
Burak Kahraman
fonte
4
Você está mutando o objeto de estado diretamente. Para usar essa abordagem, adicione um novo objeto como fonte:Object.assign({}, this.state.jasper, {name:'someOtherName'})
Albizia
3

O primeiro caso é realmente um erro de sintaxe.

Como não consigo ver o restante do seu componente, é difícil entender por que você está aninhando objetos em seu estado aqui. Não é uma boa idéia aninhar objetos no estado do componente. Tente definir seu estado inicial como:

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

Dessa forma, se você deseja atualizar o nome, basta ligar para:

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

Isso alcançará o que você está buscando?

Para armazenamentos de dados maiores e mais complexos, eu usaria algo como Redux. Mas isso é muito mais avançado.

A regra geral com o estado do componente é usá-lo apenas para gerenciar o estado da interface do usuário do componente (por exemplo, ativo, temporizadores, etc.)

Confira estas referências:

Mccambridge
fonte
1
Eu só usei isso como um exemplo, o objeto deve ser aninhados .. Eu provavelmente deveria estar usando Redux, mas eu estou tentando compreender Reagir fundementals ..
JohnSnow
2
Sim, eu ficaria longe do Redux por enquanto. Enquanto você aprende os fundamentos, tente manter seus dados simples. Isso ajudará a evitar casos estranhos que o atrapalham. M
mccambridge
2

Outra opção: defina sua variável a partir do objeto Jasper e chame apenas uma variável.

Operador de spread: 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
fonte
2

Maneira simples e dinâmica.

Isso fará o trabalho, mas você precisa definir todos os IDs para o pai, para que o pai aponte para o nome do objeto, sendo id = "jasper" e nomeie o nome do elemento de entrada = propriedade dentro do objeto jasper .

handleChangeObj = ({target: { id , name , value}}) => this.setState({ [id]: { ...this.state[id] , [name]: value } });
Alejandro Sanchez Duran
fonte
Concordo com a primeira linha, pois esta é a única coisa que funcionou para o meu objeto de objetos no estado! Também ficou claro onde eu estava preso na atualização dos meus dados. De longe a maneira mais simples e dinâmica ... Obrigado !!
Smit
2

Você pode tentar com isso : ( Nota : nome da tag de entrada === campo do 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
fonte
Bem-vindo ao Stack Overflow. Embora esse código possa responder à pergunta, fornecer um contexto adicional a respeito de por que e / ou como esse código responde à pergunta melhora seu valor a longo prazo. Como responder
Elletlar 28/01/19
2

esta é outra solução usando o utilitário immer immutabe, muito adequado para objetos profundamente aninhados com facilidade e você não deve se preocupar com mutações

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

Além disso, seguindo a solução de Alberto Piras, se você não deseja copiar todo o objeto "state":

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
fonte
1

Você pode tentar com isso:

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

ou para outras propriedades:

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

Ou você pode usar a função 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}
    })
}

e 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
fonte
Isso funciona sem JSON.stringify .. this.setState (prevState => {prevState = this.state.document; prevState.ValidDate = event.target.value; prevState.ValidDateFormat = dayFormat; return {document: prevState}}); Documento ..Where é o tipo de estado de objeto ..
Oleg Borodko
1

Sem usar Async e Aguardar Use este ...

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

Se você estiver usando com Async And Await, use este ...

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

Essa configuração funcionou para mim:

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

this.setState({newState: newState});

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