ReactJS: erro máximo de profundidade de atualização excedido

177

Estou tentando alternar o estado de um componente no ReactJS, mas recebo um erro informando:

Profundidade máxima de atualização excedida. Isso pode acontecer quando um componente chama repetidamente setState dentro de componentWillUpdate ou componentDidUpdate. React limita o número de atualizações aninhadas para evitar loops infinitos.

Não vejo o loop infinito no meu código, alguém pode ajudar?

Código do componente ReactJS:

import React, { Component } from 'react';
import styled from 'styled-components';

class Item extends React.Component {
    constructor(props) {
        super(props);     
        this.toggle= this.toggle.bind(this);
        this.state = {
            details: false
        } 
    }  
    toggle(){
        const currentState = this.state.details;
        this.setState({ details: !currentState }); 
    }

    render() {
        return (
            <tr className="Item"> 
                <td>{this.props.config.server}</td>      
                <td>{this.props.config.verbose}</td> 
                <td>{this.props.config.type}</td>
                <td className={this.state.details ? "visible" : "hidden"}>PLACEHOLDER MORE INFO</td>
                {<td><span onClick={this.toggle()}>Details</span></td>}
            </tr>
    )}
}

export default Item;
Maja Okholm
fonte
35
Alterar this.toggle()para this.toggleor{()=> this.toggle()}
aprendiz
8
Outra melhoria, embora não relacionada ao seu problema: transforme toggle(){...}-o toggle = () => {...}para não precisar binddele!
Berry M.
Obrigado @learner. Você me ajudou também. Você poderia gentilmente explicar o motivo por trás de sua solução. Qual é a diferença entre esses dois?
Shamim 15/04
2
@ Shamim É a diferença entre chamar uma função existente e passar a referência a uma função. É útil entender que estamos escrevendo o código a ser exibido e acionado quando o usuário faz algo, e não o código a ser acionado assim que o usuário carrega a página. reactjs.org/docs/faq-functions.html
DisplayName

Respostas:

274

isso porque você chamar alternar dentro do método render que fará com que renderize novamente e alterne chamará novamente e renderizará novamente e assim por diante

esta linha no seu código

{<td><span onClick={this.toggle()}>Details</span></td>}

você precisa fazer onClickreferência a this.togglenão chamá-lo

para corrigir o problema, faça isso

{<td><span onClick={this.toggle}>Details</span></td>}
Todos
fonte
8
Estou enfrentando uma situação semelhante, mas preciso passar um parâmetro para alternar, como isso pode ser feito?
Niveditha Karmegam
58
@NivedithaKarmegam Do onClick={(param) => this.toggle(param)}. Isso não será acionado imediatamente (e renderizado). Esta é uma definição de retorno de chamada (função de seta).
Fabian Picone
15
@FabianPicone tentou o seu método, mas quando eu console.log isso mostra que "param" foi passado como o evento, deve realmente fazeronClick={() => this.toggle(param)}
iWillGetBetter
6
@iWillGetBetter Sim, o primeiro parâmetro no onClick é o evento click. Se você precisar de um parâmetro adicional, poderá passá-lo também onClick={(event) => this.toggle(event, myParam)}.
Fabian Picone
1
Eu tenho essa função closeEditModal = () => this.setState({openEditModal: false});Como chamá-lo em renderizar?
Nux
31

Você deve passar o objeto de evento ao chamar a função:

{<td><span onClick={(e) => this.toggle(e)}>Details</span></td>}

Se você não precisar manipular o evento onClick, também poderá digitar:

{<td><span onClick={(e) => this.toggle()}>Details</span></td>}

Agora você também pode adicionar seus parâmetros dentro da função.

Florent Philippe
fonte
2
O objeto de evento é enviado automaticamente se nada for especificado. Basta incluir um parâmetro de entrada na função que é chamada.
22618 Jeff Pinkston
2
{<td><span onClick={() => this.toggle(whateverParameter)}>Details</span></td>}faz o truque para mim
iWillGetBetter
22

Esqueça a reação primeiro:
isso não está relacionado à reação e vamos entender os conceitos básicos do Java Script. Por exemplo, você escreveu a seguinte função no script java (o nome é A).

function a() {

};

Q.1) Como chamar a função que definimos?
Resp: a ();

Q.2) Como passar a referência da função para que possamos chamá-la mais tarde?
Resp: vamos divertir = a;

Agora, voltando à sua pergunta, você usou parênteses com o nome da função, significa que a função será chamada quando a seguinte declaração for renderizada.

<td><span onClick={this.toggle()}>Details</span></td>

Então, como corrigi-lo?
Simples!! Apenas remova os parênteses. Dessa maneira, você deu a referência dessa função ao evento onClick. Ele retornará sua função somente quando seu componente for clicado.

 <td><span onClick={this.toggle}>Details</span></td>

Uma sugestão repetida para reagir:
Evite usar a função embutida, como sugerido por alguém nas respostas, isso pode causar problemas de desempenho. Evite seguir o código, ele criará uma instância da mesma função repetidamente sempre que a função for chamada (a instrução lamda cria uma nova instância toda vez).
Nota: e não há necessidade de passar o evento (e) explicitamente para a função. você pode acessá-lo com na função sem passá-lo.

{<td><span onClick={(e) => this.toggle(e)}>Details</span></td>}

https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578

Piyush N
fonte
6

Eu sei que isso tem muitas respostas, mas como a maioria delas é antiga (bem, mais antiga), nenhuma menciona a abordagem pela qual eu cresço muito rápido. Em resumo:

Use componentes funcionais e ganchos .

Em mais tempo:

Tente usar o máximo de componentes funcionais em vez dos de classe, especialmente para renderização , e tente mantê-los o mais puro possível (sim, os dados estão sujos por padrão, eu sei).

Dois benefícios óbvios dos componentes funcionais (há mais):

  • Pureza ou quase pureza tornam a depuração muito mais fácil
  • Componentes funcionais eliminam a necessidade de código de caldeira de construtor

Prova rápida para o 2º ponto - Isso não é absolutamente nojento?

constructor(props) {
        super(props);     
        this.toggle= this.toggle.bind(this);
        this.state = {
            details: false
        } 
    }  

Se você estiver usando componentes funcionais para obter mais do que renderizar, precisará da segunda parte de ótimos ganchos. Por que eles são melhores que os métodos de ciclo de vida, o que mais eles podem fazer e muito mais me levariam muito espaço para cobrir, então eu recomendo que você ouça o próprio homem: Dan pregando os ganchos

Nesse caso, você precisa de apenas dois ganchos:

Um gancho de retorno de chamada convenientemente nomeado useCallback. Dessa forma, você está impedindo a ligação da função repetidamente ao renderizar novamente.

Um gancho de estado, chamado useState, para manter o estado, apesar de todo o componente estar funcionando e ser executado em sua totalidade (sim, isso é possível devido à magia dos ganchos). Dentro desse gancho, você armazenará o valor de alternância.

Se você ler esta parte, provavelmente desejará ver tudo o que falei em ação e o apliquei ao problema original. Aqui está: Demonstração

Para aqueles de vocês que querem apenas dar uma olhada no componente e no WTF, aqui está:

const Item = () => {

    // HOOKZ
  const [isVisible, setIsVisible] = React.useState('hidden');

  const toggle = React.useCallback(() => {
    setIsVisible(isVisible === 'visible' ? 'hidden': 'visible');
  }, [isVisible, setIsVisible]);

    // RENDER
  return (
  <React.Fragment>
    <div style={{visibility: isVisible}}>
        PLACEHOLDER MORE INFO
    </div>
    <button onClick={toggle}>Details</button>
  </React.Fragment>
  )
};

PS: Eu escrevi isso no caso de muitas pessoas chegarem aqui com problemas semelhantes. Espero que eles gostem do que veem pelo menos bem o suficiente para pesquisar no Google um pouco mais. Não sou eu que digo que outras respostas estão erradas, sou eu dizendo que desde que elas foram escritas, existe outra maneira (IMHO, uma melhor) de lidar com isso.

DanteTheSmith
fonte
5

se você não precisar passar argumentos para funcionar, basta remover () da função como abaixo:

<td><span onClick={this.toggle}>Details</span></td>

mas se você quiser passar argumentos, faça o seguinte:

<td><span onClick={(e) => this.toggle(e,arg1,arg2)}>Details</span></td>
Abolfazl Miadian
fonte
3

ReactJS: erro máximo de profundidade de atualização excedido

inputDigit(digit){
  this.setState({
    displayValue: String(digit)
  })

<button type="button"onClick={this.inputDigit(0)}>

porquê isso?

<button type="button"onClick={() => this.inputDigit(1)}>1</button>

A função onDigit define o estado, o que causa um re-renderizador, o que faz com que o onDigit seja acionado, porque esse é o valor que você está definindo como onClick, que faz com que o estado seja definido, o que causa um re-renderizador, o que faz com que o onDigit seja acionado porque esse é o valor que você ' re… Etc

Rizo
fonte
3

1.Se queremos passar o argumento na chamada, precisamos chamar o método como abaixo. Como estamos usando funções de seta, não é necessário vincular o método cunstructor.

onClick={() => this.save(id)} 

quando ligamos o método no construtor como este

this.save= this.save.bind(this);

então precisamos chamar o método sem passar nenhum argumento como abaixo

onClick={this.save}

e tentamos passar o argumento ao chamar a função, como mostrado abaixo, e o erro ocorre como a profundidade máxima excedida.

 onClick={this.save(id)}
Amazing Apps
fonte
1

onClick você deve chamar a função, isso é chamado de sua função de alternância.

onClick={() => this.toggle()}

makkagru
fonte
1

Nesse caso, esse código

{<td><span onClick={this.toggle()}>Details</span></td>}

faz com que a função de alternância chame imediatamente e a renderize repetidamente, fazendo chamadas infinitas.

portanto, passar apenas a referência a esse método de alternância resolverá o problema.

tão ,

{<td><span onClick={this.toggle}>Details</span></td>}

será o código da solução.

Se você quiser usar o (), use uma função de seta como esta

{<td><span onClick={()=> this.toggle()}>Details</span></td>}

Caso deseje passar parâmetros, você deve escolher a última opção e pode passar parâmetros como este

{<td><span onClick={(arg)=>this.toggle(arg)}>Details</span></td>}

No último caso, ele não chama imediatamente e não causa a re-renderização da função, evitando assim chamadas infinitas.

Badri Paudel
fonte