Tengo un componente de pestañas que cambia de estado cada vez que se hace clic en una pestaña diferente.
Padre del componente
import { useState } from "react"; import "./styles.scss"; import MemoizedTab from "./tab"; export default function App() { const [selectTab, setSelectTab] = useState("a"); console.log("parent render"); return ( <div className="App"> <div className="tab-list"> <MemoizedTab tab={"a"} title={"First Title"} setSelectTab={setSelectTab} /> <MemoizedTab tab={"b"} title={"Second Title"} setSelectTab={setSelectTab} /> <MemoizedTab tab={"c"} title={"Third Title"} setSelectTab={setSelectTab} /> </div> {selectTab === "a" && <div>this is a</div>} {selectTab === "b" && <div>this is b</div>} {selectTab === "c" && <div>this is c</div>} </div> ); }Hijo del componente
import { memo } from "react"; const MemoizedTab = memo(({ title, tab, setSelectTab }) => { console.log("child render"); const handleClick = (tab) => { setSelectTab(tab); }; return <p onClick={() => handleClick(tab)}>{title}</p>; }); export default MemoizedTab; Después del procesamiento inicial, el padre tiene un estado de "a" . Cuando hago clic en "First Title" , que establece el estado en "a" nuevamente, nada se muestra como se esperaba.
Cuando hago clic por primera vez en "Second Title" , que establece el estado en "b" , obtengo un registro de consola de "parent renders" , que es como se esperaba. Pero cuando hago clic en "Second Title" por segunda vez, obtengo un registro de la consola de "parent renders" nuevamente, aunque el estado no cambió. No se registra nada en el tercer o cuarto clic. Siempre es el segundo.
Este mismo comportamiento ocurre cuando hago clic en "Third Title" , que establece el estado en "c" .
¿Por qué mi padre vuelve a renderizar en el segundo clic después de las transiciones de estado?
React en realidad no está volviendo a renderizar su componente. Puede verificar esto moviendo el archivo console.log dentro del gancho componentDidUpdate.
useEffect(()=>{ console.log('Parent re-rendered!') })Este archivo console.log no se registrará la segunda vez que haga clic en cualquiera de las pestañas.
Mientras que el archivo console.log en su ejemplo proporcionado se imprime, React eventualmente se libera de la actualización de estado. Este es en realidad un comportamiento esperado en React. El siguiente extracto es de los documentos :
Si actualiza un State Hook al mismo valor que el estado actual, React se rescatará sin renderizar a los niños ni disparar efectos. (React usa el algoritmo de comparación Object.is).
Tenga en cuenta que es posible que React aún necesite renderizar ese componente específico nuevamente antes de rescatar. Eso no debería ser una preocupación porque React no profundizará innecesariamente en el árbol.
Este es interesante: investigué un poco y encontré los siguientes problemas relacionados con React:
Esta es una peculiaridad conocida debido a los detalles de implementación de la concurrencia en React. No sabemos a bajo precio cuál de las dos versiones está comprometida actualmente. Cuando ocurre esta ambigüedad, tenemos que renderizar una vez y luego sabemos que ambas versiones son iguales y no importa.
useState no se rescata cuando el estado no cambia
Esto describe el mismo concepto subyacente: establecer el estado en el mismo valor puede hacer que el componente funcional vuelva a ejecutarse. La conclusión clave de ellos es el último comentario de Dan Abramov sobre el segundo número :
React no ofrece una garantía sólida sobre cuándo invoca funciones de renderizado. Se supone que las funciones de representación son puras y no debería haber ninguna diferencia en cuanto a la corrección, independientemente de cuántas veces se llamen. Si llamarlo un tiempo extra causa un error, es un problema con el código que debe corregirse.
Desde el punto de vista del rendimiento, el costo de volver a ejecutar una sola función suele ser insignificante. Si no es así, debe agregar algunos useMemos a las partes que son caras. React garantiza que si ocurre un rescate, no actualizará los componentes secundarios. Aunque en algunos casos podría volver a ejecutar la función del componente en sí.
Entonces React no garantiza cuándo invocará el componente funcional, y en este caso parece ejecutarlo el tiempo adicional para verificar que el resultado sea el mismo.
La buena noticia es que, aunque ejecuta la función una vez más, por lo que puedo decir, no parece volver a procesarse en la segunda invocación: si usa el generador de perfiles React, al mirar el Flamegraph puede ver la aplicación por primera vez. renders debido al cambio de gancho: 
pero en el segundo clic, la App en realidad no se vuelve a procesar: 