React: Bug: muito difícil de corrigir "Não é possível atualizar um componente de dentro do corpo da função de um componente diferente."

Criado em 28 fev. 2020  ·  109Comentários  ·  Fonte: facebook/react

# Observação: o React 16.13.1 corrigiu alguns casos em que havia excesso de combustão. Se atualizar React e ReactDOM para 16.13.1 não corrigir o aviso, leia isto: https://github.com/facebook/react/issues/18178#issuecomment -595846312

Versão React:

16,13,0

Passos para reproduzir

  1. Construa uma máquina do tempo.
  2. Vá para o ano de 2017.
  3. Crie um aplicativo enorme de 10 mil linhas de código.
  4. Obtenha 80 (!) Dependências no arquivo package.json, incluindo aquelas que não são mais mantidas.
  5. Atualize o React para a versão mais recente em 27 de fevereiro de 2020.
  6. Obtenha toneladas de erros que você não sabe como consertar.
  7. Diga ao seu cliente que as correções levarão um tempo desconhecido e custarão $$$ + dias ou semanas de investigação ou que ficaremos presos com a versão desatualizada do React e bibliotecas relacionadas para sempre, o que custará mais $$$ mas depois.

Falando sério, o negócio para o qual trabalho não está nem um pouco interessado nisso. Obviamente, eu nunca fiz isso acontecer para que esses avisos aparecessem se eu os recebesse mais cedo. Atualmente é impossivelmente difícil fazer os erros serem corrigidos porque eu os recebo em muitos casos diferentes e com um rastreamento de pilha enorme. Tentei corrigir pelo menos um dos erros que apareciam e já demorou muito. Tentei depurar algumas bibliotecas usadas, mas não tive sorte.

Apenas um exemplo:

image

Lá podemos notar o uso de um react-router desatualizado, um redux-connect desatualizado (que tive que colocar na fonte do projeto para corrigir erros do método componentWillReceiveProps desatualizado), alguns HOCs criados por recompose etc. não é apenas uma árvore DOM virtual simples onde posso percorrer os componentes desenvolvidos por mim e pesquisar por setState string para consertar o bug, isso é muito mais complicado do que isso.

Escolha uma opção "INSEGURO" para desativar este erro ou fornecer uma maneira mais simples de descobrir onde o erro foi gerado 🙏

Discussion

Comentários muito úteis

Para os futuros comentaristas. Eu entendo que ver um novo aviso é frustrante. Mas ele aponta problemas legítimos que provavelmente estão causando bugs em seu código . Agradeceríamos muito se você pudesse evitar expressar sarcasmo e aborrecimento.

Se você não consegue entender de onde vem nos rastreamentos de pilha, poste capturas de tela ou crie sandboxes de reprodução e tentaremos ajudar. A maioria deles provavelmente vem de algumas bibliotecas, portanto, a coisa mais produtiva a fazer é reduzir esses casos e, em seguida, arquivar os problemas com essas bibliotecas.

Obrigado a todos.

Todos 109 comentários

Eu gostaria de ter adicionado este aviso antes. Lamento não termos feito isso. Foi um descuido com a introdução de Hooks. Eu acredito que isso deve ser causado por um código mais recente que usa Hooks, porque já havia o mesmo aviso para classes muito mais cedo, então qualquer código anterior já teria visto esse aviso.

Observe que isso provavelmente começará a falhar em versões futuras do React. Intencional ou não (tivemos muitos bugs com esse padrão). Independentemente disso, você pode acabar travando em uma versão antiga. Se não for possível consertar, recomendo fixar em uma versão mais antiga do React.

No entanto, dito isso, queremos ajudá-lo e tornar o mais fácil possível consertá-los, incluindo ver se podemos obter ajuda dos autores da biblioteca para publicar versões corrigidas.

Se você expandir a pequena seta > no console do Chrome, também deverá ver um rastreamento de pilha adicional (além da pilha de componentes em sua captura de tela). Você pode postar isso também? Isso deve mostrar o callite exato que está causando um efeito colateral na renderização.

Comigo isso aparece quando eu uso formik

image

@sebmarkbage obrigado pela resposta. O rastreamento de pilha que aparece após clicar em> é ridículo. Ele contém mais de 200 itens!

Eu ia colar lá ou dar um link para pastebin, mas tentei uma direção diferente. Passei por questões do Github de algumas bibliotecas usadas e descobri que um dos suspeitos é o redux-form: https://github.com/redux-form/redux-form/issues/4619. Espero que essa seja a única biblioteca que causa os erros e vou esperar por uma correção antes de atualizar o React.

Mesmo assim, peço para não fechar este problema e proponho que outros desenvolvedores mencionem aqui bibliotecas que também causam esses erros.

@RodolfoSilva tem certeza que é causado por formik? Se sim, você pode criar um problema aí e postar um link para ele aqui? Vou criar uma lista desses problemas no início da minha primeira mensagem, se a lista vai conter mais de um item.

Isso realmente precisa ser resolvido o mais rápido possível. Isso torna a atualização impossível. O rastreamento do erro é praticamente impossível.

Hm. Eu me pergunto se descrever qual linha procurar na mensagem de erro ajudaria.

Neste caso, a primeira linha a procurar é a linha depois de dispatchAction . Isso deve ser o que chama o React.

@RodolfoSilva você pode postar a fonte de FormItemInput.js , se for algo que você pode compartilhar? Isso parece estar chamando dispatch ou setState na linha 71.

Acho que é fundamental que essa mensagem de erro seja modificada para incluir exatamente qual linha de código está causando o erro. É quase impossível identificar se é o código local ou o código da biblioteca que está causando o problema. Bibliotecas externas de librairies

Tenho certeza de que o React-Redux não é o culpado aqui, mas concordo que a mensagem de erro e a pilha de componentes tornam meio difícil saber o que está realmente acontecendo.

Eu enfrento o mesmo problema com o Redux-form!

Tenho o mesmo problema e vejo que o aviso aparece na primeira vez que escrevo no meu campo redux ou quando apago tudo

Eu também tenho esse problema, este é o meu componente:

`const [price, setPrice] = useState (0);

const updatePrice = (newPrice) => {
setPrice (newPrice)
}
<CardContainer onPriceUpdated = {updatePrice}> CardContainer>
`

Portanto, neste caso meu componente CardContainer, notifique o componente pai quando o preço for atualizado e o componente pai renderizar o novo príncipe.
Portanto, acho que o React está me avisando que estou tentando atualizar o estado de um componente usando a função de outro componente.
Sou novo em React, então não tenho certeza se este é um padrão do React ou se é de fato um bug.

Se vocês tiverem alguma sugestão para resolver este aviso eu agradeceria

@ l0gicgate

Acho que é fundamental que essa mensagem de erro seja modificada para incluir exatamente qual linha de código está causando o erro.

Existem limites para o que podemos fazer em JavaScript. Mas todas as informações estão na pilha que você vê no navegador . Tudo que você precisa fazer é pular as linhas que estão dentro do React.

Para ver a pilha de JavaScript, você precisa clicar em uma pequena seta ao lado da mensagem de erro .

Por exemplo, olhe para esta captura de tela anterior:

75614021-cb812980-5b12-11ea-8a6e-a38f4cd6aeef

Compreendo que é um pouco chato vasculhar a pilha, mas não deve ser tão difícil pular os primeiros quadros. O quadro seguinte é a origem do problema. Nesse caso, parece algo dentro da biblioteca Formik. Portanto, você pode registrar um problema com ele.

@martinezwilmer Este exemplo é muito pequeno para ajudar. Crie uma sandbox, por favor.

Para os futuros comentaristas. Eu entendo que ver um novo aviso é frustrante. Mas ele aponta problemas legítimos que provavelmente estão causando bugs em seu código . Agradeceríamos muito se você pudesse evitar expressar sarcasmo e aborrecimento.

Se você não consegue entender de onde vem nos rastreamentos de pilha, poste capturas de tela ou crie sandboxes de reprodução e tentaremos ajudar. A maioria deles provavelmente vem de algumas bibliotecas, portanto, a coisa mais produtiva a fazer é reduzir esses casos e, em seguida, arquivar os problemas com essas bibliotecas.

Obrigado a todos.

É difícil encontrar a linha precisa envolvida pelo aviso aqui:

@gaearon , você novamente tem uma dica sobre isso?

image

É difícil encontrar a linha precisa envolvida pelo aviso aqui:

O que torna isso difícil? Como observei acima, é a primeira linha que não diz “reagir” no lado direito. Neste caso, é connectAdvanced do Redux. Por favor, registre um problema no React Redux para que os mantenedores tenham uma chance de ver isso.

Como eu disse upthread, eu ficaria _muito_ surpreso se o que está acontecendo aqui for um problema com o React-Redux.

Dito isso, eu também não tenho certeza do que exatamente aciona essa mensagem em primeiro lugar. Eu entendi pela metade o que a mensagem de erro está dizendo, mas que tipo de padrão de código de aplicativo seria realmente um exemplo desse comportamento?

Eu me deparei com isso recentemente e a correção estava envolvendo setState sites de chamada de manipulador em useEffect , como: https://github.com/airbnb/lunar/commit/db08613d46ea21089ead3e7b5cfff995f15c69a7#diff8745 onChange e onSubmit usam setState mais acima na cadeia).

@martinezwilmer Onde onPriceUpdated está sendo chamado? Talvez tente embrulhá-lo em useEffect ?

O mesmo problema parece estar acontecendo com urql

Estamos usando use-subscription + wonka (para streams) para orquestrar nossas atualizações, no entanto, uma atualização pode vir de forma síncrona. Aqui , já buscamos todos portanto, se clicarmos no botão Open , o resultado aparecerá instantaneamente, no entanto, isso parece desencadear o seguinte erro.

image

Em nosso caso, isso é intencional, não podemos apenas mostrar fetching: true para um resultado de sincronização, o que resultaria em interfaces instáveis.

Isso está começando a surgir cada vez mais em bibliotecas de terceiros agora: urql , Apollo .

Eu me deparei com isso e por várias horas presumi que o problema estava no meu código. O stacktrace condensado aponta para meus componentes, e não é incomum para mim ver bibliotecas de terceiros no stacktrace expandido quando eu _did_ explicitamente acionei um erro. Com base na minha (embora limitada) pesquisa sobre esse aviso específico, parece que a maioria dos desenvolvedores não está causando esse problema, mas sim dependendo do código que causa. Normalmente, é uma boa prática presumir que não é um bug do upstream, mas quando é um bug do upstream, perder tempo rastreando um problema que não existe em seu código é bastante frustrante. Existe algo que o React pode fazer para ajudar um usuário médio a determinar se foi o código que ele escreveu ou o código do qual depende que causou o problema?

Uma coisa que observo sobre o problema da Apollo é:

O rastreamento de pilha do aviso está mostrando o componente que inicializou as mudanças, não aquele que é re-renderizado [sic] por essas mudanças

Se estiver correto, o React pode fornecer mais informações aqui? Talvez nos dizendo o componente inicial e os componentes que causou a atualização?

Como @hugo , encontrei isso ao testar um novo aplicativo Ionic :

  1. npx ionic start demo sidemenu --type=react
  2. react-scripts test

Na verdade, a causa está enterrada no meio e na parte inferior do rastreamento da pilha.

console.error node_modules/react-dom/cjs/react-dom.development.js:88
    Warning: Cannot update a component from inside the function body of a different component.
        in Route (at App.tsx:37)
        in View (created by StackManagerInner)
        in ViewTransitionManager (created by StackManagerInner)
        in ion-router-outlet (created by IonRouterOutlet)
        in IonRouterOutlet (created by ForwardRef(IonRouterOutlet))
        in ForwardRef(IonRouterOutlet) (created by StackManagerInner)
        in StackManagerInner (created by Context.Consumer)
        in Unknown (created by Component)
        in Component (created by ForwardRef(IonRouterOutlet))
        in ForwardRef(IonRouterOutlet) (at App.tsx:36)
        in ion-split-pane (created by IonSplitPane)
        in IonSplitPane (created by ForwardRef(IonSplitPane))
        in ForwardRef(IonSplitPane) (at App.tsx:34)
        in NavManager (created by RouteManager)
        in RouteManager (created by Context.Consumer)
        in RouteManager (created by IonReactRouter)
        in Router (created by BrowserRouter)
        in BrowserRouter (created by IonReactRouter)
        in IonReactRouter (at App.tsx:33)
        in ion-app (created by IonApp)
        in IonApp (created by ForwardRef(IonApp))
        in ForwardRef(IonApp) (at App.tsx:32)
        in App (at App.test.tsx:6)

Este problema foi o mais próximo que consegui encontrar em relação a este problema.

Encontramos um padrão específico que causa esse problema com mobx em https://github.com/mobxjs/mobx-react/issues/846

@sebmarkbage não consigo mais reproduzir esse problema. Atualizamos algumas bibliotecas e os problemas acabaram.

@jgoux , parece que enfrentamos o mesmo problema @Clovis ^^ Spotted

Comecei a receber este erro após a atualização reagir a react 16.13.0 . O problema é bastante claro, pois um dos meus componentes está atualizando outro depois que uma ação específica é realizada. No entanto, não sei por que isso geraria um aviso. Alguma sugestão de como contornar isso?

@gaearon obrigado pelos detalhes sobre como depurar da pilha, eu pessoalmente não teria sido capaz de descobrir de onde vinha esse erro se você não tivesse dado esse exemplo. 🙏

Não tenho certeza se meu problema está relacionado, no entanto, estou tentando atualizar o estado do meu componente de formulário, mas sempre que tento adicionar um manipulador onChange, ele continua apresentando esse erro. Veja bem, estou usando react-jsonschema-form e importei o componente Form e estou usando sua propriedade onChange para atualizar o estado.

Para mim, esse é o padrão que causa o problema.

image

Pode haver uma maneira de contornar isso. Mas o log do console me apontou direto para a linha 385

Eu sou novo para reagir, mas eu tinha um código como este:

import React, { useState } from "react";

function Home() {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav setMobileNavOpen={setMobileNavOpen}/>}
    </div>
  );
}

function MobileNav() {
  return (
    <div>
      <button
        onClick={setMobileNavOpen(false)} //problem here
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;

O que resultou em: Cannot update a component from inside the function body of a different component.

Tudo que eu tive que fazer foi adicionar uma função de seta para setMobileNavOpen no MobileNav assim:

import React, { useState } from "react";

function Home() {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav setMobileNavOpen={setMobileNavOpen}/>}
    </div>
  );
}

function MobileNav() {
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(false)} //fixes problem
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;

E isso resolve o problema, espero que ajude alguém!

Eu sou novo para reagir, mas eu tinha um código como este:

import React, { useState } from "react";

function Home() {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav setMobileNavOpen={setMobileNavOpen}/>}
    </div>
  );
}

function MobileNav() {
  return (
    <div>
      <button
        onClick={setMobileNavOpen(false)} //problem here
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;

O que resultou em: Cannot update a component from inside the function body of a different component.

Tudo que eu tive que fazer foi adicionar uma função de seta para setMobileNavOpen no MobileNav assim:

import React, { useState } from "react";

function Home() {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav setMobileNavOpen={setMobileNavOpen}/>}
    </div>
  );
}

function MobileNav() {
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(false)} //fixes problem
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;

E isso resolve o problema, espero que ajude alguém!

Seu exemplo é, na verdade, um dos primeiros erros que as pessoas cometem ao reagir. Não tenho certeza se é exatamente o mesmo problema que está sendo discutido aqui. Sua linha aqui: onClick={setMobileNavOpen(false) está chamando a função durante o leilão do botão, não no clique. É por isso que envolvê-lo em uma função de seta o corrige.

Encontrei esse problema com o React Router, onde precisava despachar uma ação Redux antes de <Redirect> enviar o usuário para um local diferente. O problema parecia ser que o redirecionamento aconteceu antes do término do despacho. Resolvi o problema prometendo minha ação.

Antes:

<Route
  render={routeProps => {
    setRedirectionTarget(somePath(routeProps));
    return <Redirect to={someOtherPath} />;
  }}
/>;

function mapDispatchToProps(dispatch: ThunkDispatch) {
  return {
    setRedirectionTarget: (target: string | null) => dispatch(setRedirectionTarget(target))
  };
}

export const setRedirectionTarget = (location: string | null): SetRedirectionTarget => {
  return {
    type: SET_REDIRECTION_TARGET,
    location
  };
};

Depois de:

function mapDispatchToProps(dispatch: ThunkDispatch) {
  return {
    setRedirectionTarget: async (target: string | null) => dispatch(await setRedirectionTarget(target))
  };
}

export const setRedirectionTarget = (location: string | null): Promise<SetRedirectionTarget> => {
  return Promise.resolve({
    type: SET_REDIRECTION_TARGET,
    location
  });
};

Acho que faria a mensagem de erro incluir nomes para o componente que está sendo renderizado e para o componente cujo setState está sendo chamado. Alguém quer enviar um PR para isso?

Acho que faria a mensagem de erro incluir nomes para o componente que está sendo renderizado e para o componente cujo setState está sendo chamado. Alguém quer enviar um PR para isso?

Estou feliz em dar uma olhada nisso. Quaisquer dicas que eu deva estar ciente?

@ samcooke98 Abri um PR para este https://github.com/facebook/react/pull/18316

Como outros apontaram, você pode encontrar esse problema se tiver uma assinatura em um gancho como o Apollo e tentar atualizar um armazenamento ao receber dados. A solução simples é usar useEffect eg

export const useOrderbookSubscription = marketId => {
  const { data, error, loading } = useSubscription(ORDERBOOK_SUBSCRIPTION, {
    variables: {
      marketId,
    },
  })

  const formattedData = useMemo(() => {
    // Don't dispatch in here.
  }, [data])

  // Don't dispatch here either

  // Dispatch in a useEffect
  useEffect(() => {
    orderbookStore.dispatch(setOrderbookData(formattedData))
  }, [formattedData])

  return { data: formattedData, error, loading }
}

Estávamos enfrentando esse problema no Hyper, mas não estamos usando ganchos e não conseguimos encontrar nada na chamada de renderização da pilha de chamadas. Mas houve uma chamada em UNSAFE_componentWillReceiveProps na pilha. Atualizar isso com componentDidUpdate corrigiu o problema para nós https://github.com/zeit/hyper/pull/4382
Poste aqui se puder ajudar alguém

O mesmo aqui, houve uma chamada UNSAFE_componentWillMount, alterando / removendo corrigiu o problema

mas não estamos usando ganchos e não conseguimos encontrar nada na chamada de renderização da pilha de chamadas

Isso parece estranho. Não tenho certeza de como você está recebendo esse aviso. Ele só dispara se setState pertencer a um componente de função. Qual é a sua pilha?

mas não estamos usando ganchos e não conseguimos encontrar nada na chamada de renderização da pilha de chamadas

Isso parece estranho. Não tenho certeza de como você está recebendo esse aviso. Ele só dispara se setState pertencer a um componente de função. Qual é a sua pilha?

Classifique componentes com Redux e funções que aplicam plug-ins aos componentes. Talvez seja por isso que é contado como um componente de função? Mas então por que atualizar os ganchos de ciclo de vida corrige isso?

Não tenho certeza. Você pode tentar criar um exemplo isolado em uma caixa de areia? Tenho um palpite de que você pode estar fazendo outra coisa inesperada.

Não tenho certeza se seria capaz de replicá-lo em um exemplo isolado. Tive dificuldade em encontrar a causa e acabara de atualizar o gancho do ciclo de vida, pois estava no rastreamento da pilha e estava pendente para atualização. Isso de alguma forma resolveu o problema.
Você pode dar uma olhada no repo no momento em que teve o problema aqui

Pode ser por causa do fato de que UNSAFE_componentWillReceiveProps e componentDidUpdate estavam no componente naquele momento (um componente inseguro foi deixado lá por engano)?

Também estou recebendo este aviso e o rastreamento de pilha aponta para o gancho setScriptLoaded (veja abaixo meu gancho personalizado). Portanto, parece que, embora eu esteja usando useEffect , o React ainda emitirá um aviso se o manipulador setState estiver aninhado em outros retornos de chamada assíncronos (no meu caso, está aninhado em um setTimeout e um manipulador de eventos load )? Minha primeira vez usando Hooks, então eu agradeceria qualquer conselho. Obrigado!

/**
 * Detect when 3rd party script is ready to use
 * 
 * <strong i="11">@param</strong> {function} verifyScriptLoaded Callback to verify if script loaded correctly
 * <strong i="12">@param</strong> {string} scriptTagId 
 */

export const useScriptLoadStatus = (verifyScriptLoaded, scriptTagId) => {
    let initLoadStatus = true; // HTML already includes script tag when rendered server-side
    if (__BROWSER__) {
        initLoadStatus = typeof verifyScriptLoaded === 'function' ? verifyScriptLoaded() : false;
    }
    const [isScriptLoaded, setScriptLoaded] = useState(initLoadStatus); 

    useEffect(() => {
        if (!isScriptLoaded) {
            // need to wrap in setTimeout because Helmet appends the script tags async-ly after component mounts (https://github.com/nfl/react-helmet/issues/146)
            setTimeout(() => {
                let newScriptTag = document.querySelector(`#${scriptTagId}`);
                if (newScriptTag && typeof verifyScriptLoaded === 'function') {
                    newScriptTag.addEventListener('load', () => { 
                        return verifyScriptLoaded() ? setScriptLoaded(true) : null;
                    });
                    // double check if script is already loaded before the event listener is added
                    return verifyScriptLoaded() ? setScriptLoaded(true) : null;
                }
            }, 100);
        }
    });

    return isScriptLoaded;
};

@LabhanshAgrawal

Pode ser devido ao fato de que UNSAFE_componentWillReceiveProps e componentDidUpdate estavam no componente naquele momento (um componente inseguro foi deixado lá por engano)?

Não acho que nenhum método de ciclo de vida seja relevante aqui. É por isso que estou dizendo que algo está estranho em seu exemplo.

@suhanw Por favor, forneça um exemplo completo em CodeSandbox. Não vejo nenhum problema com seu código que deve estar causando este aviso.

@LabhanshAgrawal Você pode postar sua pilha completa, por favor? Eu acho que o que pode estar acontecendo é que seu UNSAFE_componentWillReceiveProps (que é equivalente a renderização) chama setState em outro componente.

backend.js:6 Warning: Cannot update a component from inside the function body of a different component.
    in Term                           (created by _exposeDecorated(Term))
    in _exposeDecorated(Term)         (created by DecoratedComponent)
    in DecoratedComponent             (created by TermGroup_)
    in TermGroup_                     (created by ConnectFunction)
    in ConnectFunction                (created by Connect(TermGroup_))
    in Connect(TermGroup_)            (created by _exposeDecorated(TermGroup))
    in _exposeDecorated(TermGroup)    (created by DecoratedComponent)
    in DecoratedComponent             (created by Terms)
    in div                            (created by Terms)
    in div                            (created by Terms)
    in Terms                          (created by _exposeDecorated(Terms))
    in _exposeDecorated(Terms)        (created by DecoratedComponent)
    in DecoratedComponent             (created by ConnectFunction)
    in ConnectFunction                (created by Connect(DecoratedComponent))
    in Connect(DecoratedComponent)    (created by Hyper)
    in div                            (created by Hyper)
    in div                            (created by Hyper)
    in Hyper                          (created by _exposeDecorated(Hyper))
    in _exposeDecorated(Hyper)        (created by DecoratedComponent)
    in DecoratedComponent             (created by ConnectFunction)
    in ConnectFunction                (created by Connect(DecoratedComponent))
    in Connect(DecoratedComponent)
    in Provider
r                                     @ backend.js:6
printWarning                          @ react-dom.development.js:88
error                                 @ react-dom.development.js:60
warnAboutRenderPhaseUpdatesInDEV      @ react-dom.development.js:23260
scheduleUpdateOnFiber                 @ react-dom.development.js:21196
dispatchAction                        @ react-dom.development.js:15682
checkForUpdates                       @ connectAdvanced.js:88
handleChangeWrapper                   @ Subscription.js:97
(anonymous)                           @ Subscription.js:23
batchedUpdates$1                      @ react-dom.development.js:21887
notify                                @ Subscription.js:19
notifyNestedSubs                      @ Subscription.js:92
checkForUpdates                       @ connectAdvanced.js:77
handleChangeWrapper                   @ Subscription.js:97
(anonymous)                           @ Subscription.js:23
batchedUpdates$1                      @ react-dom.development.js:21887
notify                                @ Subscription.js:19
notifyNestedSubs                      @ Subscription.js:92
handleChangeWrapper                   @ Subscription.js:97
dispatch                              @ redux.js:222
e                                     @ VM64:1
(anonymous)                           @ effects.ts:11
(anonymous)                           @ write-middleware.ts:14
(anonymous)                           @ index.js:11
(anonymous)                           @ plugins.ts:538
(anonymous)                           @ plugins.ts:540
(anonymous)                           @ index.js:11
dispatch                              @ redux.js:638
(anonymous)                           @ sessions.ts:124
(anonymous)                           @ index.js:8
dispatch                              @ VM64:1
onResize                              @ terms.ts:62
(anonymous)                           @ term.js:179
e.fire                                @ xterm.js:1
t.resize                              @ xterm.js:1
e.resize                              @ xterm.js:1
e.fit                                 @ xterm-addon-fit.js:1
fitResize                             @ term.js:291
UNSAFE_componentWillReceiveProps      @ term.js:408
callComponentWillReceiveProps         @ react-dom.development.js:12998
updateClassInstance                   @ react-dom.development.js:13200
updateClassComponent                  @ react-dom.development.js:17131
beginWork                             @ react-dom.development.js:18653
beginWork$1                           @ react-dom.development.js:23210
performUnitOfWork                     @ react-dom.development.js:22185
workLoopSync                          @ react-dom.development.js:22161
performSyncWorkOnRoot                 @ react-dom.development.js:21787
(anonymous)                           @ react-dom.development.js:11111
unstable_runWithPriority              @ scheduler.development.js:653
runWithPriority$1                     @ react-dom.development.js:11061
flushSyncCallbackQueueImpl            @ react-dom.development.js:11106
flushSyncCallbackQueue                @ react-dom.development.js:11094
batchedUpdates$1                      @ react-dom.development.js:21893
notify                                @ Subscription.js:19
notifyNestedSubs                      @ Subscription.js:92
handleChangeWrapper                   @ Subscription.js:97
dispatch                              @ redux.js:222
e                                     @ VM64:1
(anonymous)                           @ effects.ts:11
(anonymous)                           @ write-middleware.ts:14
(anonymous)                           @ index.js:11
(anonymous)                           @ plugins.ts:538
(anonymous)                           @ plugins.ts:540
(anonymous)                           @ index.js:11
dispatch                              @ redux.js:638
effect                                @ ui.ts:60
(anonymous)                           @ effects.ts:13
(anonymous)                           @ write-middleware.ts:14
(anonymous)                           @ index.js:11
(anonymous)                           @ plugins.ts:538
(anonymous)                           @ plugins.ts:540
(anonymous)                           @ index.js:11
dispatch                              @ redux.js:638
(anonymous)                           @ ui.ts:54
(anonymous)                           @ index.js:8
dispatch                              @ VM64:1
(anonymous)                           @ index.tsx:162
emit                                  @ events.js:210
(anonymous)                           @ rpc.ts:31
emit                                  @ events.js:210
onMessage                             @ init.ts:50

@gaearon agradece a resposta rápida. Vou descobrir uma maneira de criar um exemplo em CodeSandbox (é um desafio), mas, por enquanto, este é o meu rastreamento de pilha completo

a linha que aponta para meu gancho personalizado é esta: https://gist.github.com/suhanw/bcc2688bba131df8301dae073977654f#file -stack-trace-L144

seria incrível se você pudesse dar uma olhada e me informar se há algo no rastreamento de pilha antes / depois do meu gancho personalizado que eu deva investigar mais. Obrigado!

@LabhanshAgrawal Em sua pilha, UNSAFE_componentWillReceiveProps chama alguns fitResize que despacha uma ação Redux, que por sua vez atualiza vários componentes. Daí o problema. Então sim, mudar isso para componentDidUpdate funciona.

@suhanw Em sua pilha, algo chamado ModuleImpressionTracker parece despachar uma ação Redux durante um construtor. Os construtores não devem ter efeitos colaterais. Acho que essa é a causa do problema, não o seu Gancho.

Entendo, então deduzo disso que UNSAFE_componentWillReceiveProps é contado como renderização, mas componentDidUpdate não.
Você pode esclarecer um pouco sobre isso ser um problema com componentes funcionais e ganchos fazendo setState, não consegui entender claramente da discussão acima.
Peço desculpas se a pergunta for um pouco boba ou se minha opinião estiver errada, sou um pouco novo nisso.

@LabhanshAgrawal

Você pode esclarecer um pouco sobre isso ser um problema com componentes funcionais e ganchos fazendo setState, não consegui entender claramente da discussão acima.

Honestamente, eu mesmo não tenho certeza por causa de todas as coisas do Redux no meio. É muito confuso devido à forma como o React Redux é implementado. Seria bom se alguém pudesse obter uma reprodução clara que envolvesse apenas o React, mas usasse componentes de classe.

Ainda parece que há muitos rastreamentos de pilha sendo colados e não muitas reproduções reais do problema real, independentemente do tipo.

Eu acho que aquele para urql se destina a @gaearon, então o que acontece é:

  • Temos nosso <Todos /> montado, que busca os dados e os renderiza
  • O anterior configurou um fluxo conectado ao urqlClient
  • Renderizamos nosso segundo <Todos /> o que produzirá a mesma combinação de consulta + variáveis, portanto, atualizará o resultado de <Todos /> da primeira etapa.
  • use-subscription é acionado para ambos.

Repro - Pressione "abrir" na parte superior quando a primeira consulta for processada.

Poderíamos colocar as atualizações na fila, mas enquanto não fizermos top-down renderizações com contexto, não seremos capazes de contornar esse problema, presumo? Em teoria, este é o comportamento pretendido para este caso. Curioso como o Relay funciona em torno disso.

EDITAR: podemos ter encontrado uma solução para o caso de urql, não acionando todos os assinantes para atualizar durante o getCurrentValue de use-subscription

https://github.com/FormidableLabs/urql/commit/3a597dd92587ef852c18139e9781e853f763e930

OK, então olhando mais a fundo, acho que estamos avisando em mais casos (por exemplo, para aulas) do que pretendíamos originalmente. Veremos como silenciar alguns deles.

@JoviDeCroock Este exemplo não é muito útil porque não tenho ideia do que urql faz. :-) Se você quiser feedback sobre um padrão, poderia preparar uma caixa de areia demonstrando esse padrão isoladamente?

@JoviDeCroock Sim, getCurrentValue definitivamente não tem efeitos colaterais. Achamos que esse nome é bem explícito sobre isso.

Eu tive um problema em que recebia este aviso ao despachar uma ação redux dentro do escopo raiz de um gancho personalizado.

function useCustomHook() {
   const dispatch = useDispatch()
   const value = useSomeOtherHook()
   dispatch(action(value))
}

Eu consertei isso envolvendo o despacho em um useEffect .

@Glinkis Isso soa como um padrão ruim de qualquer maneira. Por que você deseja notificar toda a árvore sempre que um componente for renderizado?

@gaearon sim, o problema que estávamos tentando resolver é melhor explicado aqui e parece bastante comum.

@Glinkis Isso soa como um padrão ruim de qualquer maneira. Por que você deseja notificar toda a árvore sempre que um componente for renderizado?

Preciso manter meu estado redux sincronizado com os IDs de minha rota, então despacho esta atualização quando minha rota muda.

Sei que isso pode não ser o ideal, mas não encontrei outra maneira de acessar os dados de roteamento em meus seletores.

@Glinkis De onde

@JoviDeCroock Acho que nossa recomendação mais recente para esse padrão é uma passagem de coleta de lixo planejada personalizada.

@Glinkis De onde

Não tenho certeza se isso pertence a esta discussão, mas este é o meu gancho.

export function useOpenFolder() {
  const dispatch = useDispatch()
  const match = useRouteMatch('/:workspace/(super|settings)?/:type?/:id?/(item)?/:item?')
  const { id, item } = match?.params || {}

  useEffect(() => {
    dispatch(
      openFolder({
        id: id || '',
        item: item || '',
      })
    )
  }, [dispatch, item, id])
}

Posteriormente, utilizo este estado para seletores como:

export const getActiveItem = createSelector(
  [getActiveFolderItem, getItems],
  (activeItem, items) => items.all[activeItem]
)

@Glinkis Sim, talvez vamos encerrar aqui, mas minha sugestão seria ler useRouteMatch no componente pai, passar o ID como um adereço e então ler adereços no seletor como você faria normalmente. (Na verdade, não sei como isso é feito no Redux atualmente, mas deve haver alguma maneira.)

Entendo, então deduzo disso que UNSAFE_componentWillReceiveProps é contado como renderização, mas componentDidUpdate não.

@LabhanshAgrawal Correto. Algumas explicações básicas aqui :

Conceitualmente, o React funciona em duas fases:

  • A fase de renderização determina quais mudanças precisam ser feitas, por exemplo, no DOM. Durante esta fase, o React chama render e então compara o resultado com a renderização anterior.
  • A fase de confirmação é quando o React aplica as alterações. (No caso do React DOM, é quando o React insere, atualiza e remove nós DOM.) O React também chama ciclos de vida como componentDidMount e componentDidUpdate durante esta fase.

A fase de confirmação geralmente é muito rápida, mas a renderização pode ser lenta. Por esse motivo, o próximo modo simultâneo (que ainda não está habilitado por padrão) divide o trabalho de renderização em partes, pausando e retomando o trabalho para evitar o bloqueio do navegador. Isso significa que o React pode invocar os ciclos de vida da fase de renderização mais de uma vez antes de cometer, ou pode invocá-los sem comprometer (devido a um erro ou uma interrupção de prioridade mais alta).

Os ciclos de vida da fase de renderização incluem os seguintes métodos de componente de classe:

  • constructor
  • componentWillMount (ou UNSAFE_componentWillMount )
  • componentWillReceiveProps (ou UNSAFE_componentWillReceiveProps )
  • componentWillUpdate (ou UNSAFE_componentWillUpdate )
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • setState funções atualizadoras (o primeiro argumento)

Como os métodos acima podem ser chamados mais de uma vez, é importante que eles não contenham efeitos colaterais. Ignorar essa regra pode levar a vários problemas, incluindo vazamentos de memória e estado de aplicativo inválido. Infelizmente, pode ser difícil detectar esses problemas, pois muitas vezes eles podem ser não determinísticos .

OK, então olhando mais a fundo, acho que estamos avisando em mais casos (por exemplo, para aulas) do que pretendíamos originalmente. Veremos como silenciar alguns deles.

Acho que mesmo que seja mais do que pretendido, na minha opinião é uma boa coisa ter, pois ajuda a melhorar nossos projetos usando classes.

Esta mensagem de erro autoexplicativa "Não é possível atualizar um componente de dentro do corpo da função de um componente diferente"

Isso significa executar como uma função em vez de chamar o componente.

exemplo como este:

const App = () => {  
  const fetchRecords = () => { 
    return <div>Loading..</div>;
  };  
  return fetchRecords() // and not like <FetchRecords /> unless it is functional component.
};
export default App;

@rpateld Não acho que o exemplo que você está mostrando esteja relacionado a este aviso.

https://github.com/facebook/react/pull/18330 resolverá os casos que não pretendíamos disparar.

Também estou enfrentando esse problema com react@experimental + react-redux + redux .
image

O código é parecido com este:

import React, { Suspense } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { Redirect } from 'react-router-dom'
import PropTypes from 'prop-types'
import { userActions, cabinetActions, tokensActions } from '../../actions'
import { CircularProgress } from '@material-ui/core'
import { api } from './api'

const Cabinet = ({ resource }) => {
    resource.read()
    return <h1>cabinet</h1>
}

Cabinet.propTypes = {
    resource: PropTypes.shape({
        read: PropTypes.func
    })
}

const CabinetPage = ({
    failedToLoad,
    tokensRefreshFailed,
    logout,
    loadCabinet,
    clearTokens,
    clearCabinet
}) => {
    if (tokensRefreshFailed || failedToLoad) {
        clearTokens()
        clearCabinet()
        logout()
        return <Redirect to='/login' />
    }

    return (
        <Suspense fallback={<CircularProgress />}>
            <Cabinet resource={loadCabinet()} />
        </Suspense>
    )
}

CabinetPage.propTypes = {
    loadCabinet: PropTypes.func,
    failedToLoad: PropTypes.bool,
    tokensRefreshFailed: PropTypes.bool,
    logout: PropTypes.func,
    clearTokens: PropTypes.func,
    clearCabinet: PropTypes.func
}

const mapStateToProps = ({
    alert,
    tokens: { tokensRefreshFailed },
    cabinet: { failedToLoad }
}) => ({
    alert,
    tokensRefreshFailed,
    failedToLoad
})

const mapDispatchToProps = dispatch =>
    bindActionCreators(
        {
            cabinetLoad: cabinetActions.load,
            logout: userActions.logoutWithoutRedirect,
            loadCabinet: api.loadCabinet,
            clearCabinet: cabinetActions.clear,
            clearTokens: tokensActions.clear
        },
        dispatch
    )

export default connect(mapStateToProps, mapDispatchToProps)(CabinetPage)

loadCabinet() despacha uma ação assíncrona de renderização trifásica, como dizem os documentos simultâneos, e retorna um objeto com read() prop.
No entanto, não consigo ver nenhuma atualização dos pais aqui.

@ h0tw4t3r Você está despachando uma ação Redux durante a renderização de um componente. Isso não é compatível, e é sobre isso que se trata o aviso. É melhor perguntar aos especialistas do React Router sobre como lidar com este caso (redirecionar) de forma elegante, não posso ajudar nessa parte.

Com relação ao modo simultâneo, observe que o Redux não é atualmente compatível com ele em geral. Portanto, você pode querer evitá-lo se estiver fazendo experiências com CM.

Pequena atualização neste tópico

Vamos lançar um patch do React em breve que corrige o disparo excessivo deste aviso para as classes. Portanto, se você tiver isso, considere esperar alguns dias e tentar o 16.13.1 quando ele for lançado.

Se você quiser continuar procurando as causas, espero que https://github.com/facebook/react/issues/18178#issuecomment -595846312 explique como.

Acho muito estranho que a mesma lógica quando usada em um componente de classe não dê nenhum aviso, enquanto funcional (ganchos) dá:

Componente funcional (ganchos):

import React, { Component } from "react"
import SortableTree from "react-sortable-tree"
import "react-sortable-tree/style.css"

const data = [
  {
    title: "Windows 10",
    subtitle: "running",
    children: [
      {
        title: "Ubuntu 12",
        subtitle: "halted",
        children: [
          {
            title: "Debian",
            subtitle: "gone"
          }
        ]
      },
      {
        title: "Centos 8",
        subtitle: "hardening"
      },
      {
        title: "Suse",
        subtitle: "license"
      }
    ]
  }
]

const nodeInfo = row => console.log(row)

export default class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      searchString: "",
      searchFocusIndex: 0,
      searchFoundCount: null,
      treeData: data
    }
  }

  render() {
    const { searchString, searchFocusIndex, searchFoundCount } = this.state

    const customSearchMethod = ({ node, searchQuery }) =>
      searchQuery &&
      ((node.title &&
        node.title.toLowerCase().indexOf(searchQuery.toLowerCase()) > -1) ||
        (node.subtitle &&
          node.subtitle.toLowerCase().indexOf(searchQuery.toLowerCase()) > -1))

    const selectPrevMatch = () =>
      this.setState({
        searchFocusIndex:
          searchFocusIndex !== null
            ? (searchFoundCount + searchFocusIndex - 1) % searchFoundCount
            : searchFoundCount - 1
      })

    const selectNextMatch = () =>
      this.setState({
        searchFocusIndex:
          searchFocusIndex !== null
            ? (searchFocusIndex + 1) % searchFoundCount
            : 0
      })

    return (
      <div>
        <h2>Find the needle!</h2>
        <form
          style={{ display: "inline-block" }}
          onSubmit={event => {
            event.preventDefault()
          }}
        >
          <input
            id="find-box"
            type="text"
            placeholder="Search..."
            style={{ fontSize: "1rem" }}
            value={searchString}
            onChange={event =>
              this.setState({ searchString: event.target.value })
            }
          />
          &nbsp;
          <button
            type="button"
            disabled={!searchFoundCount}
            onClick={selectPrevMatch}
          >
            &lt;
          </button>
          &nbsp;
          <button
            type="submit"
            disabled={!searchFoundCount}
            onClick={selectNextMatch}
          >
            &gt;
          </button>
          &nbsp;
          <span>
            &nbsp;
            {searchFoundCount > 0 ? searchFocusIndex + 1 : 0}
            &nbsp;/&nbsp;
            {searchFoundCount || 0}
          </span>
        </form>

        <div style={{ height: 300 }}>
          <SortableTree
            treeData={this.state.treeData}
            onChange={treeData => this.setState({ treeData })}
            searchMethod={customSearchMethod}
            searchQuery={searchString}
            searchFocusOffset={searchFocusIndex}
            searchFinishCallback={matches =>
              this.setState({
                searchFoundCount: matches.length,
                searchFocusIndex:
                  matches.length > 0 ? searchFocusIndex % matches.length : 0
              })
            }
            generateNodeProps={row => {
              return {
                title: row.node.title,
                subtitle: (
                  <div style={{ lineHeight: "2em" }}>{row.node.subtitle}</div>
                ),
                buttons: [
                  <button
                    type="button"
                    className="btn btn-outline-success"
                    style={{
                      verticalAlign: "middle"
                    }}
                    onClick={() => nodeInfo(row)}
                  >
                    ℹ
                  </button>
                ]
              }
            }}
          />
        </div>
      </div>
    )
  }
}

image

Componente de classe:

import React from "react";
import SortableTree from "react-sortable-tree";
import "react-sortable-tree/style.css";

const data = [
  {
    title: "Windows 10",
    subtitle: "running",
    children: [
      {
        title: "Ubuntu 12",
        subtitle: "halted",
        children: [
          {
            title: "Debian",
            subtitle: "gone"
          }
        ]
      },
      {
        title: "Centos 8",
        subtitle: "hardening"
      },
      {
        title: "Suse",
        subtitle: "license"
      }
    ]
  }
];

const nodeInfo = row => console.log(row);

export default class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      searchString: "",
      searchFocusIndex: 0,
      searchFoundCount: null,
      treeData: data
    };
  }

  render() {
    const { searchString, searchFocusIndex, searchFoundCount } = this.state;

    const customSearchMethod = ({ node, searchQuery }) =>
      searchQuery &&
      ((node.title &&
        node.title.toLowerCase().indexOf(searchQuery.toLowerCase()) > -1) ||
        (node.subtitle &&
          node.subtitle.toLowerCase().indexOf(searchQuery.toLowerCase()) > -1));

    const selectPrevMatch = () =>
      this.setState({
        searchFocusIndex:
          searchFocusIndex !== null
            ? (searchFoundCount + searchFocusIndex - 1) % searchFoundCount
            : searchFoundCount - 1
      });

    const selectNextMatch = () =>
      this.setState({
        searchFocusIndex:
          searchFocusIndex !== null
            ? (searchFocusIndex + 1) % searchFoundCount
            : 0
      });

    return (
      <div>
        <h2>Find the needle!</h2>
        <form
          style={{ display: "inline-block" }}
          onSubmit={event => {
            event.preventDefault();
          }}
        >
          <input
            id="find-box"
            type="text"
            placeholder="Search..."
            style={{ fontSize: "1rem" }}
            value={searchString}
            onChange={event =>
              this.setState({ searchString: event.target.value })
            }
          />
          &nbsp;
          <button
            type="button"
            disabled={!searchFoundCount}
            onClick={selectPrevMatch}
          >
            &lt;
          </button>
          &nbsp;
          <button
            type="submit"
            disabled={!searchFoundCount}
            onClick={selectNextMatch}
          >
            &gt;
          </button>
          &nbsp;
          <span>
            &nbsp;
            {searchFoundCount > 0 ? searchFocusIndex + 1 : 0}
            &nbsp;/&nbsp;
            {searchFoundCount || 0}
          </span>
        </form>

        <div style={{ height: 300 }}>
          <SortableTree
            treeData={this.state.treeData}
            onChange={treeData => this.setState({ treeData })}
            searchMethod={customSearchMethod}
            searchQuery={searchString}
            searchFocusOffset={searchFocusIndex}
            searchFinishCallback={matches =>
              this.setState({
                searchFoundCount: matches.length,
                searchFocusIndex:
                  matches.length > 0 ? searchFocusIndex % matches.length : 0
              })
            }
            generateNodeProps={row => {
              return {
                title: row.node.title,
                subtitle: (
                  <div style={{ lineHeight: "2em" }}>{row.node.subtitle}</div>
                ),
                buttons: [
                  <button
                    type="button"
                    className="btn btn-outline-success"
                    style={{
                      verticalAlign: "middle"
                    }}
                    onClick={() => nodeInfo(row)}
                  >
                    ℹ
                  </button>
                ]
              };
            }}
          />
        </div>
      </div>
    );
  }
}

@radulle Este não é um exemplo muito útil por si só. Tentei colocá-lo no CodeSandbox, mas não funcionou: https://codesandbox.io/s/clever-taussig-9xixs. Você pode preparar um exemplo que possamos experimentar?

@gaearon Eu queria criar um codesandbox, mas a versão mais recente da biblioteca tem alguns problemas. O erro não é lançado nas versões antigas. No momento, parece que a única maneira de reproduzi-lo é ativá-lo no aplicativo Create React localmente :(

@radulle Qual versão posso tentar que funcione no CodeSandbox?

@gaearon 2.6.2 funciona e gera o erro / aviso com esta configuração:
image
Portanto, para a mesma configuração:
Componente funcional: erros / avisos
Componente de classe: sem erros / avisos
Talvez eu tenha perdido algo e eles não sejam equivalentes.

Sim, este é um dos casos que me referi em https://github.com/facebook/react/issues/18178#issuecomment -600369392. Silenciaremos o aviso neste caso. O aviso em si é legítimo e, como você diz com razão, conceitualmente é um problema nas aulas também. No entanto, a discrepância não faz sentido, portanto, vamos silenciá-la para ambos os casos por enquanto, quando estiver vindo de uma classe (o que é, neste exemplo).

Acredito que haja um caso de uso legítimo para disparar atualizações de estado da função de renderização e não de um efeito, que é para preservar a ordem de execução.

Para ilustrar com um exemplo prático: em nosso aplicativo, temos um sistema peculiar de gerenciamento de breadcrumbs.

  • Temos um contexto global que contém todas as migalhas de pão
  • Temos um gancho que nos permite adicionar migalhas de pão a este contexto, como este:

    const addBreadcrumb = useAddBreadcrumb();
    addBreadcrumb(<Breadcrumb>{item.name}</Breadcrumb>, [item.name]);
    
  • Temos um componente que lê as informações do contexto e renderiza todos os breadcrumbs. Portanto, sempre que quisermos renderizar as migalhas de pão, simplesmente temos que chamá-lo sem parâmetros.

Isso é muito prático, porque não precisamos manter nenhuma estrutura de breadcrumb: essa estrutura é declarada no próprio código. Se quisermos mover uma rota para outra parte do aplicativo, o sistema breadcrumb continuará funcionando.

Então, combinado com react-router , podemos fazer algo assim:

// Main/index.tsx
import { useAddBreadcrumb } from 'components/Breadcrumbs';
import React from 'react';
import Home from './Home';
import Movies from './Movies';

const Main = () => {
    const addBreadcrumb = useAddBreadcrumb();
    addBreadcrumb(<Breadcrumb to="/">Home</Breadcrumb>, []);

    return <Switch>
        <Route path="/movies">
            <Movies />
        </Route>
        <Route path="/" />
            <Home />
        </Route>
    </Switch>
}

// Movies/index.tsx
import { useAddBreadcrumb } from 'components/Breadcrumbs';
import React from 'react';
import Detail from './Detail';
import Master from './Master';

const Movies = ({ url }) => {
    const addBreadcrumb = useAddBreadcrumb();
    addBreadcrumb(<Breadcrumb to={url}>Movies</Breadcrumb>, [url]);

    return <Switch>
        <Route path="/:id">
            <Detail />
        </Route>
        <Route path="/" />
            <Master />
        </Route>
    </Switch>
}

// Movies/Detail/index.tsx
import Breadcrumbs, { useAddBreadcrumb } from 'components/Breadcrumbs';
import React from 'react';
import { useRouteMatch } from 'react-router-dom';

const MovieDetail = ({ url }) => {
    const addBreadcrumb = useAddBreadcrumb();
    const { params: { id } } = useRouteMatch<{ id: string; }>();
    const movie = useMovie(id);

    addBreadcrumb(
        <Breadcrumb to={url}>{movie?.name}</Breadcrumb>,
        [movie?.name, url]
    );

    return <div>
        <Breadcrumbs />
    </div>
}

Agora, se formos para /movies/gone-with-the-wind , nossa localização atual será semelhante a:

Home > Movies > Gone with the wind

Agora, aqui está o meu ponto: para que isso funcione, precisamos que a ordem de execução seja garantida. Nesse caso, a ordem de execução é óbvia: primeiro Main renderiza, depois renderiza seus filhos, que incluem Movies , e finalmente MovieDetail . Neste caso, a chamada addBreadcrumb será executada na ordem correta.

Agora, o changelog afirma o seguinte:

No caso raro de querer alterar intencionalmente o estado de outro componente como resultado da renderização, você pode envolver a chamada setState em useEffect.

Este é, de fato, um dos raros casos em que queremos alterar intencionalmente o estado de outro componente. No entanto, se fizermos isso, o log de alterações sugere e envolve addBreadcrumb (que no final é um setState glorificado) em useEffect , a ordem de execução não é mais garantida. Todos os três setStates serão executados após o término da renderização, o que cria uma condição de corrida e quebra nosso sistema.

Não sei se esse sistema peculiar é considerado um antipadrão ou não, mas para mim faz sentido e não encontrei nenhuma alternativa mais simples.

Portanto, para concluir, agradeço este novo aviso, mas acho que a solução ideal para nós seria encontrar uma forma de suprimi-lo. Talvez um segundo parâmetro opcional para setState resolva o problema.

@MeLlamoPablo

Depender de chamadas de renderização na ordem dos irmãos ou da ordem de renderização pai / filho parece muito frágil. Na verdade, o React não garante isso. As crianças podem (e irão) renderizar novamente sem seus pais, bem como o contrário. Se os filhos forem renderizados com um atraso (por exemplo, divisão de código), esse código também será interrompido. Ou se alguns filhos são inseridos ou excluídos dinamicamente. Sem mencionar que isso é um desempenho muito ruim porque você tem muitos renderizadores em cascata.

Tenho empatia com o problema que você está tentando resolver - na verdade, ele não tem uma solução idiomática no React hoje. Temos algumas idéias sobre isso, mas uma solução adequada precisaria ser bem diferente. Entretanto, desencorajamos fortemente esta solução alternativa.

@gaearon , obrigado por sua visão. Eu tenho uma pergunta: a ordem da primeira renderização é garantida? Porque isso é tudo de que precisamos para funcionar corretamente (uma vez que sabemos a ordem das migalhas de pão, não nos importamos com a ordem das renderizações subsequentes).

Parece-me lógico que a ordem da primeira renderização esteja garantida. De outra forma, como o React saberia que um componente pai tem filhos?

Sobre o desempenho de renderizações em cascata, você está absolutamente certo. Procurarei maneiras de melhorar nosso sistema.

@MeLlamoPablo

Eu tenho uma pergunta: a ordem da primeira renderização é garantida? Porque isso é tudo de que precisamos para funcionar corretamente (uma vez que sabemos a ordem das migalhas de pão, não nos importamos com a ordem das renderizações subsequentes).

Não fortemente. Acho que geralmente funciona na versão atual do React, mas isso pode mudar no futuro. Mesmo hoje, combinado com recursos como lazy e Suspense , não é garantido.

De outra forma, como o React saberia que um componente pai tem filhos?

O pedido de irmão não é garantido. Para a ordem pai / filho, você está certo, os pais devem renderizar primeiro; entretanto, o React pode precisar renderizar novamente o pai antes de chegar a um filho, ou mesmo depois de renderizar o primeiro filho, mas antes do segundo. Novamente, depois de adicionar recursos como divisão de código, você perde garantias ainda mais rápido.

Isso é frágil.

@gaearon , obrigado novamente. Isso é muito apreciado.

Talvez devesse haver uma regra ESLint que avisa contra chamar useState mutadores dentro de um corpo de renderização?

Chamar setState de seu próprio componente durante a renderização é um padrão compatível (embora deva ser usado com moderação). É setState em outros componentes que são ruins. Você não pode detectá-los estaticamente.

Teoricamente, isso poderia ser feito usando ts-eslint, supondo que eles estejam usando os tipos de React upstream corretos, mas sim, provavelmente mais esforço do que vale a pena.

Eu não acho que isso poderia ser feito sem algum tipo de rastreamento de efeito. Assim que você tem uma função no meio, você perde as informações.

Também estou enfrentando esse problema com react@experimental + react-redux + redux .
image

O código é parecido com este:

import React, { Suspense } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { Redirect } from 'react-router-dom'
import PropTypes from 'prop-types'
import { userActions, cabinetActions, tokensActions } from '../../actions'
import { CircularProgress } from '@material-ui/core'
import { api } from './api'

const Cabinet = ({ resource }) => {
  resource.read()
  return <h1>cabinet</h1>
}

Cabinet.propTypes = {
  resource: PropTypes.shape({
      read: PropTypes.func
  })
}

const CabinetPage = ({
  failedToLoad,
  tokensRefreshFailed,
  logout,
  loadCabinet,
  clearTokens,
  clearCabinet
}) => {
  if (tokensRefreshFailed || failedToLoad) {
      clearTokens()
      clearCabinet()
      logout()
      return <Redirect to='/login' />
  }

  return (
      <Suspense fallback={<CircularProgress />}>
          <Cabinet resource={loadCabinet()} />
      </Suspense>
  )
}

CabinetPage.propTypes = {
  loadCabinet: PropTypes.func,
  failedToLoad: PropTypes.bool,
  tokensRefreshFailed: PropTypes.bool,
  logout: PropTypes.func,
  clearTokens: PropTypes.func,
  clearCabinet: PropTypes.func
}

const mapStateToProps = ({
  alert,
  tokens: { tokensRefreshFailed },
  cabinet: { failedToLoad }
}) => ({
  alert,
  tokensRefreshFailed,
  failedToLoad
})

const mapDispatchToProps = dispatch =>
  bindActionCreators(
      {
          cabinetLoad: cabinetActions.load,
          logout: userActions.logoutWithoutRedirect,
          loadCabinet: api.loadCabinet,
          clearCabinet: cabinetActions.clear,
          clearTokens: tokensActions.clear
      },
      dispatch
  )

export default connect(mapStateToProps, mapDispatchToProps)(CabinetPage)

loadCabinet() despacha uma ação assíncrona de renderização trifásica, como dizem os documentos simultâneos, e retorna um objeto com read() prop.
No entanto, não consigo ver nenhuma atualização dos pais aqui.

Para qualquer outra pessoa com esse problema, eu corrigi-lo movendo a ação redux despachando para um componente retornado. É assim que parece agora:

const CabinetPage = ({
    alert,
    failedToLoad,
    tokensRefreshFailed,
    logout,
    loadCabinet,
    clearTokens,
    clearCabinet,
    clearAlert
}) => (
    <Suspense fallback={<MUIBackdropProgress />}>
        {alert.message && (failedToLoad || tokensRefreshFailed) ? (
            <MUIAlertDialog
                title={alert.message}
                text={errorText}
                onClose={() => {
                    clearAlert()
                    clearCabinet()
                    clearTokens()
                    logout()
                }}
            />
        ) : (
            <Cabinet resource={loadCabinet()} />
        )}
    </Suspense>
)

Gosto deste aviso porque o força a escolher os padrões de design corretos. Agora tudo funciona perfeitamente!

Corrigimos os casos em que o aviso era disparado em 16.13.1. Os casos restantes são legítimos e precisam ser corrigidos.

@gaearon acabou de atualizar e o erro desapareceu! Obrigado pessoal pelo seu trabalho!

@gaearon, obrigado. você acabou de salvar meu dia :-)

Embora a atualização não tenha resolvido meu problema, ela me deu mais informações no console para ajudar a encontrar meu problema. Obrigado @gaearon !

E se você despachar uma ação que faz com que o outro componente retorne ao mesmo estado da última vez? Isso é considerado ruim? Eu acho que haveria um curto-circuito nesse caso.

Posso apenas dizer que, embora eu entenda totalmente a lógica por trás desse aviso ... quase parece uma traição ao que a equipe do React tem dito à comunidade, porque parece que a equipe ensinou essas verdades importantes sobre como codificar Reagir:

1) manter seu estado tão alto quanto você precisa em sua hierarquia (não mais alto) e, em seguida, passar dados e setters para componentes filhos

2) Os componentes da função são fantásticos! Esqueça o ruído da classe, faça todo o seu componente em uma função!

E agora, quando as pessoas seguem essas duas regras e passam seus definidores de estado para seus componentes de função e os chama nesses componentes ... você puxa o tapete e diz "ha, mas você não pode realmente fazer o que dissemos pendência".

Nada disso muda nada sobre as necessidades técnicas aqui, e não estou dizendo que nada sobre essa mudança seja errada ou ruim ... Eu apenas sinto que há um problema de mensagem, em que você não está comunicando regras boas e claras (como os dois Acabei de mencionar) para a codificação neste novo mundo. Se você vai mudar as nossas regras, acho que seria útil fazer isso explicando primeiro as práticas recomendadas.

Então, tudo que estou realmente pedindo é ... Acho que seria mais ideal se alguém da equipe escrevesse algo (como um artigo) onde dissesse "Eu sei que demos a vocês essas duas regras antes: aqui estão os novas regras "e, em seguida, adicione um link para o que esse artigo em cada lugar nos documentos / notas de lançamento que fazem referência a esse novo aviso (para que todos que pesquisarem" WTF é este? "no Google possam aprender a maneira adequada de codificar React, no" novo mundo").

@machineghost : Acho que você não entendeu sobre o que a mensagem está alertando.

Não há nada de errado em passar callbacks para filhos que atualizam o estado nos pais. Isso sempre foi bom.

O problema é quando um componente enfileira uma atualização em outro componente, _enquanto o primeiro componente está renderizando_.

Em outras palavras, não faça isso:

function SomeChildComponent(props) {
    props.updateSomething();
    return <div />
}

Mas está tudo bem:

function SomeChildComponent(props) {
    // or make a callback click handler and call it in there
    return <button onClick={props.updateSomething}>Click Me</button>
}

E, como Dan apontou várias vezes, enfileirar uma atualização no _same_ componente durante a renderização também é bom:

function SomeChildComponent(props) {
  const [number, setNumber] = useState(0);

  if(props.someValue > 10 && number < 5) {
    // queue an update while rendering, equivalent to getDerivedStateFromProps
    setNumber(42);
  }

  return <div>{number}</div>
}

Certo, mas quando você está codificando seu componente, você não está pensando no tempo de seu pai. Isso é parte da beleza dos componentes React: encapsulamento.

Então, novamente, não estou dizendo que o novo aviso seja ruim, estou dizendo que antes tínhamos duas regras que qualquer bom desenvolvedor do React poderia seguir. Agora, sob as condições X, essas regras são descartadas, mas apenas sob X (onde parece X = "enquanto o componente pai também está sendo atualizado").

Eu só acho que precisa haver mais foco em explicar isso e em como evitar o problema, em vez de apenas ficar tipo "isso é um problema agora!".

@machineghost : você _realmente_ não está entendendo o que estou dizendo aqui.

O timing dos pais / filhos não é o problema.

O problema é o enfileiramento de atualizações de estado _ em outros componentes enquanto renderiza um componente de função.

Por definição, tem que ser um pai / filho (ou neto): de que outra forma ele poderia ter passado a decretação do estado?

Não estou dizendo que isso também não pode ser um problema em outros relacionamentos de componentes, mas estou falando especificamente sobre as pessoas que seguem as práticas recomendadas do React, aprovam os definidores de estado e, em seguida, recebem este aviso.

Isso é tudo que estou falando, e tudo o que estou dizendo é, "poderia ser explicado melhor, com mais foco em como codificar bem em vez de apenas 'aqui está um novo erro e aqui está o que ele significa'".

Cronometragem. Não. Edição.

Um componente de função tem permissão para enfileirar uma atualização de estado, enquanto renderiza, apenas para si mesmo . Como meu exemplo mostrou, isso funciona como o equivalente a getDerivedStateFromProps .

Enfileirar uma atualização para _qualquer_ outro componente de dentro do corpo de renderização real de um componente de função é ilegal.

Isso é o que este aviso está dizendo a você.

Não sei como posso explicar isso de forma mais clara.

O tempo não é o problema: não é seu, não é meu. Meu problema é a documentação, ou a falta dela.

Mas você decidiu começar uma guerra em um tópico de discussão com um estranho da Internet, em vez de ouvir o que eles estão dizendo e ... Não tenho nenhum desejo de continuar a interagir com você.

A questão é que nenhuma regra mudou. Este sempre foi um padrão errado. Ele só agora está sendo destacado para que você saiba que seu código está cheio de erros.

E literalmente nada do que você acabou de dizer discorda de qualquer coisa que eu escrevi. Na verdade, é quase como se eu tivesse dito exatamente a mesma coisa desde o início ... e tudo que eu pedi nesse tempo todo foi uma explicação melhor dessas mesmas regras, aquelas que você diz que não mudaram (e é claro eles não ... o que mudou e "fez um novo mundo" foi o aviso).

PS: Você também parece não perceber a ironia aqui. Se não consigo entender alguma coisa, é porque a documentação pode ser melhorada. Gritar comigo sobre como eu não entendo muito bem as coisas só fortalece minha posição; não melhora magicamente a documentação.

Oi pessoal, vamos nos acalmar um pouco. 🙂

@markerikson Agradeço a sua participação, mas acho que essa discussão está ficando um pouco acalorada.

@machineghost Obrigado por expressar suas preocupações. Eu sei que é irritante quando novos avisos aparecem para padrões que podem ter parecido inócuos antes.

Eu concordo que este aviso requer algum contexto prévio. Essencialmente, você precisava saber duas coisas da era da classe:

  • Que você não deve setState durante a renderização. As aulas sempre alertaram sobre isso.

  • Esse corpo de componente de função é essencialmente a mesma coisa que método de renderização de componente de classe.

Na verdade, é nossa omissão que setState em outro componente durante o corpo do componente de função não avisou antes. Você poderia inferir que é um padrão ruim a partir dos dois pontos acima, mas é justo dizer que ninguém poderia perceber isso. Pedimos desculpas pelo transtorno.

Se você acha que há algum lugar específico nos documentos onde isso deve ser mencionado, levante um problema no repositório de documentos. Estamos planejando uma reescrita dos documentos para serem baseados em Hooks, então isso é algo que podemos manter em mente. Obrigado!

Não pretendo de forma alguma fazer ninguém se sentir mal por nada, e me recuso a aceitar suas desculpas;) Ganchos são gênios, e vocês são gênios por inventá-los. E qualquer engenheiro que culpa outro engenheiro por não imaginar perfeitamente todos os resultados é ... um idiota.

Tudo o que venho tentando comunicar é que, atualmente, quando recebi esse aviso, fiz o que todo mundo faz: pesquisei no Google. Então, encontrei uma página que dizia "temos este novo aviso".

Só acho que teria sido melhor (e ainda poderia ser melhor) se pudesse haver (pode haver) um link naquele anúncio, ou lugares semelhantes que alguém poderia encontrar pesquisando no Google, para alguma "discussão de nível superior" de "aqui está por que introduzimos esse erro e aqui está como você pode ser um desenvolvedor de React incrível e seguir algumas diretrizes básicas para nunca se deparar com ele. "

Mas, novamente, os ganchos são incríveis, a equipe React é incrível e até mesmo esse novo aviso é incrível (tenho certeza que é melhor do que descobrir o erro contra o qual está tentando se proteger). Se alguém deve um aplogoy a alguém, sou eu por não ter lidado com isso.

Claro, sem ressentimentos. A resposta para “por que” não é mais complexa do que “se um componente dispara atualizações em outros componentes durante a renderização, torna-se muito difícil rastrear o fluxo de dados de seu aplicativo porque ele não vai mais de cima para baixo”. Então, se você fizer isso, estará jogando fora os benefícios do React.

Novamente, para esclarecer, isso não é novo em si - as classes sempre tiveram o mesmo aviso. Perdemos com Hooks e estamos corrigindo o erro. Suponho que a maneira como você o corrige depende do caso - portanto, não podemos lhe dar instruções exatas - mas ficarei feliz em ajudar se você compartilhar um exemplo com o qual está tendo dificuldades. Geralmente dizendo, você o consertaria de maneira semelhante a como consertaria o aviso de classe correspondente que sempre existiu.

Espero que ajude um pouco!

Acrescentarei meus dois centavos a esta discussão e concordo com @machineghost que tem havido muita confusão desde a introdução de componentes funcionais e ganchos. A comunidade está procurando a equipe de reação em busca de liderança, mas as coisas que costumavam ser simples estão se tornando complicadas e parece haver falta de comunicação e exemplos claros.

Caso e ponto sejam ComponentDidMount e Unmount, primeiro somos informados de usar componentes de função, em seguida, use useEffect com um array vazio, então somos informados de que isso não é bom, agora temos essa bagunça de mensagem de erro. Não sei, agradeço todo o trabalho árduo feito no react, mas realmente precisamos de mais esforço na documentação e nas melhores práticas.

Eu estive no movimento das funções por tanto tempo (tentando evitar classes com Recompose e tal antes mesmo dos ganchos) que eu nem me lembro desses avisos de classe.

E embora eu aprecie sua resposta, eu estava principalmente esperando por "regras de ouro", diretrizes, práticas recomendadas, etc. Coisas como "mantenha seu estado tão alto quanto necessário para cobrir todos os componentes que o usam, mas não superior "ou" passar os definidores de estado para os componentes filhos usando a inversão do padrão de controle ".

Talvez simplesmente não haja nenhum aqui, mas talvez algo como "se o componente funcional A mudar de estado, ele não deve renderizar o componente filho B para o qual ele passa um setter de estado (ele deve retornar imediatamente ou algo assim), porque então se o o componente filho renderiza e muda de estado, você terá problemas ".

Ou talvez seja tarde de domingo, estive trabalhando o dia todo em um projeto pessoal e meu cérebro está muito frito e está transformando algo simples em algo difícil. De qualquer forma, postarei de volta se tiver mais sugestões de diretrizes, mas, por outro lado, certamente não quero que ninguém mais passe o domingo à noite nisso.

Obrigado novamente!

Acho que estamos chegando ao ponto em que este tópico perdeu sua utilidade.

Caso e ponto sejam ComponentDidMount e Unmount, primeiro somos informados de usar componentes de função, em seguida, use useEffect com um array vazio, então somos informados de que isso não é bom, agora temos essa bagunça de mensagem de erro.

Lamento que nossa documentação não tenha ajudado você, mas é muito difícil dizer quais problemas específicos você encontrou. Infelizmente, isso é muito vago e é uma distorção do que tentamos dizer. Como um jogo de telefone quebrado. Se você tiver um problema específico, registre um novo problema e tente descrevê-lo com mais detalhes. Tentaremos ajudá-lo se você puder ser mais específico.

E embora aprecie sua resposta, eu esperava principalmente por "regras de ouro", diretrizes, práticas recomendadas, etc.

A regra sempre foi “não execute efeitos colaterais durante a renderização”. Pense na renderização como uma computação pura. Os efeitos colaterais vão para um lugar diferente (métodos de ciclo de vida em classes ou useEffect em componentes de função). Não há muito mais nisso.

"se o componente funcional A muda de estado, ele não deve renderizar o componente filho B para o qual ele passa um setter de estado (ele deve retornar imediatamente ou algo assim), porque então se o componente filho renderizar e mudar de estado você terá problemas".

Acho que ainda há algum mal-entendido aqui. É perfeitamente normal passar definidores de estado para um componente filho. Sempre esteve bem e sempre estará.

O problema está em chamá-los durante a renderização . Isso geralmente deve ser completamente desnecessário. É difícil para mim adivinhar por que você está fazendo isso sem um exemplo concreto. Portanto, é difícil ajudar.

O tema geral em ambas as conversas é que estamos andando em círculos. A discussão mudou para meta e, em vez de falar sobre casos específicos, estamos discutindo generalidades vagas. É provável que nos entendamos mal, mas a falta de exemplos concretos está tornando esse mal-entendido impossível de resolver.

Por esse motivo, vou bloquear este tópico. Agradeço muito a contribuição de todos aqui e adoraríamos ouvir mais de você se tiver dificuldade para corrigir esse aviso. A maneira de obter ajuda é registrar um problema com um exemplo de reprodução mínimo. Então, podemos discutir seu problema concreto e ajudá-lo a pensar em uma solução. Isso será mais produtivo para todos os envolvidos e também evitará o envio de e-mails para dezenas de pessoas que já comentaram neste tópico e acabaram se inscrevendo. Obrigado!

Como uma pequena atualização (desculpe por pingar a todos), ouvi https://github.com/final-form/react-final-form/issues/751#issuecomment -606212893 resolver um monte desses erros para as pessoas que estavam usando isso biblioteca.

Esta página foi útil?
0 / 5 - 0 avaliações