React: Error: demasiado difícil de solucionar "No se puede actualizar un componente desde el interior del cuerpo de la función de un componente diferente".

Creado en 28 feb. 2020  ·  109Comentarios  ·  Fuente: facebook/react

# Nota: React 16.13.1 solucionó algunos casos en los que esto estaba sobrecargado. Si actualizar React y ReactDOM a 16.13.1 no soluciona la advertencia, lea esto: https://github.com/facebook/react/issues/18178#issuecomment -595846312

Reaccionar versión:

16.13.0

Pasos para reproducir

  1. Construye una máquina del tiempo.
  2. Vaya al año 2017.
  3. Cree una gran aplicación de 10K líneas de código.
  4. Obtenga 80 (!) Dependencias en el archivo package.json, incluidas las que ya no se mantienen.
  5. Actualice React a la última versión el 27 de febrero de 2020.
  6. Obtenga toneladas de errores que no sabe cómo corregir.
  7. Dígale a su cliente que las correcciones tomarán un tiempo desconocido y que costará $$$ + días o semanas de investigación o nos quedaremos atascados con la versión desactualizada de React y las bibliotecas relacionadas para siempre, lo que costará más $$$ pero despues.

Hablando en serio, el negocio para el que trabajo no está interesado en eso en absoluto. Obviamente, nunca había logrado que aparecieran tales advertencias si las recibía antes. Actualmente es increíblemente difícil corregir los errores porque los obtengo en muchos casos diferentes y con un seguimiento de pila enorme. Intenté corregir al menos uno de los errores que aparecían y ya me llevó mucho tiempo. Intenté depurar algunas de las bibliotecas usadas pero no tuve suerte.

Solo un ejemplo:

image

Allí podemos notar el uso de un enrutador de reacción desactualizado, un redux-connect desactualizado (que tuve que poner en la fuente del proyecto para corregir errores del método componentWillReceiveProps desactualizado), algunos HOC creados por recomposición, etc. no es solo un simple árbol DOM virtual donde puedo recorrer los componentes desarrollados por mí y buscar por setState string para corregir el error, eso es mucho más complicado que eso.

Seleccione una opción "NO SEGURO" para deshabilitar este error o proporcionar una forma más sencilla de encontrar dónde se produce el error 🙏

Discussion

Comentario más útil

Para los futuros comentaristas. Entiendo que ver una nueva advertencia es frustrante. Pero señala problemas legítimos que probablemente estén causando errores en su código . Le agradeceríamos mucho que se abstuviera de expresar sarcasmo y molestia.

Si no puede entender de dónde viene en los seguimientos de la pila, publique capturas de pantalla o cree cajas de arena de reproducción e intentaremos ayudarlo. La mayoría de estos probablemente provienen de algunas bibliotecas, por lo que lo más productivo es reducir estos casos y luego presentar problemas con esas bibliotecas.

Gracias a todos.

Todos 109 comentarios

Ojalá hubiéramos agregado esta advertencia antes. Siento que no lo hicimos. Fue un descuido con la introducción de Hooks. Creo que esto debe ser causado por un código más nuevo que usa Hooks, porque ya había la misma advertencia para las clases mucho antes, por lo que cualquier código anterior ya habría visto esta advertencia.

Tenga en cuenta que esto probablemente comenzará a fallar en versiones futuras de React. Intencional o no (hemos tenido muchos errores con este patrón). Entonces, independientemente, puede terminar atascado en una versión anterior. Si no es posible solucionarlo, recomiendo anclarlo a una versión anterior de React.

Sin embargo, dicho esto, queremos ayudarlo y hacer que sea lo más fácil posible solucionarlos, incluido ver si podemos obtener ayuda de los autores de la biblioteca para publicar versiones fijas.

Si expande la pequeña flecha > en la consola de Chrome, también debería ver un seguimiento de pila adicional (además de la pila de componentes en su captura de pantalla). ¿Puedes publicar eso también? Eso debería mostrarle el sitio de llamadas exacto que está causando un efecto secundario en el renderizado.

Conmigo esto aparece cuando uso formik

image

@sebmarkbage gracias por la respuesta. El stacktrace que aparece después de hacer clic en> es ridículo. ¡Contiene más de 200 artículos!

Iba a pegarlo allí o darle un enlace a pastebin, pero intenté en una dirección diferente. Caminé por los problemas de Github de algunas de las bibliotecas usadas y descubrí que uno de los sospechosos es redux-form: https://github.com/redux-form/redux-form/issues/4619. Espero que sea la única biblioteca que causa los errores y voy a esperar una solución antes de actualizar React.

Pero aún así, pediría no cerrar este problema y propongo a otros desarrolladores que mencionen aquí las bibliotecas que también causan estos errores.

@RodolfoSilva ¿estás seguro de que es causado por formik? En caso afirmativo, ¿puede crear un problema allí y publicar un enlace aquí? Voy a crear una lista de estos problemas al comienzo de mi primer mensaje si la lista va a contener más de un elemento.

Esto realmente debe abordarse lo antes posible. Hace que la actualización sea imposible. El seguimiento del error es prácticamente imposible.

Hm. Me pregunto si sería útil describir qué línea buscar en el mensaje de error.

En este caso, la primera línea a buscar es la línea después de dispatchAction . Eso debería ser lo que llama a React.

@RodolfoSilva, ¿puedes publicar la fuente de FormItemInput.js , si es algo que puedas compartir? Eso parece estar llamando a dispatch o setState en la línea 71.

Creo que es imperativo que este mensaje de error se modifique para incluir exactamente qué línea de código está causando el error. Es casi imposible determinar si es el código local o el código de biblioteca lo que está causando el problema. Las bibliotecas de bibliotecas externas

Estoy bastante seguro de que React-Redux no tiene la culpa aquí, pero estoy de acuerdo en que el mensaje de error y la pila de componentes hacen que sea un poco difícil saber qué está pasando realmente.

¡Me enfrento al mismo problema con Redux-form!

Tengo el mismo problema y veo que la advertencia se muestra la primera vez que escribo en mi campo redux o cuando lo borro todo

Yo también tengo ese problema, este es mi componente:

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

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

Entonces, en este caso, mi componente CardContainer, notifique al componente principal cuando se actualice el precio y el componente principal muestre el nuevo príncipe.
Así que supongo que React me advierte que estoy tratando de actualizar el estado de un componente usando la función de otro componente.
Soy nuevo en React, así que no estoy seguro de si se trata de un patrón de React o de un error.

Si tienen alguna sugerencia para resolver esta advertencia, se los agradecería ''.

@ l0gicgate

Creo que es imperativo que este mensaje de error se modifique para incluir exactamente qué línea de código está causando el error.

Hay límites a lo que podemos hacer en JavaScript. Pero toda la información está en la pila que ves en el navegador . Todo lo que necesita hacer es omitir las líneas que están dentro de React.

Para ver la pila de JavaScript, debe hacer clic en una pequeña flecha junto al mensaje de error .

Por ejemplo, mire esta captura de pantalla anterior:

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

Aprecio que es un poco molesto buscar en la pila, pero no debería ser tan difícil saltarse los primeros fotogramas. El siguiente cuadro es la fuente del problema. En este caso, parece algo dentro de la biblioteca de Formik. Para que pueda presentar un problema.

@martinezwilmer Este ejemplo es demasiado pequeño para ayudar. Crea una caja de arena, por favor.

Para los futuros comentaristas. Entiendo que ver una nueva advertencia es frustrante. Pero señala problemas legítimos que probablemente estén causando errores en su código . Le agradeceríamos mucho que se abstuviera de expresar sarcasmo y molestia.

Si no puede entender de dónde viene en los seguimientos de la pila, publique capturas de pantalla o cree cajas de arena de reproducción e intentaremos ayudarlo. La mayoría de estos probablemente provienen de algunas bibliotecas, por lo que lo más productivo es reducir estos casos y luego presentar problemas con esas bibliotecas.

Gracias a todos.

Es difícil encontrar la línea precisa a la que se refiere la advertencia aquí:

@gaearon , ¿de nuevo tienes un consejo al respecto?

image

Es difícil encontrar la línea precisa a la que se refiere la advertencia aquí:

¿Qué lo hace difícil? Como señalé anteriormente, es la primera línea que no dice "reaccionar" en el lado derecho. En este caso, es connectAdvanced de Redux. Por favor, presente un problema en React Redux para que los encargados del mantenimiento tengan la oportunidad de verlo.

Como dije upthread, me sorprendería mucho si lo que sea que esté sucediendo aquí sea un problema con React-Redux.

Dicho esto, ni siquiera estoy seguro de qué desencadena este mensaje en primer lugar. Entiendo a medias lo que dice el mensaje de error, pero ¿qué tipo de patrón de código de aplicación sería realmente un ejemplo de ese comportamiento?

Me encontré con esto recientemente y la solución estaba envolviendo los sitios de llamadas de setState handler en useEffect , así: https://github.com/airbnb/lunar/commit/db08613d46ea21089ead3e7b5cfff995f15c69a7#diff -1c3bdd397b1ce704514624 onChange y onSubmit usan setState más arriba en la cadena).

@martinezwilmer ¿Dónde se llama onPriceUpdated ? ¿Quizás intentar envolverlo en useEffect ?

El mismo problema parece estar sucediendo para urql

Estamos usando use-subscription + wonka (para transmisiones) para organizar nuestras actualizaciones, sin embargo, una actualización puede llegar sincrónicamente. Aquí ya hemos obtenido el todos por lo que si hacemos clic en el botón Open el resultado debería aparecer instantáneamente, sin embargo, esto parece desencadenar el siguiente error.

image

En nuestro caso, esto es lo que se pretende, no podemos simplemente mostrar fetching: true para un resultado de sincronización, eso daría como resultado interfaces con saltos.

Esto está empezando a aparecer cada vez más en bibliotecas de terceros ahora: urql , Apollo .

Me encontré con esto y durante varias horas asumí que el problema estaba en mi código. El stacktrace condensado apunta a mis componentes, y no es inusual para mí ver bibliotecas de terceros en el stacktrace expandido cuando _did_ activé explícitamente un error. De mi investigación (aunque limitada) sobre esta advertencia en particular, parece que la mayoría de los desarrolladores no están causando este problema por sí mismos, sino que dependen del código que lo hace. Por lo general, es una buena práctica asumir que no es un error en sentido ascendente, pero cuando se trata de un error en sentido ascendente, perder el tiempo rastreando un problema en su código que no existe es bastante frustrante. ¿Hay algo que React pueda hacer para ayudar a un usuario promedio a determinar si fue el código que escribieron o el código del que dependen el que causó el problema?

Una cosa que noto del problema de Apollo es:

El seguimiento de pila de la advertencia muestra el componente que inició los cambios, no el que se vuelve a representar [sic] por esos cambios.

Si esto es correcto, ¿puede React dar más información aquí? ¿Quizás diciéndonos tanto el componente de inicio como los componentes que causó que se actualizaran?

Como @hugo , encontré esto al probar una nueva aplicación Ionic :

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

De hecho, la causa está enterrada en el medio y en la parte inferior del rastro de la pila.

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 fue lo más cercano que pude encontrar con respecto a este problema.

Encontramos un patrón específico que causa este problema con mobx en https://github.com/mobxjs/mobx-react/issues/846

@sebmarkbage Ya no puedo reproducir este problema. Actualizamos algunas bibliotecas y los problemas terminaron.

@jgoux parece que nos enfrentamos al mismo problema @Clovis ^^ Spotted

Comencé a recibir este error después de que la actualización reaccionara a react 16.13.0 . El problema es bastante claro ya que uno de mis componentes está actualizando otro después de que se realiza una acción específica. Sin embargo, no estoy seguro de por qué esto arrojaría una advertencia. ¿Alguna sugerencia sobre cómo solucionar esto?

@gaearon gracias por los detalles sobre cómo depurar desde la pila, personalmente no habría podido averiguar de dónde venía este error si no me hubieras dado ese ejemplo. 🙏

Sin embargo, no estoy seguro de si mi problema está relacionado, estoy intentando actualizar el estado de mi componente de formulario, pero cada vez que intento agregar un controlador onChange, sigue dándome este error. Eso sí, estoy usando react-jsonschema-form e importé el componente Form y estoy usando su propiedad onChange para actualizar el estado.

Para mí, este es el patrón que causa el problema.

image

Puede haber una forma de evitar esto. Pero el registro de la consola me apuntó directamente a la línea 385

Soy nuevo en reaccionar pero tenía un 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;

Lo que resultó en: Cannot update a component from inside the function body of a different component.

Todo lo que tenía que hacer era agregar una función de flecha para setMobileNavOpen en MobileNav así:

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;

Y eso soluciona el problema, ¡espero que esto ayude a alguien!

Soy nuevo en reaccionar pero tenía un 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;

Lo que resultó en: Cannot update a component from inside the function body of a different component.

Todo lo que tenía que hacer era agregar una función de flecha para setMobileNavOpen en MobileNav así:

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;

Y eso soluciona el problema, ¡espero que esto ayude a alguien!

Su ejemplo es en realidad uno de los primeros errores que comete la gente al reaccionar. No estoy seguro de que sea exactamente el mismo tema que se está discutiendo aquí. Su línea aquí: onClick={setMobileNavOpen(false) está llamando a la función durante la licitación del botón, no al hacer clic. Es por eso que envolverlo en una función de flecha lo arregla.

Me encontré con este problema con React Router donde necesitaba enviar una acción Redux antes de <Redirect> llevar al usuario a una ubicación diferente. El problema parecía ser que la redirección se produjo antes de que finalizara el envío. Arreglé el problema prometiendo mi acción.

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

Después:

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

Creo que haría que el mensaje de error incluyera nombres tanto para el componente que se está procesando actualmente como para el componente cuyo setState se está llamando. ¿Alguien quiere enviar un PR para esto?

Creo que haría que el mensaje de error incluyera nombres tanto para el componente que se está procesando actualmente como para el componente cuyo setState se está llamando. ¿Alguien quiere enviar un PR para esto?

Estoy feliz de echarle un vistazo a esto. ¿Algún consejo que deba tener en cuenta?

@ samcooke98 He abierto un PR para este https://github.com/facebook/react/pull/18316

Como otros han señalado, puede encontrarse con este problema si tiene una suscripción en un enlace como con Apollo e intenta actualizar una tienda para recibir datos. La solución simple es 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ábamos enfrentando este problema en Hyper, pero no usamos ganchos y no pudimos encontrar nada en la llamada de render de la pila de llamadas. Pero hubo una llamada en UNSAFE_componentWillReceiveProps en la pila. Actualizar eso con componentDidUpdate solucionó el problema para nosotros https://github.com/zeit/hyper/pull/4382
Publíquelo aquí si puede ayudar a alguien

Lo mismo aquí, hubo una llamada UNSAFE_componentWillMount, cambiarla / eliminarla solucionó el problema

pero no estamos usando ganchos y no pudimos encontrar nada en la llamada de renderización desde la pila de llamadas

Esto suena extraño. Entonces no estoy seguro de cómo está recibiendo esta advertencia. Solo se activa si setState pertenece a un componente de función. ¿Cómo es tu pila?

pero no estamos usando ganchos y no pudimos encontrar nada en la llamada de renderización desde la pila de llamadas

Esto suena extraño. Entonces no estoy seguro de cómo está recibiendo esta advertencia. Solo se activa si setState pertenece a un componente de función. ¿Cómo es tu pila?

Componentes de clase con Redux y funciones que aplican complementos a los componentes. ¿Quizás por eso se cuenta como un componente de función? Pero entonces, ¿por qué lo soluciona la actualización de los ganchos del ciclo de vida?

No estoy seguro. ¿Puedes intentar crear un ejemplo aislado en una caja de arena? Tengo el presentimiento de que podrías estar haciendo otra cosa inesperada.

No estoy seguro de poder replicarlo en un ejemplo aislado. Me costó mucho intentar encontrar la causa y acababa de actualizar el enlace del ciclo de vida, ya que estaba en el seguimiento de la pila y estaba pendiente de actualización. Eso de alguna manera solucionó el problema.
Puede echar un vistazo al repositorio en el momento en que tuvo el problema aquí

¿Puede ser por el hecho de que tanto UNSAFE_componentWillReceiveProps como componentDidUpdate estaban en el componente en ese momento (se dejó uno inseguro por error)?

También recibo esta advertencia, y el seguimiento de la pila apunta al gancho setScriptLoaded (vea a continuación mi gancho personalizado). Entonces parece que aunque estoy usando useEffect , React todavía lanzará una advertencia si el controlador setState está anidado dentro de otras devoluciones de llamada asíncronas (en mi caso, está anidado en un setTimeout y un controlador de eventos load )? Es la primera vez que uso Hooks, por lo que agradecería cualquier consejo. ¡Gracias!

/**
 * 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

¿Puede ser debido al hecho de que tanto UNSAFE_componentWillReceiveProps como componentDidUpdate estaban en el componente en ese momento (uno inseguro se dejó allí por error)?

No creo que ningún método de ciclo de vida sea relevante aquí en absoluto. Por eso digo que algo es extraño en tu ejemplo.

@suhanw Proporcione un ejemplo completo en CodeSandbox. No veo ningún problema con su código que deba estar causando esta advertencia.

@LabhanshAgrawal ¿Puedes publicar tu pila completa, por favor? Creo que lo que puede estar sucediendo es que su UNSAFE_componentWillReceiveProps (que es equivalente a renderizar) llama a setState en otro 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 la rápida respuesta. Descubriré una manera de crear un ejemplo en CodeSandbox (es un desafío), pero mientras tanto, este es mi seguimiento de pila completo

la línea que apunta a mi gancho personalizado es esta: https://gist.github.com/suhanw/bcc2688bba131df8301dae073977654f#file -stack-trace-L144

Sería increíble si pudiera echar un vistazo y hacerme saber si hay algo en el seguimiento de la pila antes / después de mi gancho personalizado que debería investigar más a fondo. ¡Gracias!

@LabhanshAgrawal En su pila, UNSAFE_componentWillReceiveProps llama a algo fitResize que envía una acción Redux, que a su vez actualiza un montón de componentes. De ahí el problema. Así que sí, cambiar eso a componentDidUpdate funciona.

@suhanw En su pila, algo llamado ModuleImpressionTracker parece enviar una acción Redux durante un constructor. Los constructores no deberían tener efectos secundarios. Creo que esa es la causa del problema, no tu Hook.

Ya veo, así que deduzco de esto que UNSAFE_componentWillReceiveProps se cuenta como renderizado pero componentDidUpdate no lo es.
¿Puede aclarar un poco que es un problema con los componentes funcionales y los ganchos que hacen setState? No podría obtenerlo claramente de la discusión anterior.
Disculpas si la pregunta es un poco tonta o mi respuesta es incorrecta, soy un poco nuevo en esto.

@LabhanshAgrawal

¿Puede aclarar un poco que es un problema con los componentes funcionales y los ganchos que hacen setState? No podría obtenerlo claramente de la discusión anterior.

Honestamente, yo mismo no estoy seguro debido a todas las cosas de Redux en el medio. Es bastante confuso debido a cómo se implementa React Redux. Sería bueno si alguien pudiera obtener una reproducción clara que involucre solo a React pero que use componentes de clase.

Todavía parece que se están pegando muchos rastros de pila y no muchos repros reales del problema real, independientemente del tipo.

Supongo que el de urql está destinado a @gaearon, así que lo que sucede es:

  • Tenemos nuestro <Todos /> montado, que recupera los datos y los renderiza
  • La anterior configuró una transmisión conectada al urqlClient
  • Representamos nuestro segundo <Todos /> esto producirá la misma combinación de consulta + variables, por lo que actualizará el resultado para <Todos /> del primer paso.
  • use-subscription se activa para ambos.

Repro :

Podríamos poner en cola las actualizaciones, pero mientras no hagamos top-down renderizados con contexto, no podremos eludir este problema, supongo. En teoría, este es el comportamiento previsto para este caso. Es curioso cómo trabaja Relay en torno a esto.

EDITAR: podríamos haber encontrado una solución para el caso de urql al no hacer que todos los suscriptores se actualicen durante el getCurrentValue de un use-subscription

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

Bien, así que mirando más profundamente en esto, creo que estamos advirtiendo en más casos (por ejemplo, para las clases) de lo que pretendíamos originalmente. Veremos cómo silenciar algunos de esos.

@JoviDeCroock Este ejemplo no es muy útil porque no tengo idea de lo que hace urql . :-) Si desea comentarios sobre un patrón, ¿podría preparar una caja de arena que demuestre ese patrón de forma aislada?

@JoviDeCroock Sí, getCurrentValue definitivamente no tiene la intención de tener efectos secundarios. Pensamos que ese nombre es bastante explícito al respecto.

Tuve un problema en el que recibía esta advertencia al enviar una acción redux dentro del alcance raíz de un gancho personalizado.

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

Arreglé esto envolviendo el envío en un useEffect .

@Glinkis Eso suena como un mal patrón independientemente. ¿Por qué querría notificar a todo el árbol cada vez que se haya renderizado un componente?

@gaearon sí, el problema con el que estábamos tratando de resolver se explica mejor aquí y parece bastante común.

@Glinkis Eso suena como un mal patrón independientemente. ¿Por qué querría notificar a todo el árbol cada vez que se haya renderizado un componente?

Necesito mantener mi estado redux sincronizado con los ID de mi ruta, por lo que envío esta actualización cuando cambia mi ruta.

Sé que esto puede ser subóptimo, pero no he encontrado otra forma de acceder a los datos de enrutamiento dentro de mis selectores.

@Glinkis ¿ provienen los datos de la ruta?

@JoviDeCroock Creo que nuestra última recomendación para ese patrón es un pase de recolección de basura programado personalizado.

@Glinkis ¿ provienen los datos de la ruta?

No estoy seguro de si esto pertenece a esta discusión, pero este es mi 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])
}

Luego uso este estado para selectores como:

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

@Glinkis Sí, tal vez terminemos aquí, pero mi sugerencia sería leer useRouteMatch en el componente principal, pasar la ID como un accesorio y luego leer los accesorios en el selector como lo haría normalmente. (En realidad, no sé cómo se hace esto en Redux en estos días, pero debe haber alguna forma).

Ya veo, así que deduzco de esto que UNSAFE_componentWillReceiveProps se cuenta como renderizado pero componentDidUpdate no lo es.

@LabhanshAgrawal Eso es correcto. Alguna explicación de fondo aquí :

Conceptualmente, React funciona en dos fases:

  • La fase de renderización determina qué cambios deben realizarse, por ejemplo, en el DOM. Durante esta fase, React llama a render y luego compara el resultado con el render anterior.
  • La fase de confirmación es cuando React aplica los cambios. (En el caso de React DOM, esto es cuando React inserta, actualiza y elimina los nodos DOM). React también llama a ciclos de vida como componentDidMount y componentDidUpdate durante esta fase.

La fase de confirmación suele ser muy rápida, pero el procesamiento puede ser lento. Por esta razón, el próximo modo concurrente (que aún no está habilitado por defecto) rompe el trabajo de renderizado en pedazos, pausando y reanudando el trabajo para evitar bloquear el navegador. Esto significa que React puede invocar ciclos de vida de la fase de renderización más de una vez antes de comprometerse, o puede invocarlos sin comprometerse en absoluto (debido a un error o una interrupción de mayor prioridad).

Los ciclos de vida de la fase de renderizado incluyen los siguientes métodos de componentes de clase:

  • constructor
  • componentWillMount (o UNSAFE_componentWillMount )
  • componentWillReceiveProps (o UNSAFE_componentWillReceiveProps )
  • componentWillUpdate (o UNSAFE_componentWillUpdate )
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • setState funciones de actualización (el primer argumento)

Debido a que los métodos anteriores pueden llamarse más de una vez, es importante que no contengan efectos secundarios. Ignorar esta regla puede provocar una variedad de problemas, incluidas pérdidas de memoria y estado de aplicación no válido. Desafortunadamente, puede ser difícil detectar estos problemas, ya que a menudo pueden ser no deterministas .

Bien, así que mirando más profundamente en esto, creo que estamos advirtiendo en más casos (por ejemplo, para las clases) de lo que pretendíamos originalmente. Veremos cómo silenciar algunos de esos.

Supongo que incluso si es más de lo previsto, en mi opinión es bueno tenerlo, ya que ayuda a mejorar nuestros proyectos usando clases.

Este mensaje de error autoexplicativo "No se puede actualizar un componente desde el interior del cuerpo de la función de un componente diferente"

Eso significa ejecutar como una función en lugar de llamar al componente.

ejemplo 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 No creo que el ejemplo que está mostrando esté relacionado con esta advertencia.

https://github.com/facebook/react/pull/18330 resolverá los casos que no teníamos la intención de comenzar a disparar.

También estoy enfrentando este problema con react@experimental + react-redux + redux .
image

El código se ve así:

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() envía una acción asíncrona de renderizado en tres fases como dicen los documentos concurrentes, y devuelve un objeto con read() prop.
Sin embargo, no puedo ver ninguna actualización de los padres aquí.

@ h0tw4t3r Está enviando una acción de Redux durante el procesamiento de un componente. Esto no es compatible, y de eso se trata la advertencia. Es mejor preguntar a los expertos de React Router sobre cómo manejar este caso (redireccionar) con elegancia, no puedo ayudar con esa parte.

Con respecto al modo concurrente, tenga en cuenta que Redux no es actualmente compatible con él en general. Por lo tanto, es posible que desee evitarlo si está experimentando con CM.

Pequeña actualización en este hilo

Pronto lanzaremos un parche de React que corrige la activación excesiva de esta advertencia para las clases. Entonces, si experimenta esto, considere esperar unos días y luego probar 16.13.1 cuando esté disponible.

Si desea seguir buscando las causas, espero que https://github.com/facebook/react/issues/18178#issuecomment -595846312 explique cómo.

Me parece muy extraño que la misma lógica cuando se usa en un componente de clase no da ninguna advertencia, mientras que los funcionales (ganchos) sí lo hacen:

Componente funcional (Hooks):

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 clase:

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 no es un ejemplo muy útil en sí mismo. Intenté ponerlo en CodeSandbox pero no funciona: https://codesandbox.io/s/clever-taussig-9xixs. ¿Puedes preparar un ejemplo que podamos probar?

@gaearon Quería crear un codesandbox pero la última versión de la biblioteca tiene algunos problemas. El error no se lanza en las versiones antiguas. Por el momento, parece que la única forma de reproducirlo es activarlo en la aplicación Create React de forma local :(

@radulle ¿Qué versión puedo probar que funcione en CodeSandbox?

@gaearon 2.6.2 funciona y arroja el error / advertencia con esta configuración:
image
Entonces, para la misma configuración:
Componente funcional: errores / advertencias
Componente de clase: sin errores / advertencias
Tal vez me haya perdido algo y no sean equivalentes.

Sí, este es uno de los casos a los que me referí en https://github.com/facebook/react/issues/18178#issuecomment -600369392. Silenciaremos la advertencia en este caso. La advertencia en sí es legítima y, como bien dice, conceptualmente también es un problema en las clases. Sin embargo, la discrepancia no tiene sentido, por lo que la silenciaremos para ambos casos por ahora cuando provenga de una clase (que es, en este ejemplo).

Creo que existe un caso de uso legítimo para activar actualizaciones de estado desde la función de renderizado y no desde un efecto, y es preservar el orden de ejecución.

Para ilustrarlo con un ejemplo práctico: en nuestra aplicación, tenemos un peculiar sistema de gestión de migas de pan.

  • Tenemos un contexto global que contiene todas las migas de pan
  • Tenemos un gancho que nos permite agregar migas de pan a este contexto, así:

    const addBreadcrumb = useAddBreadcrumb();
    addBreadcrumb(<Breadcrumb>{item.name}</Breadcrumb>, [item.name]);
    
  • Tenemos un componente que lee la información del contexto y procesa todas las rutas de navegación. Entonces, siempre que queramos renderizar las migas de pan, simplemente tenemos que llamarlo sin parámetros.

Esto es muy práctico, porque no tenemos que mantener ninguna estructura de ruta de navegación: esta estructura está declarada en el código mismo. Si queremos mover una ruta a otra parte de la aplicación, el sistema de breadcrumb seguirá funcionando.

Entonces, combinado con react-router , podemos hacer algo como esto:

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

Ahora, si vamos a /movies/gone-with-the-wind , nuestras migas de pan se verán así:

Home > Movies > Gone with the wind

Ahora, aquí está mi punto: para que eso funcione, necesitamos que la orden de ejecución esté garantizada. En este caso, el orden de ejecución es obvio: primero Main renderiza, luego renderiza sus hijos, que incluyen Movies , y finalmente MovieDetail . En este caso, la llamada addBreadcrumb se ejecutará en el orden correcto.

Ahora, el registro de cambios dice esto:

En el raro caso de que desee cambiar intencionalmente el estado de otro componente como resultado de la renderización, puede envolver la llamada setState en useEffect.

Este es, de hecho, uno de los raros casos en los que queremos cambiar intencionalmente el estado de otro componente. Sin embargo, si hacemos lo que sugiere el registro de cambios y envolvemos el addBreadcrumb (que al final es un setState glorificado) en useEffect , el orden de ejecución ya no está garantizado. Los tres setStates se ejecutarán una vez finalizada la renderización, lo que crea una condición de carrera y rompe nuestro sistema.

No sé si este peculiar sistema se considera un antipatrón, pero para mí tiene sentido y no he encontrado ninguna alternativa más sencilla.

Entonces, para concluir, acojo con satisfacción esta nueva advertencia, pero creo que la solución óptima para nosotros sería tener una forma de suprimirla. Quizás un segundo parámetro opcional para setState haría el truco.

@MeLlamoPablo

Confiar en que las llamadas de procesamiento estén en el orden de los hermanos o el orden de procesamiento padre / hijo suena muy frágil. React no garantiza esto en realidad. Los niños pueden (y lo harán) volver a renderizar sin sus padres, y también al revés. Si los niños se procesan con un retraso (por ejemplo, división de código), este código también se rompería. O si algunos niños se insertan o eliminan dinámicamente. Sin mencionar que esto es bastante malo en cuanto al rendimiento porque tienes tantos renderizados en cascada.

Siento empatía con el problema que estás tratando de resolver; de hecho, hoy no tiene una solución idiomática en React. Tenemos algunas ideas al respecto, pero una solución adecuada debería ser bastante diferente. Mientras tanto, desaconsejamos encarecidamente esta solución.

@gaearon , gracias por tu información. Tengo una pregunta: ¿está garantizado el orden del primer renderizado? Porque eso es todo lo que necesitamos para funcionar correctamente (una vez que conocemos el orden de las rutas de navegación, no nos importa el orden de las representaciones posteriores).

Me parece lógico que el orden del primer render esté garantizado. ¿Cómo sabría React que un componente padre tiene hijos de otra manera?

Sobre el rendimiento de los renders en cascada, tiene toda la razón. Buscaré formas de mejorar nuestro sistema.

@MeLlamoPablo

Tengo una pregunta: ¿está garantizado el orden del primer renderizado? Porque eso es todo lo que necesitamos para funcionar correctamente (una vez que conocemos el orden de las rutas de navegación, no nos importa el orden de las representaciones posteriores).

No mucho. Creo que sobre todo funciona en la versión actual de React, pero eso puede cambiar en el futuro. Incluso hoy, combinado con características como lazy y Suspense no está garantizado.

¿Cómo sabría React que un componente padre tiene hijos de otra manera?

El pedido de hermanos no está garantizado. Para la orden de padre / hijo, tiene razón, los padres tienen que rendir primero; sin embargo, React puede necesitar volver a renderizar el padre antes de que llegue a un hijo, o incluso después de que haya renderizado el primer hijo pero antes del segundo. Nuevamente, una vez que agrega funciones como la división de código, pierde garantías aún más rápido.

Esto es frágil.

@gaearon , gracias de nuevo. Esto es muy apreciado.

¿Quizás debería haber una regla ESLint que advierta contra la llamada useState mutators dentro de un cuerpo de renderizado?

Llamar a setState de su propio componente durante el renderizado es un patrón compatible (aunque debe usarse con moderación). Está setState en otros componentes que son malos. No puedes detectarlos estáticamente.

En teoría, podría hacerse usando ts-eslint, asumiendo que están usando los tipos de React ascendentes correctos, pero sí, probablemente más esfuerzo de lo que vale.

No creo que se pueda hacer sin algún tipo de seguimiento de efectos. Tan pronto como tenga una función en el medio, perderá la información.

También estoy enfrentando este problema con react@experimental + react-redux + redux .
image

El código se ve así:

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() envía una acción asíncrona de renderizado en tres fases como dicen los documentos concurrentes, y devuelve un objeto con read() prop.
Sin embargo, no puedo ver ninguna actualización de los padres aquí.

Para cualquier otra persona que tenga este problema, lo solucioné moviendo el envío de acciones redux a un componente devuelto. Así es como se ve ahora:

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

Me gusta esta advertencia porque te obliga a elegir patrones de diseño correctos. ¡Ahora todo funciona a la perfección!

Arreglamos los casos en los que la advertencia era excesiva en 16.13.1. Los casos restantes son legítimos y deben arreglarse.

¡@gaearon acaba de actualizar y el error desapareció! ¡Gracias chicos por su trabajo!

@gaearon gracias. me acabas de salvar el día :-)

Si bien la actualización no resolvió mi problema, me brindó más información en la consola para ayudarme a encontrar mi problema. ¡Gracias @gaearon !

¿Qué sucede si envía una acción que hace que el otro componente devuelva el mismo estado que la última vez? ¿Eso se considera malo? Creo que se produciría un cortocircuito en ese caso.

¿Puedo simplemente decir que esto, si bien entiendo totalmente la lógica detrás de esta advertencia ... casi se siente como una traición a lo que el equipo de React le ha estado diciendo a la comunidad, porque parece que el equipo ha enseñado estas importantes verdades sobre cómo codificar? Reaccionar:

1) mantenga su estado tan alto como necesite en su jerarquía (no más alto), y luego pase los datos y los establecedores a los componentes secundarios

2) ¡Los componentes de función son increíbles! Olvídese de ese ruido de clase, ¡haga todo su componente en una función!

Y ahora, cuando la gente sigue esas dos reglas, y pasa sus establecedores de estado a sus componentes de función, y los llama en esos componentes ... saca la alfombra y dice "ja, pero en realidad no puede hacer lo que le dijimos hacer".

Nada de esto cambia nada sobre las necesidades técnicas aquí, y no estoy diciendo que nada sobre este cambio sea incorrecto o malo ... Solo siento que hay un problema de mensajería, ya que no está comunicando reglas claras y buenas (como las dos Acabo de mencionar) para codificar en este nuevo mundo. Si va a cambiar nuestras reglas, creo que sería útil hacerlo explicando primero las mejores prácticas.

Entonces, todo lo que realmente estoy preguntando es ... Creo que sería más ideal si alguien en el equipo escribiera algo (como un artículo) donde diga "Sé que te dimos esas dos reglas antes: aquí están las nuevas reglas ", y luego agregue un enlace a lo que ese artículo en cada lugar en los documentos / notas de la versión que hacen referencia a esta nueva advertencia (para que todos los que busquen en Google" WTF es esto? "puedan aprender la forma correcta de codificar React, en el" nuevo mundo").

@machineghost : Creo que estás malinterpretando sobre qué advierte el mensaje.

No hay nada de malo en pasar devoluciones de llamada a los niños que actualizan el estado en los padres. Eso siempre ha estado bien.

El problema es cuando un componente pone en cola una actualización en otro componente, mientras que el primer componente se está procesando.

En otras palabras, no hagas esto:

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

Pero esto está bien:

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

Y, como Dan ha señalado varias veces, poner en cola una actualización en el componente _mismo_ mientras se renderiza también está bien:

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

Correcto, pero cuando estás codificando tu componente no estás pensando en la sincronización de su padre. Eso es parte de la belleza de los componentes de React: encapsulación.

Entonces, nuevamente, no estoy diciendo que la nueva advertencia sea mala en absoluto, estoy diciendo que antes teníamos dos reglas que cualquier buen desarrollador de React podría seguir. Ahora, en condiciones X, esas reglas desaparecen por la ventana, pero solo en X (donde suena como X = "mientras que el componente principal también se está actualizando").

Creo que es necesario centrarse más en explicar eso y en cómo evitar el problema, en lugar de simplemente decir "¡esto es un problema ahora!".

@machineghost :

El tiempo entre padres e hijos no es el problema.

El problema es poner en cola actualizaciones de estado _en otros componentes mientras se renderiza un componente de función_.

Por definición, tiene que ser padre / hijo (o nieto): ¿de qué otra manera podría haber pasado al establecedor estatal?

No estoy diciendo que esto no pueda ser un problema en las relaciones de otros componentes, pero estoy hablando específicamente de personas que siguen las mejores prácticas de React, pasan los establecedores de estado y luego reciben esta advertencia.

Eso es todo de lo que estoy hablando, y todo lo que digo es, "podría explicarse mejor, con más enfoque en cómo codificar bien en lugar de simplemente 'aquí hay un nuevo error y esto es lo que significa'".

Momento. No. Asunto.

Un componente de función puede poner en cola una actualización de estado, mientras se procesa, solo para sí mismo . Como mostró mi ejemplo, eso actúa como el equivalente de getDerivedStateFromProps .

Poner en cola una actualización para _cualquier_ otro componente desde dentro del cuerpo de representación real de un componente de función es ilegal.

Eso es lo que le dice esta advertencia.

No sé cómo puedo explicarlo más claramente.

El tiempo no es el problema: no es tuyo, no es mío. Mi problema es la documentación o la falta de ella.

Pero has decidido comenzar una guerra en un hilo de problemas con un extraño en Internet, en lugar de escuchar lo que están diciendo y ... No tengo ningún deseo de seguir comprometiéndome contigo.

El caso es que no ha cambiado ninguna regla. Este siempre ha sido un patrón incorrecto. Ahora está resaltado para que sepa que su código tiene errores.

Y, literalmente, nada de lo que acaba de decir no está de acuerdo con nada de lo que escribí. De hecho, es casi como si hubiera dicho exactamente lo mismo desde el principio ... y todo lo que pedí en todo este tiempo fue una mejor explicación de esas mismas reglas, las que dices que no han cambiado (y por supuesto no han ... lo que cambió y "hizo un mundo nuevo" fue la advertencia).

PD: También parece que no se da cuenta de la ironía aquí. Si no entiendo algo, eso significa que la documentación podría mejorarse. Gritarme sobre lo mal que entiendo las cosas solo fortalece mi posición; no mejora mágicamente la documentación.

Hola amigos, vamos a enfriarnos un poco. 🙂

@markerikson Le involucrado , pero creo que esta discusión se está volviendo un poco acalorada.

@machineghost Gracias por expresar sus inquietudes. Sé que es molesto cuando aparecen nuevas advertencias de patrones que podrían haber parecido inocuos antes.

Estoy de acuerdo en que esta advertencia requiere un contexto previo. Básicamente, necesitabas saber dos cosas de la era de las clases:

  • Que no deberías setState durante el render. Las clases siempre advirtieron sobre esto.

  • Ese cuerpo del componente de función es esencialmente lo mismo que el método de representación del componente de clase.

De hecho, es nuestra omisión que setState en otro componente durante el cuerpo del componente de función no advirtió antes. Se podría inferir que es un patrón incorrecto a partir de los dos puntos anteriores, pero es justo decir que uno no pudo darse cuenta. Lamentamos las molestias.

Si cree que hay algún lugar en particular en los documentos donde se debe mencionar esto, plantee un problema en el repositorio de documentos. Estamos planeando una reescritura de los documentos para que se basen en Hooks, por lo que eso es algo que podemos tener en cuenta. ¡Gracias!

De ninguna manera pretendo hacer que nadie se sienta mal por nada, y me niego a aceptar tus disculpas;) Los Hooks son genios, y todos ustedes son genios por inventarlos. Y cualquier ingeniero que culpe a otro ingeniero por no imaginarse perfectamente todos los resultados es ... un idiota.

Todo lo que he estado tratando de comunicar es que, actualmente, cuando recibí esta advertencia, hice lo que todos hacen: lo busqué en Google. Luego encontré una página que decía "tenemos esta nueva advertencia".

Creo que hubiera sido mejor (y aún podría ser mejor) si hubiera (puede haber) un vínculo en ese anuncio, o lugares similares que alguien podría encontrar al buscar en Google, a alguna "discusión de nivel superior" de "aquí está por qué introdujimos este error y aquí le mostramos cómo puede ser un desarrollador de React increíble y seguir algunas pautas básicas para no encontrarse nunca con él ".

Pero de nuevo, los ganchos son increíbles, el equipo de React es increíble e incluso esta nueva advertencia es increíble (estoy seguro de que es mejor que descubra el error del que está tratando de protegerse). Si alguien le debe a alguien una disculpa soy yo por no liderar con eso.

Seguro, sin resentimientos. La respuesta al "por qué" no es más compleja que "si un componente activa actualizaciones en otros componentes durante el renderizado, se vuelve muy difícil rastrear el flujo de datos de su aplicación porque ya no va de arriba hacia abajo". Entonces, si haces eso, estás desperdiciando los beneficios de React.

Nuevamente, para aclarar, esto no es nuevo en sí mismo: las clases siempre tenían la misma advertencia. Nos lo perdimos con Hooks y estamos rectificando el error. Supongo que la forma en que lo arregle depende del caso, por lo que no podemos darle instrucciones exactas, pero feliz de ayudar si comparte un ejemplo con el que está luchando. En general, lo arreglaría de manera similar a cómo arreglaría la advertencia de clase correspondiente que siempre existió.

¡Espero que eso ayude un poco!

Agregaré mis dos centavos a esta discusión y estoy de acuerdo con @machineghost en que ha habido mucha confusión desde la introducción de componentes funcionales y ganchos. La comunidad está buscando liderazgo en el equipo de reacción, pero las cosas que solían ser simples se están volviendo complicadas y parece haber una falta de comunicación y ejemplos claros.

El caso y el punto son ComponentDidMount y Unmount, primero nos dicen que usemos los componentes de la función, luego usemos useEffect con una matriz vacía, luego nos dicen que eso no es bueno, ahora tenemos este mensaje de error. No lo sé, agradezco todo el trabajo duro para reaccionar, pero realmente necesitamos más esfuerzo en la documentación y las mejores prácticas.

He estado en el tren de la función durante tanto tiempo (tratando de evitar clases con Recompose y demás incluso antes de los hooks) que ni siquiera recuerdo esas advertencias de clase.

Y aunque aprecio tu respuesta, principalmente esperaba "reglas generales", directrices, mejores prácticas, etc. Cosas como "mantén tu estado tan alto como debe ser para cubrir todos los componentes que lo usan, pero no más alto "o" pasar los establecedores de estado a los componentes secundarios mediante la inversión del patrón de control ".

Tal vez simplemente no haya ninguno aquí, pero tal vez algo como "si el componente funcional A cambia de estado, no debería representar el componente secundario B al que le pasa un establecedor de estado (debería regresar en ese momento o algo así), porque entonces si el el componente secundario representa y cambia el estado que tendrá problemas ".

O tal vez es tarde el domingo, he estado trabajando todo el día en un proyecto personal y mi cerebro está demasiado frito y está convirtiendo algo simple en algo difícil. De cualquier manera, volveré a publicar si tengo más sugerencias para las pautas, pero de lo contrario, ciertamente no quiero que nadie más pase la noche del domingo en esto.

¡Gracias de nuevo!

Creo que estamos llegando al punto en que este hilo ha dejado de ser útil.

El caso y el punto son ComponentDidMount y Unmount, primero nos dicen que usemos los componentes de la función, luego usemos useEffect con una matriz vacía, luego nos dicen que eso no es bueno, ahora tenemos este mensaje de error.

Lamento que nuestra documentación no le haya ayudado, pero es muy difícil decir con qué problemas específicos se encontró. Desafortunadamente, esto es muy vago y es una distorsión de lo que hemos intentado decir. Como un juego de teléfono roto. Si tiene un problema específico, presente un nuevo problema e intente describirlo con más detalle. Intentaremos ayudarlo si puede ser más específico.

Y aunque agradezco su respuesta, principalmente solo esperaba "reglas generales", pautas, mejores prácticas, etc.

La regla general siempre ha sido "no realizar efectos secundarios mientras se renderiza". Piense en el renderizado como un puro cálculo. Los efectos secundarios van a un lugar diferente (métodos de ciclo de vida en clases o useEffect en componentes de función). No hay mucho más.

"si el componente funcional A cambia de estado, no debería representar el componente secundario B al que le pasa un establecedor de estado (debería regresar en ese momento o algo así), porque entonces si el componente secundario se procesa y cambia de estado, tendrá problemas".

Creo que todavía hay algunos malentendidos aquí. Está perfectamente bien pasar los establecedores de estado a un componente secundario. Siempre ha estado bien y siempre lo estará.

El problema está en llamarlos mientras se renderiza . Eso generalmente debería ser completamente innecesario. Es difícil para mí adivinar por qué lo está haciendo sin un ejemplo concreto. Entonces es difícil ayudar.

El tema general en ambas conversaciones es que vamos en círculos. La discusión ha pasado a ser meta, y en lugar de hablar de casos específicos, estamos discutiendo vagas generalidades. Es probable que nos malinterpretemos, pero la falta de ejemplos concretos hace que este malentendido sea imposible de resolver.

Por esta razón voy a bloquear este hilo. Aprecio mucho la contribución de todos aquí, y nos encantaría saber más de usted si tiene dificultades para solucionar esta advertencia. La forma de obtener ayuda es presentar un problema con un ejemplo de reproducción mínimo. Luego, podemos discutir su problema concreto y ayudar a pensar en una solución. Esto será más productivo para todos los involucrados y también evitará enviar correos electrónicos a decenas de personas que ya han comentado este hilo y terminaron suscribiéndose. ¡Gracias!

Como una pequeña actualización (lo siento por hacer ping a todos), escuché que https://github.com/final-form/react-final-form/issues/751#issuecomment -606212893 resolvió un montón de estos errores para las personas que estaban usando eso Biblioteca.

¿Fue útil esta página
0 / 5 - 0 calificaciones