Modificação correta de matrizes de estado no ReactJS

416

Eu quero adicionar um elemento ao final de uma statematriz, esta é a maneira correta de fazer isso?

this.state.arrayvar.push(newelement);
this.setState({arrayvar:this.state.arrayvar});

Estou preocupado que modificar a matriz no local pushpossa causar problemas - é seguro?

A alternativa de fazer uma cópia da matriz e setStateisso parece um desperdício.

fadedbee
fonte
9
Eu acho que você pode usar auxiliares de imutabilidade a reagir. veja isto: facebook.github.io/react/docs/update.html#simple-push
nilgun
setState na matriz de estados verifique minha solução. <br/> <br> stackoverflow.com/a/59711447/9762736
Rajnikant Lodhi

Respostas:

763

Os documentos do React dizem:

Trate esse estado como se fosse imutável.

Você pushmodificará o estado diretamente e isso poderá levar a um código propenso a erros, mesmo se você estiver "redefinindo" o estado novamente depois. Por exemplo, isso pode levar a que alguns métodos do ciclo de vida componentDidUpdatenão sejam acionados.

A abordagem recomendada nas versões posteriores do React é usar uma função atualizadora ao modificar estados para evitar condições de corrida:

this.setState(prevState => ({
  arrayvar: [...prevState.arrayvar, newelement]
}))

O "desperdício" de memória não é um problema comparado aos erros que você pode enfrentar ao usar modificações de estado não padrão.

Sintaxe alternativa para versões anteriores do React

Você pode usar concatpara obter uma sintaxe limpa, pois retorna uma nova matriz:

this.setState({ 
  arrayvar: this.state.arrayvar.concat([newelement])
})

No ES6, você pode usar o Operador de propagação :

this.setState({
  arrayvar: [...this.state.arrayvar, newelement]
})
David Hellsing
fonte
8
Você pode fornecer um exemplo de quando uma condição de corrida ocorreria?
soundly_typed
3
@Qiming pushretorna o novo tamanho da matriz para que não funcione. Além disso, setStateé assíncrono e o React pode enfileirar várias alterações de estado em uma única passagem de renderização.
David Hellsing
2
@mindeavor diz que você tem um animationFrame que procura parâmetros neste estado e outro método que altera alguns outros parâmetros na alteração de estado. Pode haver alguns quadros em que o estado foi alterado, mas não refletido no método que atende à alteração, porque setState é assíncrono.
David Hellsing
1
@ChristopherCamps Esta resposta não incentiva a chamada setStateduas vezes, mostra dois exemplos semelhantes de configuração da matriz de estados sem alterá-la diretamente.
David Hellsing
2
Uma maneira fácil de tratar uma matriz de estados como imutável atualmente é: let list = Array.from(this.state.list); list.push('woo'); this.setState({list});Modifique para suas preferências de estilo, é claro.
basicdays 03/03
133

Mais fácil, se você estiver usando ES6.

initialArray = [1, 2, 3];

newArray = [ ...initialArray, 4 ]; // --> [1,2,3,4]

Nova matriz será [1,2,3,4]

para atualizar seu estado no React

this.setState({
         arrayvar:[...this.state.arrayvar, newelement]
       });

Saiba mais sobre a destruição de matriz

StateLess
fonte
@Muzietto você pode elaborar?
StateLess
4
O cerne desta questão está mudando o estado React, não modificando matrizes. Você viu meu ponto sozinho e editou sua resposta. Isso torna sua resposta relevante. Bem feito.
Marco Faustinelli
2
Se suas perguntas não dizem respeito à questão OP diretamente
apátridas
1
@ ChanceSmith: é necessário na resposta StateLess também. Não dependa da atualização do estado no próprio estado. Documento
arcol
1
@RayCoder log e verifique o valor de arrayvar, parece que não é array.
StateLess 11/09/19
57

A maneira mais simples com ES6:

this.setState(prevState => ({
    array: [...prevState.array, newElement]
}))
Ridd
fonte
Desculpe, no meu caso, eu quero colocar uma matriz em matriz. tableData = [['test','test']]Depois empurrou minha nova matriz tableData = [['test','test'],['new','new']]. como empurrar este @ David e @Ridd
Johncy
@Johncy Se você gostaria de [['test','test'],['new','new']]tentar:this.setState({ tableData: [...this.state.tableData, ['new', 'new']]
Ridd 23/09
this.setState({ tableData: [...this.state.tableData ,[item.student_name,item.homework_status_name,item.comments===null?'-':item.comments] ] });Ele insere a nova matriz duas vezes this.state.tableData.push([item.student_name,item.homework_status_name,item.comments===null?'-':item.comments]);. Alcança a coisa desejada que eu quero. mas não é a maneira correta, eu acho.
Johncy
26

O React pode atualizar atualizações em lote e, portanto, a abordagem correta é fornecer ao setState uma função que execute a atualização.

Para o complemento React update, o seguinte funcionará de maneira confiável:

this.setState( state => update(state, {array: {$push: [4]}}) );

ou para concat ():

this.setState( state => ({
    array: state.array.concat([4])
}));

O seguinte mostra o que https://jsbin.com/mofekakuqi/7/edit?js,output como exemplo do que acontece se você errar.

A chamada setTimeout () adiciona corretamente três itens porque o React não atualizará em lotes as atualizações em um retorno de chamada setTimeout (consulte https://groups.google.com/d/msg/reactjs/G6pljvpTGX0/0ihYw2zK9dEJ ).

O buggy onClick adicionará apenas "Terceiro", mas o fixo adicionará F, S e T conforme o esperado.

class List extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      array: []
    }

    setTimeout(this.addSome, 500);
  }

  addSome = () => {
      this.setState(
        update(this.state, {array: {$push: ["First"]}}));
      this.setState(
        update(this.state, {array: {$push: ["Second"]}}));
      this.setState(
        update(this.state, {array: {$push: ["Third"]}}));
    };

  addSomeFixed = () => {
      this.setState( state => 
        update(state, {array: {$push: ["F"]}}));
      this.setState( state => 
        update(state, {array: {$push: ["S"]}}));
      this.setState( state => 
        update(state, {array: {$push: ["T"]}}));
    };



  render() {

    const list = this.state.array.map((item, i) => {
      return <li key={i}>{item}</li>
    });
       console.log(this.state);

    return (
      <div className='list'>
        <button onClick={this.addSome}>add three</button>
        <button onClick={this.addSomeFixed}>add three (fixed)</button>
        <ul>
        {list}
        </ul>
      </div>
    );
  }
};


ReactDOM.render(<List />, document.getElementById('app'));
NealeU
fonte
Existe realmente um caso em que isso acontece? Se simplesmente fizermosthis.setState( update(this.state, {array: {$push: ["First", "Second", "Third"]}}) )
Albizia
1
@ Albizia Acho que você deve encontrar um colega e discuti-lo com eles. Não há problema de lote se você estiver fazendo apenas uma chamada setState. O objetivo é mostrar que o React atualiza lotes, então sim ... existe realmente um caso, que é o que você pode encontrar na versão JSBin do código acima. Quase todas as respostas neste tópico não conseguem resolver isso, então haverá um monte de código lá fora que às vezes dá errado
NealeU
state.array = state.array.concat([4])isso muda o objeto de estado anterior.
Emile Bergeron
1
@EmileBergeron Obrigado por sua persistência. Eu finalmente olhei para trás e vi o que meu cérebro estava se recusando a ver, e verifiquei os documentos, então vou editar.
NealeU 31/10/19
1
Boa! É realmente fácil errar, pois a imutabilidade no JS não é óbvia (ainda mais quando se lida com a API de uma biblioteca).
Emile Bergeron
17

Como @nilgun mencionado no comentário, você pode usar os auxiliares de imutabilidade de reação . Eu achei isso super útil.

Dos documentos:

Push simples

var initialArray = [1, 2, 3];
var newArray = update(initialArray, {$push: [4]}); // => [1, 2, 3, 4]

initialArray ainda é [1, 2, 3].

Clarkie
fonte
6
Os auxiliares de imutabilidade do React são descritos como obsoletos na documentação. Agora , github.com/kolodny/immutability-helper deve ser usado.
você precisa saber é o seguinte
3

Se você estiver usando componente funcional, use-o como abaixo.

const [chatHistory, setChatHistory] = useState([]); // define the state

const chatHistoryList = [...chatHistory, {'from':'me', 'message':e.target.value}]; // new array need to update
setChatHistory(chatHistoryList); // update the state
user3444748
fonte
1

Para adicionar novo elemento à matriz, push()deve ser a resposta.

Para remover o elemento e atualizar o estado da matriz, o código abaixo funciona para mim. splice(index, 1)Não pode trabalhar.

const [arrayState, setArrayState] = React.useState<any[]>([]);
...

// index is the index for the element you want to remove
const newArrayState = arrayState.filter((value, theIndex) => {return index !== theIndex});
setArrayState(newArrayState);
Jagger
fonte
0

Eu estou tentando empurrar o valor em um estado de matriz e definir um valor como este e definir a matriz de estado e empurrar o valor pela função de mapa.

 this.state = {
        createJob: [],
        totalAmount:Number=0
    }


 your_API_JSON_Array.map((_) => {
                this.setState({totalAmount:this.state.totalAmount += _.your_API_JSON.price})
                this.state.createJob.push({ id: _._id, price: _.your_API_JSON.price })
                return this.setState({createJob: this.state.createJob})
            })
Rajnikant Lodhi
fonte
0

Isso funcionou para eu adicionar uma matriz dentro de uma matriz

this.setState(prevState => ({
    component: prevState.component.concat(new Array(['new', 'new']))
}));
MadKad
fonte
0

Aqui está um exemplo de 2020, Reactjs Hook, que pensei que poderia ajudar outras pessoas. Estou usando-o para adicionar novas linhas a uma tabela Reactjs. Deixe-me saber se eu poderia melhorar alguma coisa.

Incluindo um novo elemento em um componente de estado funcional:

Defina os dados do estado:

    const [data, setData] = useState([
        { id: 1, name: 'John', age: 16 },
        { id: 2, name: 'Jane', age: 22 },
        { id: 3, name: 'Josh', age: 21 }
    ]);

Ter um botão acionar uma função para adicionar um novo elemento

<Button
    // pass the current state data to the handleAdd function so we can append to it.
    onClick={() => handleAdd(data)}>
    Add a row
</Button>
function handleAdd(currentData) {

        // return last data array element
        let lastDataObject = currentTableData[currentTableData.length - 1]

        // assign last elements ID to a variable.
        let lastID = Object.values(lastDataObject)[0] 

        // build a new element with a new ID based off the last element in the array
        let newDataElement = {
            id: lastID + 1,
            name: 'Jill',
            age: 55,
        }

        // build a new state object 
        const newStateData = [...currentData, newDataElement ]

        // update the state
        setData(newStateData);

        // print newly updated state
        for (const element of newStateData) {
            console.log('New Data: ' + Object.values(element).join(', '))
        }

}
Ian Smith
fonte
-1
this.setState({
  arrayvar: [...this.state.arrayvar, ...newelement]
})
M.Samiei
fonte
2
Adicione algumas explicações para evitar que esta postagem seja considerada de baixa qualidade por stackoverflow.
Harsha Biyani
2
@ Ative esta chamada "operador de propagação" no ES6 e anexe os itens do array2 ao array1. obrigado por
voto negativo
@ M. Samiei Você precisa editar sua postagem para evitar votos negativos. Sua baixa qualidade é apenas um trecho de código. Além disso, é perigoso para modificar o estado desta forma como as atualizações podem ser agrupadas, de modo a maneira preferida é, por exemplosetState(state => ({arrayvar: [...state.arrayvar, ...newelement]}) );
NealeU
e espalhar o newelementobjeto dentro de uma matriz lança um de TypeErrorqualquer maneira.
Emile Bergeron
-1
//------------------code is return in typescript 

const updateMyData1 = (rowIndex:any, columnId:any, value:any) => {

    setItems(old => old.map((row, index) => {
        if (index === rowIndex) {
        return Object.assign(Object.assign({}, old[rowIndex]), { [columnId]: value });
    }
    return row;
}));
ANUBHAV RAWAT
fonte
-6

Este código funciona para mim:

fetch('http://localhost:8080')
  .then(response => response.json())
  .then(json => {
    this.setState({mystate: this.state.mystate.push.apply(this.state.mystate, json)})
  })
Hamid Hosseinpour
fonte
2
ainda assim, você
muda o
2
E falha ao atualizar o estado do componente corretamente, pois .pushretorna um número , não uma matriz.
Emile Bergeron