React: [ESLint] Comentarios sobre la regla de pelusa 'exhaustive-deps'

Creado en 21 feb. 2019  ·  111Comentarios  ·  Fuente: facebook/react

Respuestas Comunes

💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡

Analizamos los comentarios en esta publicación para proporcionar alguna orientación: https://github.com/facebook/react/issues/14920#issuecomment -471070149.

💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡


Que es esto

Esta es una nueva regla de ESLint que verifica la lista de dependencias para Hooks como useEffect y similares, protegiendo contra los errores de cierre obsoletos. En la mayoría de los casos tiene autofijación. Agregaremos más documentación durante las próximas semanas.

autofix demo

Instalación

yarn add eslint-plugin-react-hooks<strong i="18">@next</strong>
# or
npm install eslint-plugin-react-hooks<strong i="19">@next</strong>

Configuración de ESLint:

{
  "plugins": ["react-hooks"],
  // ...
  "rules": {
    "react-hooks/rules-of-hooks": 'error',
    "react-hooks/exhaustive-deps": 'warn' // <--- THIS IS THE NEW RULE
  }
}

Caso de prueba simple para verificar que la regla funcione:

function Foo(props) {
  useEffect(() => {
    console.log(props.name);
  }, []); // <-- should error and offer autofix to [props.name]
}

La regla de pelusa se queja, ¡pero mi código está bien!

Si esta nueva regla react-hooks/exhaustive-deps lint se activa pero cree que su código es correcto , publique en este número.


ANTES DE PUBLICAR UN COMENTARIO

Por favor, incluya estas tres cosas:

  1. Un CodeSandbox que demuestra un ejemplo de código mínimo que aún expresa su intención (no "barra de foo", sino el patrón de interfaz de usuario real que está implementando).
  2. Una explicación de los pasos que realiza un usuario y lo que espera ver en la pantalla.
  3. Una explicación de la API prevista de su Hook / componente.

please

Pero mi caso es simple, ¡no quiero incluir esas cosas!

Puede que le resulte sencillo, pero no lo es en absoluto para nosotros. Si su comentario no incluye ninguno de ellos (p. Ej., Ningún enlace de CodeSandbox), ocultaremos su comentario porque, de lo contrario, es muy difícil rastrear la discusión. Gracias por respetar el tiempo de todos al incluirlos.

El objetivo final de este hilo es encontrar escenarios comunes y transformarlos en mejores documentos y advertencias. Esto solo puede suceder cuando se dispone de suficientes detalles. Los comentarios inesperados con fragmentos de código incompletos reducen significativamente la calidad de la discusión, hasta el punto de que no vale la pena.

ESLint Rules Discussion

Comentario más útil

Hicimos un pase sobre estos con @threepointone hoy. He aquí un resumen:

Corregido en la regla de pelusa

Dependencias useEffect extrañas

La regla ya no le impide agregar deps "extraños" a useEffect ya que existen escenarios legítimos.

Funciones en el mismo componente pero definidas fuera del efecto

Linter no advierte sobre los casos en los que ahora es seguro, pero en todos los demás casos le brinda mejores sugerencias (como mover la función dentro del efecto o envolverla con useCallback ).

Vale la pena arreglarlo en el código de usuario

Restableciendo el estado en el cambio de accesorios

Esto ya no produce violaciones de pelusa, pero la forma idiomática de restablecer el estado en respuesta a los accesorios es diferente . Esta solución tendrá un renderizado extra inconsistente, por lo que no estoy seguro de que sea deseable.

"Mi valor no funcional es constante"

Los ganchos te empujan hacia la corrección siempre que sea posible. Si especifica los deps (que en algunos casos se puede omitir), se recomienda encarecidamente que incluya incluso los que usted piensa que no va a cambiar. Sí, en este ejemplo de useDebounce es poco probable que cambie el retraso. Pero sigue siendo un error si lo hace, pero el Hook no puede manejarlo. Esto también se muestra en otros escenarios. (Por ejemplo, los Hooks son mucho más compatibles con la recarga en caliente porque cada valor se trata como dinámico).

Si insiste absolutamente en que un determinado valor es estático, puede aplicarlo.
La forma más segura es hacerlo explícitamente en su API:

const useFetch = createFetch({ /* config object */});
const useDebounce = createDebounce(500);
const FormInput = createInput({ rules: [emailValidator, phoneValidator] });

Entonces claramente no puede cambiar a menos que lo coloques dentro de render. (Lo cual no sería un uso idiomático de su Hook). Pero decir que <Slider min={50} /> nunca puede cambiar no es realmente válido, alguien podría cambiarlo fácilmente a <Slider min={state ? 50 : 100} /> . De hecho, alguien podría hacer esto:

let slider
if (isCelsius) {
  slider = <Slider min={0} max={100} />
} else {
  slider = <Slider min={32} max={212} />
}

Si alguien cambia isCelsius de estado, un componente que suponga que min nunca cambia no se actualizará. No es obvio en este caso que el Slider será el mismo (pero lo será porque tiene la misma posición en el árbol). Así que este es un arma importante en términos de realizar cambios en el código. Un punto importante de React es que las actualizaciones se procesan como los estados iniciales (normalmente no se puede saber cuál es cuál). Ya sea que represente el valor de prop B, o si pasa del valor de prop A a B, debería verse y comportarse de la misma manera.

Si bien esto no es aconsejable, en algunos casos, el mecanismo de aplicación podría ser un Hook que advierte cuando cambia el valor (pero proporciona el primero). Al menos entonces es más probable que se note.

function useMyHook(a) {
  const initialA = usePossiblyStaleValue(a);
  // ...
}

function usePossiblyStaleValue(value) {
  const ref = useRef(value);

  if (process.env.NODE_ENV !== 'production') {
    if (ref.current !== value) { // Or your custom comparison logic
      console.error(
        'Unlike normally in React, it is not supported ' +
        'to pass dynamic values to useMyHook(). Sorry!'
      );
    }
  }

  return ref.current;
}

También puede haber un caso legítimo en el que simplemente no pueda manejar una actualización. Por ejemplo, si la API de nivel inferior no lo admite, como un complemento jQuery o una API DOM. En ese caso, la advertencia sigue siendo apropiada para que el consumidor de su componente la comprenda. Alternativamente, puede crear un componente contenedor que restablezca el key en actualizaciones incompatibles, lo que obligará a un remontaje limpio con accesorios nuevos. Probablemente sea preferible para componentes de hoja como controles deslizantes o casillas de verificación.

"El valor de mi función es constante"

En primer lugar, si es constante y se eleva al alcance de nivel superior, el linter no se quejará. Pero eso no ayuda con las cosas que provienen de la utilería o el contexto.

Si realmente es constante, especificarlo en profundidad no está de más. Como el caso en el que una función setState dentro de un Hook personalizado se devuelve a su componente y luego la llama desde un efecto. La regla de la pelusa no es lo suficientemente inteligente como para comprender una indirecta como esta. Pero, por otro lado, cualquiera puede ajustar esa devolución de llamada más tarde antes de regresar, y posiblemente hacer referencia a otra propiedad o estado dentro de ella. ¡Entonces no será constante! Y si no logra manejar esos cambios, tendrá desagradables errores de propiedad / estado obsoletos. Por lo tanto, especificarlo es un mejor valor predeterminado.

Sin embargo, es un error pensar que los valores de las funciones son necesariamente constantes. Son más a menudo constantes en las clases debido al enlace de métodos, aunque eso crea su propia gama de errores . Pero, en general, cualquier función que se cierre sobre un valor en un componente de función no se puede considerar constante. La regla de la pelusa ahora es más inteligente para decirle qué hacer. (Como moverlo dentro del efecto, la solución más simple, o envolverlo con useCallback ).

Hay un problema en el espectro opuesto a esto, que es donde se obtienen bucles infinitos (el valor de una función siempre cambia). Lo detectamos en la regla de pelusa ahora cuando es posible (en el mismo componente) y sugerimos una solución. Pero es complicado si pasas algo varios niveles hacia abajo.

Aún puede envolverlo en useCallback para solucionar el problema. Recuerde que técnicamente es válido que una función cambie , y no puede ignorar este caso sin correr el riesgo de errores. Como onChange={shouldHandle ? handleChange : null} o renderizar foo ? <Page fetch={fetchComments /> : <Page fetch={fetchArticles /> en el mismo lugar. O incluso fetchComments que se cierra sobre el estado del componente principal. Eso puede cambiar. Con las clases, su comportamiento cambiará silenciosamente pero la referencia a la función seguirá siendo la misma. Por lo tanto, su hijo se perderá esa actualización; en realidad, no tiene otra opción que no sea pasar más datos al niño. Con los componentes de la función y useCallback , la identidad de la función en sí cambia, pero solo cuando es necesario. Así que esa es una propiedad útil y no solo un obstáculo a evitar.

Deberíamos agregar una mejor solución para detectar bucles asíncronos infinitos. Eso debería mitigar el aspecto más confuso. Podríamos agregar alguna detección para esto en el futuro. También puede escribir algo como esto usted mismo:

useWarnAboutTooFrequentChanges([deps]);

Esto no es ideal y tendremos que pensar en manejar esto con más gracia. Estoy de acuerdo en que casos como este son bastante desagradables. La solución sin romper la regla sería hacer rules estático, por ejemplo, cambiando la API a createTextInput(rules) , y envolver register y unregister en useCallback . Aún mejor, elimine register y unregister , y reemplácelos con un contexto separado donde ponga dispatch solo. Entonces puede garantizar que nunca tendrá una identidad de función diferente a la de leerlo.

Agregaría que probablemente desee poner useMemo en el valor de contexto de todos modos porque el proveedor hace muchos cálculos que sería triste repetir si no hay nuevos componentes registrados pero su propio padre actualizado. Así que este tipo de problema pone más visible el problema que de otro modo no habrías notado. Aunque estoy de acuerdo, debemos hacerlo aún más prominente cuando esto suceda.

Ignorar las dependencias de funciones conduce por completo a errores peores con los componentes de función y los Hooks porque seguirían viendo accesorios obsoletos y estados si lo haces. Así que trata de no hacerlo cuando puedas.

Reacción a los cambios de valor compuesto

Me resulta extraño por qué este ejemplo usa un efecto para algo que es esencialmente un controlador de eventos. Hacer el mismo "registro" (supongo que podría ser un envío de formulario) en un controlador de eventos parece más apropiado. Esto es especialmente cierto si pensamos en lo que sucede cuando el componente se desmonta. ¿Qué pasa si se desmonta justo después de que se programó el efecto? Cosas como el envío de formularios no deberían "no suceder" en ese caso. Entonces, parece que el efecto podría ser una elección incorrecta allí.

Dicho esto, aún puede hacer lo que intentó, haciendo que fullName sea setSubmittedData({firstName, lastName}) lugar, y luego [submittedData] es su dependencia, de la cual puede leer firstName y lastName .

Integración con código imperativo / heredado

Al integrarse con cosas imperativas como los complementos de jQuery o las API DOM sin procesar, se pueden esperar algunas cosas desagradables. Dicho esto, todavía espero que puedas consolidar los efectos un poco más en ese ejemplo.


¡Espero no haberme olvidado de nadie! Avísame si lo hice o si algo no está claro. Intentaremos convertir las lecciones de esto en algunos documentos pronto.

Todos 111 comentarios

Este ejemplo tiene una respuesta: https://github.com/facebook/react/issues/14920#issuecomment -466145690

CódigoSandbox

Este es un componente de casilla de verificación no controlado que toma un defaultIndeterminate prop para establecer el estado indeterminado en el procesamiento inicial (que solo se puede hacer en JS usando una referencia porque no hay indeterminate atributo de elemento defaultValue , donde su valor se usa solo en el renderizado inicial.

La regla se queja de que defaultIndeterminate falta en la matriz de dependencias, pero agregarla haría que el componente sobrescribiera incorrectamente el estado no controlado si se pasa un nuevo valor. La matriz de dependencia no se puede eliminar por completo porque haría que el estado indeterminado estuviera completamente controlado por la propiedad.

No veo ninguna forma de distinguir entre esto y el tipo de caso que la regla debe captar, pero sería genial si la documentación de la regla final pudiera incluir una solución alternativa sugerida. 🙂

Re: https://github.com/facebook/react/issues/14920#issuecomment -466144466

@billyjanitsch ¿ https://codesandbox.io/s/jpx1pmy7ry

Agregué useState por indeterminate que se inicializa en defaultIndeterminate . El efecto luego acepta [indeterminate] como argumento. Actualmente no lo cambia, pero si lo hiciera más tarde, supongo que también funcionaría. Por lo tanto, el código anticipa un poco mejor los posibles casos de uso futuros.

Entonces obtuve el siguiente caso (¿borde?) Donde paso un poco de html y lo uso con dangerouslySetInnerHtml para actualizar mi componente (algo de contenido editorial).
No estoy usando el prop html sino la referencia donde puedo usar ref.current.querySelectorAll para hacer algo de magia.
Ahora necesito agregar html a mis dependencias en useEffect aunque no lo esté usando explícitamente. ¿Es este un caso de uso en el que debería deshabilitar la regla?

La idea es interceptar todos los clics en enlaces del contenido editorial y realizar un seguimiento de algunos análisis o hacer otras cosas relevantes.

Este es un ejemplo diluido de un componente real:
https://codesandbox.io/s/8njp0pm8v2

Estoy usando react-redux, así que cuando paso un creador de acción en los accesorios de mapDispatchToProps , y uso ese creador de acción en un gancho, obtengo una advertencia de exhaustive-deps .

Entonces, obviamente, puedo agregar la acción redux a la matriz de dependencia, pero debido a que la acción redux es una función y nunca cambia, esto es innecesario, ¿verdad?

const onSubmit = React.useCallback(
  () => {
    props.onSubmit(emails);
  },
  [emails, props]
);

Espero que la pelusa arregle los deps en [emails, props.onSubmit] , pero ahora mismo siempre arregla los deps en [emails, props] .

  1. Un CodeSandbox que demuestra un ejemplo de código mínimo que aún expresa su intención (no "barra de foo", sino el patrón de interfaz de usuario real que está implementando).

https://codesandbox.io/s/xpr69pllmz

  1. Una explicación de los pasos que realiza un usuario y lo que espera ver en la pantalla.

Un usuario agregará un correo electrónico e invitará a esos correos electrónicos a la aplicación. Elimino intencionalmente el resto de la interfaz de usuario a solo button porque son irrelevantes para mi problema.

  1. Una explicación de la API prevista de su Hook / componente.

Mi componente tiene un solo accesorio, onSubmit: (emails: string[]) => void . Se llamará con el estado emails cuando un usuario envíe el formulario.


EDITAR: Respondido en https://github.com/facebook/react/issues/14920#issuecomment -467494468

Esto se debe a que técnicamente props.foo() pasa props como this a foo llamada. Entonces foo podría depender implícitamente de props . Sin embargo, necesitaremos un mensaje mejor para este caso. La mejor práctica es siempre la desestructuración.

CódigoSandbox

No considera que el montaje y la actualización puedan ser claramente diferentes cuando se integran con bibliotecas de terceros. El efecto de actualización no se puede incluir en el montaje (y eliminar la matriz por completo), porque la instancia no debe destruirse en cada renderizado

Hola,

No estoy seguro de qué está mal con mi código aquí:

const [client, setClient] = useState(0);

useEffect(() => {
    getClient().then(client => setClient(client));
  }, ['client']);

Tengo React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked

@joelmoss @sylvainbaronnet aprecio sus comentarios pero me escondí sus comentarios porque no incluyen la información que pedimos en la parte superior de la cuestión. Eso hace que esta discusión sea innecesariamente difícil para todos porque falta contexto. Me complacerá continuar la conversación si publica de nuevo e incluye toda la información relevante (consulte la publicación superior). Gracias por entender.

  1. CódigoSandbox
  2. El usuario puede seleccionar un nombre y obliga a cambiar el apellido. El usuario puede seleccionar un apellido y no obliga a cambiar el nombre.
  3. Hay un gancho personalizado que devuelve una parte del estado, un componente seleccionado que actualiza ese estado y el método de actualización del gancho de estado que actualiza el estado mediante programación. Como se demostró, no siempre quiero usar la función de actualización, así que lo dejé como último elemento en la matriz devuelta.

Creo que el código tal como está no debería generar errores. Lo hace ahora mismo en la línea 35, diciendo que setLastName debería incluirse en la matriz. ¿Alguna idea sobre qué hacer al respecto? ¿Estoy haciendo algo inesperado?

Lo entiendo totalmente, y normalmente haría todo eso por usted, pero en mi caso específico, ninguno de los códigos es exclusivo para mí. Es simplemente una pregunta sobre si el uso de una función que se define fuera del gancho (es decir, un creador de acciones redux) y se usa dentro de un gancho debería requerir que esa función se agregue como un depósito de gancho.

Feliz de crear un codeandbox si aún necesita más información. Gracias

@joelmoss Creo que mi repro también cubre tu caso.

Sí, un CodeSandbox aún ayudaría. Imagínese lo que es cambiar de contexto entre los fragmentos de código de las personas durante todo el día. Es un gran costo mental. Ninguno tiene el mismo aspecto. Cuando necesitas recordar cómo la gente usa los creadores de acciones en Redux o algún otro concepto externo a React, es aún más difícil. El problema puede parecerle obvio, pero para mí no es del todo obvio lo que quiso decir.

@gaearon Entiendo que tiene sentido, en realidad funcionó para mí porque la razón por la que quería lograrlo era:

Si desea ejecutar un efecto y limpiarlo solo una vez (al montar y desmontar), puede pasar una matriz vacía ([]) como segundo argumento.

Muchas gracias por la aclaración. (y perdón por el tema fuera de tema)

Aquí está la versión de código de lo que estoy implementando. No parece mucho, pero el patrón es 100% idéntico a mi código real.

https://codesandbox.io/s/2x4q9rzwmp?fontsize=14

  • Una explicación de los pasos que realiza un usuario y lo que espera ver en la pantalla.

¡Todo funciona muy bien en este ejemplo! Excepto el problema del linter.

  • Una explicación de la API prevista de su Hook / componente.

Tengo varios archivos como este, donde estoy ejecutando una función en el efecto, pero solo quiero que se ejecute cuando se cumpla alguna condición, por ejemplo, cambiando el Id. De la serie. No quiero incluir la función en la matriz.

Esto es lo que obtengo del linter cuando lo ejecuto en el código de la zona de pruebas:

25:5   warning  React Hook useEffect has a missing dependency: 'fetchPodcastsFn'. Either include it or remove the dependency array  react-hooks/exhaustive-deps

Mi pregunta es: ¿es así como debería comportarse (ya sea la regla de linter o la regla de matriz de ganchos)? ¿Existe una forma más idiomática de describir este efecto?

@svenanders , tengo curiosidad sobre la razón por la que no quieres incluir fetchPodcastsFn ? ¿Es porque encuentras que cambia en cada render? Si es así, probablemente desee memorizar esa función o hacerla estática (en caso de que no tenga ningún parámetro)

Por un lado, se trata de claridad. Cuando miro la función, quiero entender fácilmente cuándo debería dispararse. Si veo _one_ id en la matriz, es muy claro. Si veo esa identificación y un montón de funciones, se vuelve más confuso. Tengo que dedicar tiempo y esfuerzo, posiblemente incluso a depurar funciones, para entender qué está pasando.
Mis funciones no cambian en tiempo de ejecución, por lo que no sé si memorizarlas sería importante (esta en particular es una acción de envío que desencadena una epopeya, que eventualmente resulta en un cambio de estado).

https://codesandbox.io/s/4xym4yn9kx

  • Pasos

El usuario accede a una ruta en la página, pero no es un superusuario, por lo que queremos redirigirlo fuera de la página. El props.navigate se inyecta a través de una biblioteca de enrutador, por lo que en realidad no queremos usar window.location.assign para evitar la recarga de una página.

¡Todo funciona!

  • API prevista

Puse las dependencias correctamente como en la zona de pruebas del código, pero el linter me dice que la lista de dependencias debería tener props lugar de props.navigate . ¿Porqué es eso?

¡Un tweet con capturas de pantalla! https://twitter.com/ferdaber/status/1098966716187582464

EDITAR: una razón válida por la que esto podría tener errores es si navigate() es un método que se basa en un this limitado, en cuyo caso técnicamente si algo dentro de props cambia, entonces las cosas dentro this también cambiará.

CodeSandbox: https://codesandbox.io/s/711r1zmq50

API prevista:

Este gancho le permite eliminar cualquier valor que cambie rápidamente. El valor antirrebote solo reflejará el último valor cuando no se haya llamado al gancho useDebounce durante el período de tiempo especificado. Cuando se usa junto con useEffect, como lo hacemos en la receta, puede asegurarse fácilmente de que las operaciones costosas, como las llamadas a la API, no se ejecuten con demasiada frecuencia.

Pasos:

El ejemplo le permite buscar en la API de Marvel Comic y usa useDebounce para evitar que se activen llamadas a la API con cada pulsación de tecla.

En mi opinión, agregar "todo" que usamos en la matriz de dependencias no es eficiente.

Por ejemplo, considere el gancho useDebounce . En usos del mundo real (al menos en el nuestro), delay no cambiará después del primer render, pero estamos verificando si ha cambiado o no en cada re-renderizado. Entonces, en este gancho, el segundo argumento de useEffect es mejor [value] lugar de [value, delay] .

Por favor, no crea que los controles de igualdad superficiales son extremadamente baratos. Pueden ayudar a su aplicación cuando se colocan estratégicamente, pero simplemente hacer que cada componente sea puro puede hacer que su aplicación sea más lenta. Compensaciones.

Creo que agregar todo en la matriz de dependencias tiene el mismo problema de rendimiento (o incluso peor) que usar componentes puros en todas partes. Dado que, hay muchos escenarios en los que sabemos que algunos de los valores que estamos usando no cambiarán, por lo que no deberíamos agregarlos en la matriz de dependencias, porque como dijo @gaearon , las verificaciones de igualdad superficiales no son muy baratas y esto puede hacer que nuestra aplicación sea más lenta.

Tengo algunos comentarios sobre cómo habilitar esta regla para que funcione automáticamente en ganchos personalizados por convención.

Puedo ver en el código fuente que existe la intención de permitir que las personas especifiquen una expresión regular para capturar ganchos personalizados por nombre:

https://github.com/facebook/react/blob/ba708fa79b3db6481b7886f9fdb6bb776d0c2fb9/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js#L490 -L492

¿Qué pensaría el equipo de React sobre una convención de nomenclatura adicional para ganchos personalizados con matrices de dependencia? Los ganchos ya siguen la convención de tener el prefijo use para que este complemento los detecte como un gancho. La convención que propondría para detectar ganchos personalizados que dependen de ciertas dependencias sería algún tipo de sufijo, algo como WithDeps , lo que significa que un nombre de gancho personalizado completo podría ser algo así como useCustomHookWithDeps . El sufijo WithDeps le diría al complemento que el argumento final de la matriz es una de las dependencias.

El complemento aún podría admitir expresiones regulares, pero creo que vería un gran beneficio al permitir que los autores de la biblioteca simplemente exporten sus ganchos personalizados con WithDeps lugar de obligar a los consumidores de la biblioteca a configurar explícitamente el complemento para todos y cada uno personalizado ganchos de terceros o de otro tipo.

Advierte y elimina automáticamente la verificación de igualdad personalizada.

const myEqualityCheck = a => a.includes('@');

useCallback(
  () => {
    // ...
  },
  [myEqualityCheck(a)]
);

Para useEffect, no creo que deba aparecer la advertencia de "dependencia innecesaria" porque esas "dependencias" cambiarán la forma en que se activa el efecto.

Digamos que tengo dos contadores, padre e hijo:

  • Los contadores se pueden incrementar / disminuir de forma independiente.
  • Quiero restablecer el contador secundario a cero cuando cambia el contador principal.

Implementación:

  const [parent, setParent] = useState(0);
  const [child, setChild] = useState(0);

  useEffect(
    () => {
      setChild(0);
    },
    [parent] // "unnecessary dependency: 'parent'"
  );

La regla exhaustiva deps da la advertencia React Hook useEffect has an unnecessary dependency: 'parent'. Either exclude it or remove the dependency array.

screen shot 2019-02-25 at 11 06 40 am

No creo que el linter deba hacer esta sugerencia porque cambia el comportamiento de la aplicación.

  1. CodeSandbox: https://codesandbox.io/s/ol6r9plkv5
  2. Pasos: Cuando parent cambia, child restablece a cero.
  3. Intención: En lugar de marcar la dependencia como innecesaria, el linter debería reconocer que parent cambia el comportamiento de useEffect y no debería aconsejarme que lo elimine.

[EDITAR]

Como solución alternativa, puedo escribir algo como

const [parent, setParent] = useState(0)
const [child, setChild] = useState(0)
const previousParent = useRef(parent)

useEffect(() => {
  if (parent !== previousParent.current) {
    setChild(0)
  }

  previousParent.current = parent
}, [parent])

Pero hay una "poesía" en el fragmento original que me gusta.

Creo que la pregunta se reduce a si deberíamos interpretar la matriz de dependencia como solo un mecanismo para optimizar el rendimiento frente al comportamiento real de los componentes. Las preguntas frecuentes de React Hooks usan el rendimiento como un ejemplo de por qué lo usaría, pero no interpreté el ejemplo en el sentido de que solo debería usar la matriz de dependencia para mejorar el rendimiento, sino que lo vi como una forma conveniente de omitir las llamadas de efectos. en general.

En realidad, es un patrón que he usado un par de veces para invalidar un caché interno:

useEffect(() => {
  if (props.invalidateCache) {
    cache.clear()
  }
}, [props.invalidateCache])

Si no debería usarlo de esta manera, no dude en hacérmelo saber e ignorar este comentario.

@MrLeebo
Qué pasa

  const [parent, setParent] = useState(0);
  const [child, setChild] = useState(0);
  const updateChild = React.useCallback(() => setChild(0), [parent]);

  useEffect(
    () => {
      updateChild();
    },
    [updateChild] 
  );

Yo tampoco entendería ese fragmento tuyo. La dependencia solo se puede asumir en función de su código, pero podría ser un error. Si bien mi propuesta no necesariamente resuelve eso, la hace al menos más explícita.

Parece que esto se resolvió anteriormente a través de getDerivedStateFromProps ? ¿ Cómo implemento getDerivedStateFromProps? ¿ayuda?

@nghiepit Escondí tu comentario porque

@ eps1lon Tendría exactamente la misma advertencia en useCallback , y no estoy de acuerdo con que ese patrón en general sea una mejora con respecto al original, pero no quiero descarrilar el tema para hablar de eso. Para aclarar, creo que la regla de dependencia innecesaria debe relajarse para useEffect o useLayoutEffect específicamente, porque pueden contener una lógica eficaz, pero la regla debe permanecer en su lugar para useCallback , useMemo , etc.

Me encuentro con algunos de los mismos problemas / preguntas de modelo mental que @MrLeebo ha descrito aquí. Mi intuición es que la regla de dependencia useEffect no puede ser tan estricta. Tengo un ejemplo increíblemente elaborado en el que estaba trabajando para una idea básica de prueba de concepto. Sé que este código apesta y la idea no es particularmente útil, pero creo que es una buena ilustración del problema en cuestión. Creo que @MrLeebo expresó la pregunta que tengo bastante bien:

Creo que la pregunta se reduce a si deberíamos interpretar la matriz de dependencia como solo un mecanismo para optimizar el rendimiento frente al comportamiento real de los componentes. Las preguntas frecuentes de React Hooks usan el rendimiento como un ejemplo de por qué lo usaría, pero no interpreté el ejemplo en el sentido de que solo debería usar la matriz de dependencia para mejorar el rendimiento, sino que lo vi como una forma conveniente de omitir las llamadas de efectos. en general.

https://codesandbox.io/s/5v9w81j244

Específicamente miraría el gancho useEffect en useDelayedItems . En este momento, incluir la propiedad items en la matriz de dependencia provocará un error de formación de pelusa, pero eliminar esa propiedad o la matriz por completo le da un comportamiento que no desea.

La idea básica del gancho useDelayedItems se le da una matriz de elementos y un objeto de configuración (retraso en ms, tamaño de página inicial), inicialmente se me entregará un subconjunto de elementos dependiendo de mi tamaño de página configurado. Después de que haya pasado config.delay , los elementos ahora serán el conjunto completo de elementos. Cuando se le entrega un nuevo conjunto de elementos, el gancho debe volver a ejecutar esta lógica de "demora". La idea es una versión muy burda y tonta de la presentación retrasada de listas grandes.

Gracias por todo el trabajo en estas reglas, ha sido muy útil durante el desarrollo. Incluso si mi ejemplo es de calidad cuestionable, espero que brinde una idea de cómo se pueden usar estos operadores.

EDITAR : He agregado algunos comentarios aclaratorios dentro de los códigos y el cuadro sobre la intención de comportamiento. Espero que la información sea suficiente, pero avíseme si algo sigue siendo confuso.

¿Qué pasa con un valor único que combina otros valores?

Ejemplo: fullName se deriva de firstName y lastName . Queremos activar el efecto solo cuando fullName cambia (como cuando el usuario presiona "Guardar") pero también queremos acceder a los valores que compone en el efecto

Manifestación

Agregar firstName o lastName a las dependencias rompería las cosas ya que solo queremos ejecutar el efecto después de que fullName haya cambiado.

@aweary No estoy seguro de qué valor está obteniendo de la indirección de cambio useEffect prop. Parece que su onClick debería manejar ese "efecto".

https://codesandbox.io/s/0m4p3klpyw

En cuanto a los valores individuales que combinan otros valores, useMemo probablemente sea lo que desee. La naturaleza retrasada del cálculo en su ejemplo significa que no se asigna exactamente 1: 1 con su comportamiento vinculado.

Crearé enlaces de codesandbox para estos ejemplos y editaré esta publicación.

Tengo una regla extremadamente simple: si la pestaña actual cambió, desplácese hasta la parte superior:
https://codesandbox.io/s/m4nzvryrxj

useEffect(() => {
    window.scrollTo(0, 0);
  }, [activeTab]);

Y tengo React Hook useEffect has an unnecessary dependency: 'activeTab'. Either exclude it or remove the dependency array

Además, cuando estaba migrando algunos componentes, quiero replicar el comportamiento de componentDidMount:

useEffect(() => {
    ...
  }, []);

esta regla se queja mucho de esto. Dudo en agregar todas las dependencias a la matriz useEffect.

Finalmente, si declara una nueva función en línea antes de useEffect, así:
https://codesandbox.io/s/nr7wz8qp7l

const foo = () => {...};
useEffect(() => {
    foo();
  }, []);

Obtienes: React Hook useEffect has a missing dependency: 'foo'. Either include it or remove the dependency array
No estoy seguro de cómo me siento al agregar foo a la lista de dependencias. En cada render foo hay una nueva función y useEffect siempre se ejecuta?

@ ksaldana1 poner el efecto secundario dentro del controlador de eventos no es suficiente. Eso provocaría que el efecto secundario se produzca antes de que se confirme la actualización real, lo que podría provocar que el efecto secundario se active con más frecuencia de la deseada.

Tampoco funcionará al usar useReducer porque el estado actualizado no estará disponible dentro del controlador de eventos.

En cuanto a los valores individuales que combinan otros valores, useMemo probablemente sea lo que desee.

Si este ejemplo usara useMemo , se rompería porque useMemo derivaría un nuevo fullName cada vez que firstName o lastName cambiaran. El comportamiento aquí es que fullName no se actualiza hasta que se hace clic en el botón Guardar.

@aweary ¿Algo como esto funcionaría? https://codesandbox.io/s/lxjm02j50m
Tengo mucha curiosidad por ver cuál es la implementación recomendada.

También tengo curiosidad por saber más sobre un par de cosas que dijiste. ¿Puede indicarme más información sobre estos?

Disparo y efecto en el controlador:

eso provocaría que el efecto secundario se produzca antes de que se confirme la actualización real, lo que podría provocar que el efecto secundario se active con más frecuencia de la deseada.

Usando useReducer state en el controlador de eventos:

el estado actualizado no estará disponible.

¡Gracias!

@bugzpodder no está relacionado, pero la llamada de desplazamiento debe estar dentro de useLayoutEffect lugar de useEffect . Actualmente hay un parpadeo notable al navegar hacia la nueva ruta

No estoy seguro de si esto es intencional o no:

Cuando llamas a una función desde props, el linter sugiere agregar todo el objeto props como una dependencia.

Version corta:

useEffect(function() {
  props.setLoading(true)
}, [props.setLoading])

// ⚠️ React Hook useEffect has a missing dependency: 'props'.

Versión completa: Codesandbox

Intentando de nuevo ... pero más simple esta vez;)

Al pasar una función en los accesorios, o de hecho desde cualquier lugar, y usar esa función dentro de un gancho, obtengo una advertencia exhaustive-deps .

Entonces, obviamente, puedo agregar la función a la matriz de dependencia, pero debido a que es una función y nunca cambia, esto es innecesario, ¿verdad?

-> Caja de arena

Espero que sea todo lo que necesitas, pero simplemente bifurqué la caja de arena de

Gracias.

Entonces, obviamente, puedo agregar la función a la matriz de dependencia, pero debido a que es una función y nunca cambia, esto es innecesario, ¿verdad?

Esto no es correcto; las funciones pueden cambiar todo el tiempo cuando el padre vuelve a renderizar.

@siddharthkp

Cuando llamas a una función desde props, el linter sugiere agregar todo el objeto props como una dependencia.

Esto se debe a que técnicamente props.foo() pasa props como this a foo llamada. Entonces foo podría depender implícitamente de props . Sin embargo, necesitaremos un mensaje mejor para este caso. La mejor práctica es siempre la desestructuración.

las funciones pueden cambiar todo el tiempo cuando el padre vuelve a renderizar.

Claro, pero si la función se definiera en otro lugar y no desde un componente principal, nunca cambiaría.

Claro, pero si la función se definiera en otro lugar y no desde un componente principal, nunca cambiaría.

Si lo importa directamente, la regla de pelusa no le pedirá que lo agregue a los departamentos de efectos. Solo si está en el ámbito de renderizado. Si está en el ámbito de procesamiento, la regla de pelusa no sabe de dónde viene. Incluso si no es dinámico hoy, podría ser mañana cuando alguien cambie el componente principal. Por lo tanto, especificarlo es el valor predeterminado correcto. No está de más especificarlo si es estático de todos modos.

gracias @gaearon

Esto se debe a que técnicamente props.foo() pasa props como this a foo llamada. Entonces foo podría depender implícitamente de props . Sin embargo, necesitaremos un mensaje mejor para este caso. La mejor práctica es siempre la desestructuración.

Eso también responde a mi pregunta. ¡Gracias! 😄

https://codesandbox.io/s/98z62jkyro

Así que estoy creando una biblioteca para manejar la validación de entrada registrando todas las entradas usando una api expuesta en un contexto para que cada entrada se registre. Creé un gancho personalizado llamado useRegister.

Ex:

 useEffect(
    () => {
      register(props.name, props.rules || []);
      console.log("register");
      return function cleanup() {
        console.log("cleanup");
        unregister(props.name);
      };
    },
    [props.name, props.rules, register, unregister]
  );

Al forzar a props.rules a ser parte de las dependencias, parece terminar en un bucle de renderizado infinito. Props.rules es un conjunto de funciones que se registrarán como validadores. Solo he detectado este problema al proporcionar matrices como dependencias. En mi codeandbox, puedes ver que se repite abriendo la consola.

El solo hecho de tener props.name como dependencia hace que funcione según lo previsto. Hacer cumplir las dependencias cambiará el comportamiento de la aplicación, como se ha señalado, y en esta ocasión los efectos secundarios son graves.

@bugzpodder

Re: https://github.com/facebook/react/issues/14920#issuecomment -467212561

useEffect(() => {
    window.scrollTo(0, 0);
  }, [activeTab]);

Este parece un caso legítimo. Voy a relajar la advertencia para permitir deps extraños solo para efectos. (Pero no por useMemo o useCallback .)

Además, cuando estaba migrando algunos componentes, quiero replicar el comportamiento de componentDidMount:
esta regla se queja mucho de esto. Dudo en agregar todas las dependencias a la matriz useEffect.

Lo sentimos, no agregaste ningún ejemplo para eso, así que perdimos la oportunidad de discutirlo. Esa es exactamente la razón por la que la publicación OP solicita especificar un ejemplo de interfaz de usuario concreto . Lo hizo en el primer punto, pero no en este. Me complace discutirlo cuando agregue un ejemplo concreto. Los detalles realmente dependen de ello.

Finalmente, si declara una nueva función en línea antes de useEffect, así: https://codesandbox.io/s/nr7wz8qp7l

En este caso, la solución fácil es mover doSomething al efecto. Entonces no es necesario declararlo. Alternativamente, puede useCallback alrededor de doSomething . Estoy abierto a relajar la regla para permitir omitir funciones que solo usan deps declarados. Pero esto puede resultar confuso si tiene una función que llama a otra función y agrega una propiedad o estado en una de esas funciones. De repente, todos los departamentos de efectos que lo usan de forma transitiva deben actualizarse. Puede resultar confuso.

No sé si se trata de una solicitud de función para una nueva regla o algo que podría mejorarse sobre exhaustive-deps

import React from 'react'
import emitter from './thing'

export const Foo = () => {
  const onChange = () => {
    console.log('Thing changed')
  }

  React.useEffect(() => {
    emitter.on('change', onChange)
    return () => emitter.off('change', onChange)
  }, [onChange])

  return <div>Whatever</div>
}

Debido a que la función onChange se crea en cada renderizado, el argumento useEffect hook [onChange] es redundante y bien podría eliminarse:

   React.useEffect(() => {
     emitter.on('change', onChange)
     return () => emitter.off('change', onChange)
-  }, [onChange])
+  })

El linter podría detectar eso y aconsejarle que elimine el argumento de la matriz.

Ha habido situaciones en las que he estado manteniendo una lista de elementos de matriz, solo para darme cuenta de que uno o más de ellos se estaban creando e invalidando el gancho en cada renderizado de todos modos.

Acaba de publicar [email protected] con algunas correcciones y mejores mensajes para esta regla. Nada innovador, pero algunos casos deben resolverse. Veré el resto la semana que viene.

También publiqué el primer paso posible para omitir los departamentos de funciones "seguras" aquí: https://github.com/facebook/react/pull/14996. (Consulte las pruebas). Si tiene ideas sobre heurísticas útiles y cómo guiar a las personas hacia las correcciones correctas, comente las relaciones públicas.

@gaearon Excelente idea. Esto definitivamente será útil para tener un mejor estilo al usar ganchos 🙏

@gaearon Todavía no funciona en caso de que la acción provenga de prop. Considere este ejemplo:

image

En el que setScrollTop es una acción de reducción.

En este ejemplo en el componente Slider , estoy usando useEffect para esperar hasta que el DOM esté disponible para poder montar el componente noUiSlider. Por lo tanto, paso [sliderElement] para asegurarme de que la referencia esté disponible en el DOM cuando se ejecute el efecto. Nosotros también renderizamos nuestros componentes en el servidor, por lo que esto también asegura que el DOM esté disponible antes de renderizar. Los otros accesorios que uso en useEffect (es decir, min, max, onUpdate, etc.) son constantes y, por lo tanto, no veo la necesidad de que se pasen al efecto.

screen shot 2019-03-02 at 5 17 09 pm


Aquí está el efecto como se ve en codesandbox:

const { min, max, step } = props;
const sliderElement = useRef();

useEffect(() => {
  if (!sliderElement.current) {
    return;
  }

  const slider = sliderElement.current;
  noUiSlider.create(slider, {
    connect: true,
    start: [min, max],
    step,
    range: {
      min: [min],
      max: [max],
    },
  });

  if (props.onUpdate) {
    slider.noUiSlider.on('update', props.onUpdate);
  }

  if (props.onChange) {
    slider.noUiSlider.on('change', props.onChange);
  }
}, [sliderElement]);

@ WebDeg-Brian No puedo ayudarlo sin una demostración completa de CodeSandbox. Perdón. Ver la publicación superior.

Publiqué un poco sobre el error común de "las funciones nunca cambian":

https://overreacted.io/how-are-function-components-different-from-classes/

No es el mismo tema, pero es relevante para esta regla.

Hola @gaearon , Este es el ejemplo que me pediste que publicara aquí (de tweeter) :)

Básicamente, estoy tratando de convertir mi biblioteca react-trap en ganchos.
Esto es solo una trampa para eventos, fuera / dentro de un elemento.

Mi problema es que si useEffect no depende del valor del estado ( trapped ), a veces queda obsoleto.
Escribí algunos comentarios y registros para demostrarlo. Mire el archivo useTrap.js , los comentarios y registros están en las funciones useEffect y preventDefaultHelper .

Que yo sepa, si un valor no está dentro de useEffect entonces no debería ser parte de sus dependencias (corríjame si me equivoco).

  1. Un código caja de arena
  2. Pasos:
    Un usuario hace clic dentro del cuadro para activarlo, y afuera para que no esté activo, el usuario también puede hacer clic con el botón derecho del mouse, aunque para el primer clic no debería activar el menú contextual ( e.preventDefault ).
    Cuando digo "primer clic" me refiero al primer clic que cambia el estado.
    Dado un cuadro activo, un clic derecho fuera de él cambiará el estado a "no activo" y evitará el menú contextual. otro clic fuera no afectará el estado, por lo tanto, debería aparecer el menú contextual.

Espero haber sido claro aquí y que el caso de uso sea comprensible, hágamelo saber si necesito proporcionar más información. ¡Gracias!

Hola @gaearon , ¡estoy agregando aquí mi gancho personalizado como me

Está elaborado como ejemplo, espero poder explicarlo de una manera clara y comprensible.

Este es su estado actual: react-async-utils / src / hooks / useAsyncData.ts

Resumen de la funcionalidad
Ayudar al usuario a lidiar con las llamadas asíncronas, los datos resultantes y su estado a lo largo del proceso.

triggerAsyncData actualiza asyncData estado asincrónicamente de acuerdo con una función getData que devuelve Promise . triggerAsyncData puede ser invocado tanto como un efecto como "manualmente" por el usuario del gancho.

Desafíos

  1. Las dependencias del efecto que invoca triggerAsyncData son las intrincadas. triggerAsyncData es una dependencia del efecto, pero se crea en cada render. Línea de pensamientos hasta ahora:

    1. Solo agréguelo como una dependencia => Pero luego el efecto se ejecuta en cada renderizado.

    2. Agréguelo como una dependencia, pero use useMemo / useCallback con triggerAsyncData => useMemo / useCallback solo debe usarse para optimizaciones de rendimiento HASTA DONDE SE.

    3. Ámbito dentro del efecto => Entonces no puedo devolvérselo al usuario.

    4. En lugar de usar triggerAsyncData como dependencia, use las dependencias de triggerAsyncData como dependencias => La mejor opción que encontré hasta ahora. Pero rompe la regla de "profundidad exhaustiva".

  2. Cada parámetro de entrada del gancho personalizado es / se convierte en una dependencia de nuestro efecto interno. Entonces, las funciones en línea y los objetos literales como parámetros hacen que el efecto se ejecute con demasiada frecuencia.

    1. Deje la responsabilidad al usuario. Proporcionarán valores apropiados, usando useMemo / useCallback si es necesario => Me temo que a menudo no lo harán. Y si lo hacen, es bastante detallado.

    2. Permita un argumento adicional para que el gancho personalizado proporcione la profundidad de las entradas y utilícelo en lugar de las entradas en sí mismas => Genial, menos detallado, más control para el usuario. Estoy usando esto. Pero rompe la regla de "profundidad exhaustiva". (En realidad, estoy usando I am this y recurriendo a los deps regulares si no se proporciona un argumento adicional. Me parece un patrón poderoso).

  3. Las dependencias mal administradas para el gancho personalizado generan un bucle asíncrono infinito debido al efecto interno. He usado programación defensiva para evitar esto, pero ¿agrega una "falsificación"? dependencia ( asyncData ). Esto vuelve a romper la regla de los "fondos exhaustivos".

Explicación más larga de la que deseaba ... pero creo que refleja mis luchas por usar los ganchos de manera adecuada. Por favor, avíseme si hay algo más que pueda hacer para aclarar estas dificultades.

¡Gracias por todo el trabajo duro aquí!

Hola @gaearon, gracias por todo tu arduo trabajo.

  1. Un ejemplo mínimo de obtención de datos asíncronos

  2. Se espera que el usuario vea 5 cadenas de título de lorem ipsum obtenidas de json api .

  3. Creé un gancho personalizado para la obtención de datos con la API prevista:

const { data, isLoading, isError } = useDataApi('https://jsonplaceholder.typicode.com/posts')

Elementos internos del gancho useDataApi :

...
  const fetchData = async () => {
    let response;
    setIsError(false);
    setIsLoading(true);

    try {
      response = await fetch(url).then(response => response.json());

      setData(response);
    } catch (error) {
      setIsError(true);
    }

    setIsLoading(false);
  };

  useEffect(
    () => {
      fetchData();
    },
    [url]
  );
...

El problema es este código

  useEffect(
    () => {
      fetchData();
    },
    [url]
  );

donde react-hooks/exhaustive-deps dispara una advertencia de que debo agregar fetchData a mi matriz de dependencia y url debe eliminarse.

Si cambio este gancho en

  useEffect(
    () => {
      fetchData();
    },
    [fetchData]
  );

luego, está enviando solicitudes continuamente y nunca se detiene, lo que, por supuesto, es un gran problema. No estoy seguro de si mi código tiene errores o si react-hooks/exhaustive-deps está emitiendo un falso positivo.

Cualquier ayuda apreciada. Muchas gracias.

PD Leí su comentario sobre useEffect no apto para la búsqueda de datos , sin embargo, los documentos de reacción afirman que Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects que me dio la confianza de que los datos useEffect son excelentes para la búsqueda de datos. Así que ahora estoy un poco confundido 😕

@ jan-stehlik debe ajustar fetchData con useCallback. bifurqué su codesandbox con el cambio necesario aquí https://codesandbox.io/s/pjmjxprp0m

Muy útil, muchas gracias @viankakrisna !

Que yo sepa, si un valor no está dentro de useEffect, entonces no debería ser parte de sus dependencias (corríjame si me equivoco).

Creo que te equivocas aquí. O mejor dicho, un poco confundido. Está utilizando handleEvent dentro de su efecto. Pero no lo declaras. Es por eso que los valores que lee son obsoletos.

Interesante.

Estás usando handleEvent dentro de tu efecto. Pero no lo declaras. Es por eso que los valores que lee son obsoletos.

¿Qué quieres decir con: _ "Pero no lo declaras" _?
Se declara debajo del efecto (igual que todos los demás controladores).

¿O quiere decir que porque el controlador está usando el valor de estado y el efecto es adjuntar el controlador, entonces significa que el efecto depende de ese valor de estado?

¿O quiere decir que porque el controlador está usando el valor de estado y el efecto es adjuntar el controlador, entonces significa que el efecto depende de ese valor de estado?

Sí, si se utiliza una función debe declarar ya sea en deps (y en ese caso se envuelve con useCallback para evitar volver a crearlo), o todo lo que el uso de funciones.

¡Ok, esto es una novedad para mí! Gracias por esta entrada @gaearon :)
Solo quiero que sea muy claro para mí (¿y para los demás?) ...

Si un efecto está invocando, pasando o haciendo algo con una función, necesitamos pasar a su matriz deps:
La función en sí O Las variables que utiliza esta función.
Si la función se declara dentro del componente de función / gancho personalizado, se recomienda envolverla con useCallback para que no se vuelva a crear cada vez que se ejecute nuestro componente o gancho personalizado.

Debo decir que no lo vi en los documentos .
¿Crees que está bien agregarlo a la sección _Note_?

Nota
La matriz de entradas no se pasa como argumentos a la función de efecto. Sin embargo, conceptualmente eso es lo que representan: cada valor al que se hace referencia dentro de la función de efecto también debe aparecer en la matriz de entradas. En el futuro, un compilador suficientemente avanzado podría crear esta matriz automáticamente.

Editar
Una cosa más, en mi ejemplo, ¿cuáles son los deps para useCallback cuando envuelve handleEvent (o cualquier otro controlador por esa razón). ¿Es el event sí mismo?

Debo decir que no lo vi en los documentos.

Los documentos dicen que "todos los valores referenciados dentro de la función de efecto también deberían aparecer en la matriz de entradas". Las funciones también son valores. Estoy de acuerdo en que necesitamos documentar esto mejor, que es de lo que trata este hilo :-) Estoy recopilando casos de uso para una nueva página de documentación dedicada a esto.

Una cosa más, en mi ejemplo, cuáles son los deps para useCallback cuando envuelve handleEvent (o cualquier otro controlador por esa razón). ¿Es el evento en sí mismo?

No estoy seguro de lo que quieres decir. Son los valores a los que hace referencia la función fuera de ella. Como en useEffect .

Supongo que no pensé en esto con funciones como dependencias. mi modelo mental estaba equivocado, estaba pensando en pasar una función como una dependencia solo si se ingresó como apoyo o argumento a mi componente / gancho. Gracias por aclararme esto.

En cuanto al useCallback , lo usé así:

const memoHandleEvent = useCallback(
    handleEvent
);

y, por supuesto, pasó memoHandleEvent como una dependencia para useEffect y también para addEventListener insead de la función handleEvent real. parece funcionar, espero que esta sea la forma correcta e idiomática de hacerlo.

Tenga en cuenta que useCallback sin un segundo argumento no hace nada.

Una vez más, sería necesaria una caja de arena completa. No puedo decir si su solución es correcta por una descripción incompleta como esta.

Tenga en cuenta que useCallback sin un segundo argumento no hace nada.

¡Argg! : muecas: lol

Una vez más, sería necesaria una caja de arena completa. No puedo decir si su solución es correcta por una descripción incompleta como esta.

Aw, este es el mismo enlace de arriba. acabo de actualizarlo :)

No actualice los enlaces de CodeSandbox: PI los necesita como estaban originalmente; de ​​lo contrario, no puedo probar la regla de pelusa en ellos. ¿Podría crear dos entornos sandbox separados si tiene dos soluciones diferentes? Entonces puedo revisar cada uno.

¡Ups, lo siento! : PI superó el número de entornos sandbox en mi cuenta. déjame eliminar algunos y crearé otro (y revertiré los cambios en el original).

@gaearon este es el segundo enlace a la solución con useCallback

Tengo un escenario que creo que está bien, pero el linter se queja. Mi muestra:

CódigoSandbox

Lo que la muestra está tratando de representar es cuando un usuario hace clic en un botón, luego se realiza una solicitud para solicitar nuevos datos en función de una identificación y una función proporcionadas anteriormente. Si el Id cambia, entonces no debería solicitar nuevos datos; solo una nueva solicitud de recarga debería desencadenar una nueva solicitud de datos.

El ejemplo aquí es un poco artificial. En mi aplicación real, React vive en un DIV que es una pequeña parte de una aplicación web mucho más grande. La función que se pasa es a través de Redux y mapDispatchToProps, donde la creación de una acción toma el Id y realiza una solicitud ajax para obtener datos y actualizar la tienda. La propiedad refreshRequest se pasa a través de React.createElement. En mi implementación original, tenía un código que se veía así en el componente de clase:

componentDidUpdate (prevProps) { const { getData, someId, refreshRequest} = this.props; if (prevProps.refreshRequest!== this.props.refreshRequest) { getData(someId); } }

Estoy tratando de implementar el mismo comportamiento con un efecto de gancho. Pero como está escrito en la muestra, el linter se queja:

advertencia React Hook useEffect tiene dependencias faltantes: 'getData' y 'someId'. Inclúyalos o elimine la matriz de dependencia

Si agrego todo lo que quiere el linter, entonces si el usuario hace clic en cualquiera de los botones de la muestra, se activa useEffect. Pero solo quiero que se active cuando se presione el botón Solicitar nuevos datos.

Ojalá tenga sentido. Estaría feliz de aclarar más, si hay algo que no está claro. ¡Gracias!

Acabo de publicar [email protected] que tiene un soporte experimental para detectar dependencias de funciones desnudas (que tienden a no ser muy útiles sin useCallback ). Aquí hay un gif:

demo

¡Me encantaría que todos pudieran probarlo en sus proyectos y ver cómo se siente! (Comente sobre ese flujo específico en https://github.com/facebook/react/pull/15026).

Lo intentaré con ejemplos de este hilo mañana.

No lo he probado todavía, pero me pregunto si se trata de izar. Este es solo un "ejemplo mínimo" que se me ocurrió en el lugar, por lo que no es útil para nada, pero utilizo muchas declaraciones elevadas para que sea más fácil ver la declaración return .

function Component() {
  useEffect(() => {
    handleChange
  }, [handleChange])

  return null

  function handleChange() {}
}

Sería bueno si la regla tuviera una opción para configurar otra función para que se comporte como useEffect en lo que respecta al linter. Por ejemplo, agregué esto para poder usar fácilmente funciones asíncronas para efectos que hacen una llamada AJAX; sin embargo, así pierdo todos los beneficios de exhaustive-deps linting:

const useAsyncEffect = (fn, ...args) => {
    /* eslint-disable react-hooks/exhaustive-deps */
    useEffect(() => {
        fn();
    }, ...args);
};

Editar: No importa, acabo de notar que esto ya es posible usando la opción additionalHooks de la regla.

Hola a todos,
Tengo un ejemplo https://codesandbox.io/s/znnmwxol7l

Lo siguiente es lo que quiero:

function App() {
  const [currentTime, setCurrentTime] = React.useState(moment());

  const currentMonth = React.useMemo(
    () => {
      console.log("RUN");
      return currentTime.format("MMMM");
    },
    [currentTime.format("MMMM")] // <= this proplem [currentTime]
  );

  return (
    <div className="App">
      <h1>Current month: {currentMonth}</h1>
      <div>
        <button
          onClick={() => setCurrentTime(currentTime.clone().add(1, "days"))}
        >
          + 1 day
        </button>
      </div>
      <div>{currentTime.toString()}</div>
    </div>
  );
}

Pero esto es lo que obtengo:

  const currentMonth = React.useMemo(
    () => {
      console.log("RUN");
      return currentTime.format("MMMM");
    },
    [currentTime] // <= this proplem
  );

En cualquier momento el cambio currentTime entonces currentMonth recalcular innecesario

O de alguna manera puedo hacerlo a continuación:

  const currentMonth = React.useMemo(
    () => {
      return currentTime.format("MMMM");
    },
    [myEqualityCheck(currentTime)]
  );

Lo siento si esto ya ha sido respondido, no pude verlo en el hilo de arriba:
La forma de ejecutar el gancho useEffect solo en mount es definir una matriz de entradas vacía como segundo parámetro. Sin embargo, al hacer eso, las quejas exhaustivas sobre la inclusión de esos argumentos de entrada, lo que cambiaría el efecto para que también se ejecute en la actualización.
¿Cuál es el enfoque para ejecutar useEffect solo en el montaje con exhaustive-deps habilitado?

@einarq Supongo que debe asegurarse de que todos los valores referidos en su useEffect en el montaje nunca cambien. Eso podría lograrse utilizando otros ganchos como useMemo . Después de eso, independientemente de si esta regla de ESlint agrega todas las referencias a la matriz (con autofix) o no, el código se ejecutará solo una vez.

@einarq Como se indica en otra parte del hilo, no podemos ayudarlo sin CodeSandbox. Porque la respuesta realmente depende de tu código.

@nghiepit Tu ejemplo realmente no tiene sentido para mí. Si su entrada es currentTime.format("MMMM") entonces useMemo no optimiza nada porque ya lo ha calculado . Así que lo estás calculando dos veces innecesariamente.

¿Es posible especificar qué índice de argumento es la devolución de llamada en la opción additionalHooks ? Veo que estamos asumiendo en este momento en el código que sería el primer https://github.com/facebook/react/blob/9b7e1d1389e080d19e71680bbbe979ec58fa7389/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js#L1051 - L1081

Actualmente no es posible. Nuestra guía general es que los Hooks personalizados deberían preferir no tener un argumento deps porque se vuelve muy difícil pensar en cómo se componen los deps. Si toma una función, es posible que desee pedir a los usuarios que useCallback lugar.

@CarlosGines ¿Puede hacer un CodeSandbox, como se solicita en la publicación OP. Lo pregunto por una razón; de lo contrario, me resultará difícil verificar los cambios. Especialmente cuando está escrito en TypeScript.

Acabo de publicar [email protected] con mensajes más útiles y heurísticas más inteligentes. Pruébelo en sus proyectos antes de que se estabilice.

Espero que, con la mayoría de los ejemplos de este hilo, le dé un consejo razonable que le ayude a solucionar los problemas.

¿Debería ser [email protected] ?

Si.

En realidad, acaba de publicar [email protected] con un par de pequeños cambios.

@gaearon ¿Conseguiste que eslint funcionara dentro del codesandbox por casualidad?

@einarq tal vez algo como esto, ¿funcionaría?

const useDidMount = fn => {
  const callbackFn = useCallback(fn)
  useEffect(() => {
    callbackFn()
  }, [callbackFn])
}

De hecho, me avergüenza decir que no puedo hacer que el complemento funcione en una aplicación CRA simple (según mi fragmento ).
aquí está el repositorio bifurcado de codesandbox.

He visto esta nota en los documentos :

Nota: Si está utilizando la aplicación Create React, espere la versión correspondiente de los scripts de reacción que incluya esta regla en lugar de agregarla directamente.

Pero, ¿no hay forma de probarlo con CRA en este momento? 👂

Sí, tendrías que expulsarlo y agregarlo a la configuración de ESLint hasta que lo agreguemos allí. Puede expulsar una rama y no fusionarla. :-)

Hola,

Primero que nada: ¡Muchas gracias por trabajar tan de cerca con la comunidad y el increíble trabajo que están haciendo en general!

Tenemos un problema con un gancho personalizado que creamos alrededor de la API Fetch . Creé un Codesandbox para demostrar el problema.

Ejemplo en Codesandbox

https://codesandbox.io/s/kn0km7mzv

Nota: El problema (ver más abajo) da como resultado un bucle infinito y no quiero DDoS jsonplaceholder.typicode.com que estoy usando para demostrar el problema. Por lo tanto, he incluido un limitador de solicitudes simple usando un contador. Esto no es necesario para demostrar el problema, pero se sintió mal enviar una cantidad ilimitada de solicitudes a este gran proyecto.

Explicación

La idea es facilitar el manejo de los 3 estados posibles de una solicitud de API: carga, éxito y error. Por lo tanto, hemos creado un gancho personalizado useFetch() que devuelve 3 propiedades isLoading , response y error. It makes sure that either respuesta or error is set and updates isLoading . As the name implies, it uses the API de recuperación.

Para hacer eso, usa 3 useState ganchos:

  const [isLoading, setIsLoading] = useState(false);
  const [response, setResponse] = useState(null);
  const [error, setError] = useState(null);

y un gancho useEffect :

useEffect(
    () => {
      fetch(url, fetchConfig)
        .then(/* Handle fetch response... See Codesandbox for the actual implementation */)
    },
    [url]
  );

Funciona bien siempre que la matriz de dependencias de useEffect solo contenga [url] . Si agregamos fetchConfig (= [url, fetchConfig] ), el resultado es un bucle infinito. Para nuestro caso de uso particular, sería suficiente volver a ejecutar el efecto solo cuando el url cambia pero el linter no permite usar solo [url] (probado con v1.4.0 y v1.5.0-beta.1 ).

Al final del enlace personalizado, las 3 variables de estado se devuelven como un objeto:

  return {
    error,
    isLoading,
    response
  };

No estoy seguro de si este es el lugar adecuado para pedir orientación sobre este tema, ya que supongo que la sugerencia tiene sentido. Lo siento si no lo es.

¿Qué pasa si fetchConfig cambia? ¿Por qué espera que sea estático?

Está bien. Lancé [email protected] con las correcciones y mejoras de la semana pasada.
Este hilo ha sido inmensamente útil para encontrar diferentes patrones que causan problemas.

Muchas gracias a todos ustedes. (Especialmente aquellos que suministraron cajas de arena. :-)


No responderé a todos personalmente en detalle porque son muchos casos.

En cambio, escribiremos recetas y trucos comunes en los próximos días, y los enlazaremos desde los documentos. Me aseguraré de que se cubran todos los patrones comunes en este hilo (o pediré un seguimiento en un número separado para patrones raros).

Una vez que se publiquen los ejemplos, comentaré aquí y editaré cada comentario que contenga un CodeSandbox agregando un enlace a la respuesta / ejemplo relevante que demuestre la solución correcta. Espero que esto les ayude a usted y a los futuros lectores.

¡Salud!

❤️

@timkraut , debería poder agregar fetchConfig en los departamentos, y el componente debe envolverlo con una nota para que la referencia permanezca.
Un ejemplo: https://codesandbox.io/s/9l015v2x4w


Mi problema con esto es que ahora el componente debe conocer los detalles de implementación del gancho ...

No estoy seguro de si este hilo todavía está abierto a una discusión de ejemplo, pero todavía estoy pensando en las mejores prácticas. Mi pregunta es sobre controlar cuándo se activa el gancho useEffect con la matriz de dependencia y usar los valores en ese momento en lugar de requerir declarar una referencia separada para sortear la regla de pelusa.

Ejemplo

https://codesandbox.io/s/40v54jnkyw

En este ejemplo, intento guardar automáticamente los valores de entrada periódicamente.

Explicación

Cada 5 segundos, el código intenta guardar automáticamente los valores actuales (solo imprímalos en la pantalla por ahora). El linter requeriría que todos los valores de entrada se incluyan en la matriz de dependencia, lo que cambiaría la frecuencia con la que se activará el efecto.

  useEffect(
    () => {
      if (autoSaveTick % 5 === 0) {
        setAutosaveValue(
          `${input1Value.value}, ${input2Value.value}, ${input3Value.value}`
        );
      }
    },
    [autoSaveTick]
  );

La alternativa que veo es tener una implementación similar a los ganchos useTick y useInterval definidos en el ejemplo, donde hay una devolución de llamada asignada a una referencia. Parece que eso causaría redefiniciones de funciones innecesarias solo por alinearse con la regla de pelusa.

Estoy buscando las mejores prácticas para escribir un efecto que se ejecuta cuando una variable cambia (Variable A) que usa otros valores de variable (Variable B y Variable C) en el momento del cambio de variable anterior (Variable A).

¿Qué pasa si fetchConfig cambia? ¿Por qué espera que sea estático?

Estaría bien para agregarlo a la matriz de dependencias. En nuestro caso comercial particular, esto nunca puede suceder, pero supongo que es una buena idea agregarlo de todos modos.

Mi problema con esto es que ahora el componente debe conocer los detalles de implementación del gancho ...

Ese es exactamente el problema que tenemos también: / En nuestra implementación, incluso hemos usado una propiedad adicional para facilitar la configuración del cuerpo, por lo que tendríamos que usar 2 llamadas useMemo() cada vez que usemos este gancho. Todavía no estoy seguro de cómo superar esta limitación. Si tienes una idea, ¡házmelo saber!

¿Cómo puede ejecutar un useEffect que tiene dependencias solo una vez sin que la regla se queje al pasar una matriz vacía?

Di algo como:

useEffect(() => { init({ dsn, environment}) }, [])

Mi problema con esto es que ahora el componente debe conocer los detalles de implementación del gancho ...

No diría que estos son detalles de implementación. Es tu API. Si se pasa un objeto, asumimos que puede cambiar en cualquier momento.

Si en la práctica siempre es estático, puede convertirlo en un argumento para una fábrica de Hook. Como createFetch(config) que devuelve useFetch() . Llamas a la fábrica en el nivel superior.

Entiendo que es un poco raro. Tenemos un problema similar con el useSubscription que estamos trabajando. Pero dado que es un problema común, esto podría significar que useMemo realidad es una respuesta legítima y la gente debería acostumbrarse a hacerlo en estos casos.

En la práctica, analizaremos más esto en el futuro. Pero no debe tratar un objeto potencialmente dinámico como estático porque entonces un usuario no puede cambiarlo.

@asylejmani No ha proporcionado ningún detalle sobre sus casos de uso, como se solicita en la publicación superior. ¿Por qué espera que alguien pueda dar una respuesta a su pregunta?

El objetivo de la regla es decirle que environment y dsn pueden cambiar con el tiempo y su componente debería manejar eso. Entonces, o su componente tiene errores (porque no maneja cambios en esos valores) o tiene un caso único en el que no importa (en cuyo caso debe agregar una regla de pelusa ignorar el comentario que explique por qué).

En ambos casos, no está claro lo que está preguntando. La regla se queja de que las cosas pueden cambiar y usted no maneja ese cambio. Sin un ejemplo completo, usted solo puede responder por qué cree que manejarlo no es necesario.

@gaearon lo siento por no ser más claro (siempre suena claro en la cabeza) :) Mi pregunta es más como cómo lograrías componentDidMount con useEffect.

Tenía la impresión de que una matriz vacía hace eso, pero la regla me dice que siempre incluya dependencias en la matriz.

@asylejmani Creo que el mayor problema con los métodos de ciclo de vida de clases como componentDidMount es que tendemos a pensar en él como un método aislado, pero de hecho es parte de un flujo.
Si hace referencia a algo en componentDidMount , lo más probable es que también necesite manejarlo en componentDidUpdate , o su componente puede tener errores.
Esto es lo que la regla está tratando de corregir, debe manejar los valores a lo largo del tiempo.

  1. componente montado, hacer algo con un accesorio
  2. componente actualizado (por ejemplo: el valor de la propiedad ha cambiado), haga algo con el nuevo valor de la propiedad

El número 1 es donde pones la lógica en componentDidMount / useEffect body
El número 2 es donde pones la lógica en componentDidUpdate / useEffect deps

La regla es quejarse de que no está haciendo la parte número 2 del flujo.

@gaearon lo siento por no ser más claro (siempre suena claro en la cabeza) :) Mi pregunta es más como cómo lograrías componentDidMount con useEffect.

Tenía la impresión de que una matriz vacía hace eso, pero la regla me dice que siempre incluya dependencias en la matriz.

Creo que @asylejmani y @gaearon es que probablemente nos equivoquemos al ejecutar el efecto en el montaje solo si realmente tenemos dependencias.
¿Es esa una declaración justa? Supongo que estoy pensando que proporcionar una matriz vacía es como decir "Sé lo que estoy haciendo", pero entiendo por qué quieres mantener la regla en su lugar.

Lo siento por no proporcionar una caja de arena todavía. Comencé la otra noche con un ejemplo de Create React App, no pude averiguar cómo ejecutar eslint en la caja de arena, y luego perdí la caja de arena cuando recargué el navegador sin guardar primero (asumí que CodeSandbox temp almacenado, mi error).
Luego tuve que irme a la cama y no he tenido tiempo desde entonces.

En cualquier caso, entiendo lo que está diciendo y que, en escenarios normales, probablemente sea mejor incluir esas dependencias y no asumir que es suficiente para ejecutarse solo en el montaje.

Sin embargo, probablemente también haya casos de uso válidos para eso, pero es un poco difícil de explicar o dar un buen ejemplo, así que viviré con la desactivación de la regla en línea cuando sea necesario.

@asylejmani ¿su caso de uso es similar a https://github.com/facebook/react/issues/14920#issuecomment -466378650? No creo que sea posible que la regla comprenda el escenario en este caso, por lo que solo necesitaremos deshabilitarlo manualmente para ese tipo de código. En todos los demás casos, la regla funciona como debería.

No estoy seguro de si esto tiene sentido, pero un escenario que es bastante común para mí es algo como esto:

useEffect(() => {
    if(!dataIsLoaded) { // flag from redux
        loadMyData(); // redux action creator
    }
}, []);

Ambas dependencias provienen de redux. Los datos (en este caso) solo deben cargarse una vez, y el creador de la acción es siempre el mismo.

Esto es específico de redux, y su regla de eslint no puede saberlo, así que entiendo por qué debería advertir. ¿Todavía se pregunta si proporcionar una matriz vacía tal vez debería deshabilitar la regla? Me gusta que la regla me diga sobre los departamentos que faltan si proporcioné algunos pero no todos, o si no proporcioné ninguno. Matriz vacía significa algo diferente para mí. Pero ese podría ser solo yo :)

¡Gracias por todo su trabajo duro! Y por mejorar nuestra vida como desarrolladores :)

Mi caso de uso es mucho más simple y, por supuesto, puedo agregar todas las dependencias y seguirá funcionando igual, sin embargo, tenía la impresión de que la regla "advertirá" cuando tenga algunas dependencias pero falten otras.

El caso de uso de @einarq es algo que utilicé varias veces, por ejemplo, en "componentDidMount" cargar los datos si no hay ninguno (de redux o lo que sea).

También estoy de acuerdo en que, en estos casos, deshabilitar la regla en línea es la mejor opción. En ese caso, sabes exactamente lo que estás haciendo.

Creo que toda mi confusión fue [] contra [algunos] y, por supuesto, gracias a @gaearon por el increíble trabajo :)

Creo que @asylejmani y @gaearon es que probablemente nos equivoquemos al ejecutar el efecto en el montaje solo si realmente tenemos dependencias. ¿Es esa una declaración justa?

Si. Si su componente no maneja las actualizaciones de un accesorio, generalmente tiene errores. El diseño de useEffect te obliga a afrontarlo. Por supuesto, puede solucionarlo, pero el valor predeterminado es empujarlo para que maneje estos casos. Este comentario lo explica bien: https://github.com/facebook/react/issues/14920#issuecomment -470913287.

Ambas dependencias provienen de redux. Los datos (en este caso) solo deben cargarse una vez, y el creador de la acción es siempre el mismo.

Si es lo mismo, incluirlo en las dependencias no le hará daño. Quiero enfatizarlo: si está seguro de que sus dependencias nunca cambian, no hay ningún daño en enumerarlas . Sin embargo, si más tarde sucede que cambian (por ejemplo, si un componente principal pasa una función diferente según el estado), su componente lo manejará correctamente.

¿Todavía se pregunta si proporcionar una matriz vacía tal vez debería deshabilitar la regla?

No. Proporcionar una matriz vacía y luego preguntarse por qué algunos accesorios o estados están obsoletos es literalmente el error más común.

>

Tiene mucho sentido, gracias

El 8 de marzo de 2019, a las 15:27, Dan Abramov [email protected] escribió:

Creo que @asylejmani y @gaearon es que probablemente nos equivoquemos al ejecutar el efecto en el montaje solo si realmente tenemos dependencias. ¿Es esa una declaración justa?

Si. Si su componente no maneja las actualizaciones de un accesorio, generalmente tiene errores. El diseño de useEffect te obliga a afrontarlo. Por supuesto, puede solucionarlo, pero el valor predeterminado es empujarlo para que maneje estos casos. Este comentario lo explica bien: # 14920 (comentario).

Ambas dependencias provienen de redux. Los datos (en este caso) solo deben cargarse una vez, y el creador de la acción es siempre el mismo.

Si es lo mismo, incluirlo en las dependencias no le hará daño. Quiero enfatizarlo: si está seguro de que sus dependencias nunca cambian, no hay ningún daño en enumerarlas. Sin embargo, si más tarde sucede que cambian (por ejemplo, si un componente principal pasa una función diferente según el estado), su componente lo manejará correctamente.

¿Todavía se pregunta si proporcionar una matriz vacía tal vez debería deshabilitar la regla?

No. Proporcionar una matriz vacía y luego preguntarse por qué algunos accesorios o estados están obsoletos es literalmente el error más común.

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub o silencie el hilo.

@acansada

Eso provocaría que el efecto secundario se produzca antes de que se confirme la actualización real, lo que podría provocar que el efecto secundario se active con más frecuencia de la deseada.

No estoy seguro de lo que esto significa. ¿Puede dar un ejemplo?

Hicimos un pase sobre estos con @threepointone hoy. He aquí un resumen:

Corregido en la regla de pelusa

Dependencias useEffect extrañas

La regla ya no le impide agregar deps "extraños" a useEffect ya que existen escenarios legítimos.

Funciones en el mismo componente pero definidas fuera del efecto

Linter no advierte sobre los casos en los que ahora es seguro, pero en todos los demás casos le brinda mejores sugerencias (como mover la función dentro del efecto o envolverla con useCallback ).

Vale la pena arreglarlo en el código de usuario

Restableciendo el estado en el cambio de accesorios

Esto ya no produce violaciones de pelusa, pero la forma idiomática de restablecer el estado en respuesta a los accesorios es diferente . Esta solución tendrá un renderizado extra inconsistente, por lo que no estoy seguro de que sea deseable.

"Mi valor no funcional es constante"

Los ganchos te empujan hacia la corrección siempre que sea posible. Si especifica los deps (que en algunos casos se puede omitir), se recomienda encarecidamente que incluya incluso los que usted piensa que no va a cambiar. Sí, en este ejemplo de useDebounce es poco probable que cambie el retraso. Pero sigue siendo un error si lo hace, pero el Hook no puede manejarlo. Esto también se muestra en otros escenarios. (Por ejemplo, los Hooks son mucho más compatibles con la recarga en caliente porque cada valor se trata como dinámico).

Si insiste absolutamente en que un determinado valor es estático, puede aplicarlo.
La forma más segura es hacerlo explícitamente en su API:

const useFetch = createFetch({ /* config object */});
const useDebounce = createDebounce(500);
const FormInput = createInput({ rules: [emailValidator, phoneValidator] });

Entonces claramente no puede cambiar a menos que lo coloques dentro de render. (Lo cual no sería un uso idiomático de su Hook). Pero decir que <Slider min={50} /> nunca puede cambiar no es realmente válido, alguien podría cambiarlo fácilmente a <Slider min={state ? 50 : 100} /> . De hecho, alguien podría hacer esto:

let slider
if (isCelsius) {
  slider = <Slider min={0} max={100} />
} else {
  slider = <Slider min={32} max={212} />
}

Si alguien cambia isCelsius de estado, un componente que suponga que min nunca cambia no se actualizará. No es obvio en este caso que el Slider será el mismo (pero lo será porque tiene la misma posición en el árbol). Así que este es un arma importante en términos de realizar cambios en el código. Un punto importante de React es que las actualizaciones se procesan como los estados iniciales (normalmente no se puede saber cuál es cuál). Ya sea que represente el valor de prop B, o si pasa del valor de prop A a B, debería verse y comportarse de la misma manera.

Si bien esto no es aconsejable, en algunos casos, el mecanismo de aplicación podría ser un Hook que advierte cuando cambia el valor (pero proporciona el primero). Al menos entonces es más probable que se note.

function useMyHook(a) {
  const initialA = usePossiblyStaleValue(a);
  // ...
}

function usePossiblyStaleValue(value) {
  const ref = useRef(value);

  if (process.env.NODE_ENV !== 'production') {
    if (ref.current !== value) { // Or your custom comparison logic
      console.error(
        'Unlike normally in React, it is not supported ' +
        'to pass dynamic values to useMyHook(). Sorry!'
      );
    }
  }

  return ref.current;
}

También puede haber un caso legítimo en el que simplemente no pueda manejar una actualización. Por ejemplo, si la API de nivel inferior no lo admite, como un complemento jQuery o una API DOM. En ese caso, la advertencia sigue siendo apropiada para que el consumidor de su componente la comprenda. Alternativamente, puede crear un componente contenedor que restablezca el key en actualizaciones incompatibles, lo que obligará a un remontaje limpio con accesorios nuevos. Probablemente sea preferible para componentes de hoja como controles deslizantes o casillas de verificación.

"El valor de mi función es constante"

En primer lugar, si es constante y se eleva al alcance de nivel superior, el linter no se quejará. Pero eso no ayuda con las cosas que provienen de la utilería o el contexto.

Si realmente es constante, especificarlo en profundidad no está de más. Como el caso en el que una función setState dentro de un Hook personalizado se devuelve a su componente y luego la llama desde un efecto. La regla de la pelusa no es lo suficientemente inteligente como para comprender una indirecta como esta. Pero, por otro lado, cualquiera puede ajustar esa devolución de llamada más tarde antes de regresar, y posiblemente hacer referencia a otra propiedad o estado dentro de ella. ¡Entonces no será constante! Y si no logra manejar esos cambios, tendrá desagradables errores de propiedad / estado obsoletos. Por lo tanto, especificarlo es un mejor valor predeterminado.

Sin embargo, es un error pensar que los valores de las funciones son necesariamente constantes. Son más a menudo constantes en las clases debido al enlace de métodos, aunque eso crea su propia gama de errores . Pero, en general, cualquier función que se cierre sobre un valor en un componente de función no se puede considerar constante. La regla de la pelusa ahora es más inteligente para decirle qué hacer. (Como moverlo dentro del efecto, la solución más simple, o envolverlo con useCallback ).

Hay un problema en el espectro opuesto a esto, que es donde se obtienen bucles infinitos (el valor de una función siempre cambia). Lo detectamos en la regla de pelusa ahora cuando es posible (en el mismo componente) y sugerimos una solución. Pero es complicado si pasas algo varios niveles hacia abajo.

Aún puede envolverlo en useCallback para solucionar el problema. Recuerde que técnicamente es válido que una función cambie , y no puede ignorar este caso sin correr el riesgo de errores. Como onChange={shouldHandle ? handleChange : null} o renderizar foo ? <Page fetch={fetchComments /> : <Page fetch={fetchArticles /> en el mismo lugar. O incluso fetchComments que se cierra sobre el estado del componente principal. Eso puede cambiar. Con las clases, su comportamiento cambiará silenciosamente pero la referencia a la función seguirá siendo la misma. Por lo tanto, su hijo se perderá esa actualización; en realidad, no tiene otra opción que no sea pasar más datos al niño. Con los componentes de la función y useCallback , la identidad de la función en sí cambia, pero solo cuando es necesario. Así que esa es una propiedad útil y no solo un obstáculo a evitar.

Deberíamos agregar una mejor solución para detectar bucles asíncronos infinitos. Eso debería mitigar el aspecto más confuso. Podríamos agregar alguna detección para esto en el futuro. También puede escribir algo como esto usted mismo:

useWarnAboutTooFrequentChanges([deps]);

Esto no es ideal y tendremos que pensar en manejar esto con más gracia. Estoy de acuerdo en que casos como este son bastante desagradables. La solución sin romper la regla sería hacer rules estático, por ejemplo, cambiando la API a createTextInput(rules) , y envolver register y unregister en useCallback . Aún mejor, elimine register y unregister , y reemplácelos con un contexto separado donde ponga dispatch solo. Entonces puede garantizar que nunca tendrá una identidad de función diferente a la de leerlo.

Agregaría que probablemente desee poner useMemo en el valor de contexto de todos modos porque el proveedor hace muchos cálculos que sería triste repetir si no hay nuevos componentes registrados pero su propio padre actualizado. Así que este tipo de problema pone más visible el problema que de otro modo no habrías notado. Aunque estoy de acuerdo, debemos hacerlo aún más prominente cuando esto suceda.

Ignorar las dependencias de funciones conduce por completo a errores peores con los componentes de función y los Hooks porque seguirían viendo accesorios obsoletos y estados si lo haces. Así que trata de no hacerlo cuando puedas.

Reacción a los cambios de valor compuesto

Me resulta extraño por qué este ejemplo usa un efecto para algo que es esencialmente un controlador de eventos. Hacer el mismo "registro" (supongo que podría ser un envío de formulario) en un controlador de eventos parece más apropiado. Esto es especialmente cierto si pensamos en lo que sucede cuando el componente se desmonta. ¿Qué pasa si se desmonta justo después de que se programó el efecto? Cosas como el envío de formularios no deberían "no suceder" en ese caso. Entonces, parece que el efecto podría ser una elección incorrecta allí.

Dicho esto, aún puede hacer lo que intentó, haciendo que fullName sea setSubmittedData({firstName, lastName}) lugar, y luego [submittedData] es su dependencia, de la cual puede leer firstName y lastName .

Integración con código imperativo / heredado

Al integrarse con cosas imperativas como los complementos de jQuery o las API DOM sin procesar, se pueden esperar algunas cosas desagradables. Dicho esto, todavía espero que puedas consolidar los efectos un poco más en ese ejemplo.


¡Espero no haberme olvidado de nadie! Avísame si lo hice o si algo no está claro. Intentaremos convertir las lecciones de esto en algunos documentos pronto.

@gaearon , gracias por tomarse el tiempo para profundizar en los problemas y resumir los elementos de acción en diferentes categorías. Te perdiste mi muestra (# 14920 (comentario) de @trevorgithub) en tu comentario de resumen de cierre. (Ciertamente aprecio que haya muchos comentarios de mucha gente; creo que mi comentario original se perdió en la sección de elementos ocultos en algún lugar en medio de los comentarios del problema).

Supongo que mi muestra se incluiría en 'Integración con código imperativo / heredado', aunque tal vez también haya otras categorías.

En el caso de problemas de 'Integración con código imperativo / heredado', parece que no hay mucho que se pueda hacer. En esos casos, ¿cómo ignorar esta advertencia? Creo:
// eslint-disable-line react-hooks/exhaustive-deps

Lo siento, me perdí este.

Si recibe algunos datos a través de accesorios pero no quiere usar esos accesorios hasta que se realice algún cambio explícito, parece que una forma correcta de modelarlo sería tener un estado derivado.

Lo estás pensando como "Quiero ignorar un cambio en un accesorio hasta otro accesorio". Pero también puede pensar en ello como “Mi componente tiene una función de búsqueda en el estado. Se actualiza a partir de un accesorio cuando cambia otro accesorio ".

No se recomienda el estado derivado generalmente, pero aquí parece lo que desea. La forma más sencilla de implementar esto sería algo como:

const [currentGetData, setCurrentGetData] = useState(getData);
const [prevRefreshRequest, setPrevRefreshRequest] = useState(refreshRequest);

if (prevRefreshRequest !== refreshRequest) {
  setPrevRefreshRequest(refreshRequest);
  setCurrentGetData(getData);
}

useEffect(() => {
  currentGetData(someId);
}, [currentGetData, someId]);

También necesitaría poner useCallback alrededor de getData que está transmitiendo.

Tenga en cuenta que, en general, el patrón de transmisión de funciones asíncronas para la búsqueda me parece sombrío. Supongo que en tu caso usas Redux, así que tiene sentido. Pero si la función asíncrona se definiera en un componente principal, sería sospechoso porque probablemente tendría condiciones de carrera. Sus efectos no tienen una limpieza, entonces, ¿cómo puede saber cuándo el usuario elige una ID diferente? Existe el riesgo de que las solicitudes lleguen fuera de servicio y establezcan el estado incorrecto. Así que eso es solo algo a tener en cuenta. Si la búsqueda de datos está en el propio componente, entonces puede tener una función de limpieza de efectos que establezca un indicador de "ignorar" para evitar que un setState aparezca en la respuesta. (Por supuesto, mover datos a un caché externo suele ser una mejor solución; así es como también funcionará Suspense).

Hacer el mismo "registro" (supongo que podría ser un envío de formulario) en un controlador de eventos parece más apropiado.

@gaearon, el problema que veo es que hacerlo en el controlador de eventos significa que el efecto secundario ocurre antes de que se

Por ejemplo, si quiero registrar que el usuario ha enviado correctamente una nueva consulta de búsqueda y está viendo los resultados. Si algo sale mal y el componente se lanza, no quisiera que ocurriera ese evento de registro.

Luego está el caso en el que este efecto secundario podría ser asíncrono, por lo que usar useEffect le brinda la función de limpieza.

También está el problema de useReducer , donde el valor que podría querer registrar no estará disponible en el controlador de eventos. Pero creo que eso ya está en el radar de todos ustedes 🙂

En cualquier caso, es probable que el enfoque que recomiende sea suficiente. Almacene el estado compuesto en un formulario en el que aún pueda acceder a los valores individuales que compone.

Tengo un gancho de conveniencia para envolver funciones con parámetros adicionales y pasarlos. Se parece a esto:

function useBoundCallback(fn, ...bound) {
  return useCallback((...args) => fn(...bound, ...args), [fn, ...bound]);
}

(en realidad es un poco más complicado porque tiene algunas características adicionales, pero lo anterior aún muestra el problema relevante).

El caso de uso es cuando un componente necesita pasar una propiedad a un niño y quiere que el niño tenga una forma de modificar esa propiedad específica. Por ejemplo, considere una lista en la que cada elemento tiene una opción de edición. El objeto padre pasa un valor a cada hijo y una función de devolución de llamada para invocar si desea editar el valor. El niño no sabe qué ID de artículo está mostrando, por lo que el padre debe modificar la devolución de llamada para incluir este parámetro. Esto podría anidarse a una profundidad arbitraria.

Aquí se muestra un ejemplo simplificado de este flujo: https://codesandbox.io/s/vvv36834k5 (al hacer clic en "Ir", se muestra un registro de la consola que incluye la ruta de los componentes)


El problema es que obtengo 2 errores de linter con esta regla:

React Hook (X) tiene una dependencia faltante: 'bound'. Inclúyalo o elimine la matriz de dependencia

React Hook (X) tiene un elemento de propagación en su matriz de dependencia. Esto significa que no podemos verificar estáticamente si ha pasado las dependencias correctas

Cambiarlo para usar una lista de parámetros (es decir, no usar el operador de propagación) destruiría la memorización, porque la lista se crea con cada invocación incluso si los parámetros son idénticos.


Pensamientos:

  • ¿Vale la pena mostrar el primer error cuando también se aplica el segundo?
  • ¿Hay alguna forma de deshabilitar esta regla específicamente para lugares donde se usa el operador de propagación?
  • si el único uso de una variable dentro de una función es con el operador de propagación, es seguro usar el operador de propagación en las dependencias, y dado que la regla ya está detectando esto, seguramente debería permitirlo. Me doy cuenta de que hay casos más complejos que son más difíciles de resolver con el análisis estático, pero esto parece una victoria fácil para un uso relativamente común de propagación.

Hola @gaearon , acabo de recibir esta advertencia, que no he encontrado discutida en ninguna parte:

Accessing 'myRef.current' during the effect cleanup will likely read a different ref value because by this time React has already updated the ref. If this ref is managed by React, store 'myRef.current' in a variable inside the effect itself and refer to that variable from the cleanup function.

Encuentro este mensaje un poco confuso. Supongo que intenta advertirme que el valor actual de referencia en la limpieza puede ser diferente del valor en el cuerpo del efecto. ¿Derecha?
Si ese es el caso, y siendo consciente de ello, ¿es seguro / legítimo ignorar esta advertencia?

Mi caso, si es interesante: CodeSandbox
Contexto: algún gancho de obtención de datos personalizados. Utilizo un contador en una referencia para evitar tanto las condiciones de carrera como las actualizaciones de los componentes desmontados.

Creo que podría eludir esta advertencia ocultando la lectura de referencia dentro de una función, o creando otra referencia booleana para el caso de limpieza. Pero encuentro innecesariamente detallado si puedo ignorar esta advertencia.

@acansada

Por ejemplo, si quiero registrar que el usuario ha enviado correctamente una nueva consulta de búsqueda y está viendo los resultados. Si algo sale mal y el componente se lanza, no quisiera que ocurriera ese evento de registro.

Sí, eso suena como un caso de uso de nicho. Creo que cuando la mayoría de la gente quiere "efectos secundarios" se refieren al envío de formularios en sí, no al hecho de que usted vio un formulario enviado. En ese caso, la solución que proporcioné parece estar bien.

@ davidje13

¿Vale la pena mostrar el primer error cuando también se aplica el segundo?

Envíe un nuevo número de propuestas para cambiar la regla de pelusa.

¿Hay alguna forma de deshabilitar esta regla específicamente para lugares donde se usa el operador de propagación?

Siempre puedes // eslint-disable-next-line react-hooks/exhaustive-deps si crees que sabes lo que estás haciendo.

si el único uso de una variable dentro de una función es con el operador de propagación, es seguro usar el operador de propagación en las dependencias, y dado que la regla ya está detectando esto, seguramente debería permitirlo.

Presentar un nuevo problema por favor.

@CarlosGines

Encuentro este mensaje un poco confuso. Supongo que intenta advertirme que el valor actual de referencia en la limpieza puede ser diferente del valor en el cuerpo del efecto. ¿Derecha?

Si.

Si ese es el caso, y siendo consciente de ello, ¿es seguro / legítimo ignorar esta advertencia?

Ummm ... no si eso conduce a un error. 🙂

Contexto: algún gancho de obtención de datos personalizados. Utilizo un contador en una referencia para evitar tanto las condiciones de carrera como las actualizaciones de los componentes desmontados.

Sí, tal vez este caso de uso sea legítimo. ¿Presentar un nuevo problema para discutir por favor?

Voy a bloquear este problema ya que obtuvimos suficientes comentarios y se ha incorporado.

Preguntas y respuestas comunes: https://github.com/facebook/react/issues/14920#issuecomment -471070149

Si desea profundizar en useEffect y dependencias, está aquí: https://overreacted.io/a-complete-guide-to-useeffect/

Pronto también agregaremos más cosas a los documentos.

Si desea cambiar algo en la regla o no está seguro de que su caso sea legítimo, presente un nuevo problema.

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