Tengo un componente principal con un par de componentes secundarios dentro de él. Quiero poder establecer el estado de los padres dependiendo del estado de los hijos.

Por ejemplo, imagine un componente principal con 3 hijos que obtienen su estado de un servidor. Luego establecen el estado del padre para que el padre pueda ver si todos los niños tienen problemas, o solo una pareja, por ejemplo.

Padre:

const Parent = () ={amp}gt; { const [hasIssues, setHasIssues] = useState({ "child-item-1": false, "child-item-2": false, "child-item-3": false }); const issuesHandler = (childName, childStatus) ={amp}gt; { setHasIssues({ ...hasIssues, [childName]: childStatus }); }; return ( {amp}lt;div{amp}gt; {amp}lt;pre{amp}gt;{JSON.stringify(hasIssues, null, 2)}{amp}lt;/pre{amp}gt; {amp}lt;div{amp}gt; {amp}lt;ChildItemOne issuesHandler={issuesHandler} /{amp}gt; {amp}lt;ChildItemTwo issuesHandler={issuesHandler} /{amp}gt; {amp}lt;ChildItemThree issuesHandler={issuesHandler} /{amp}gt; {amp}lt;/div{amp}gt; {amp}lt;/div{amp}gt; ); }; 

Un ejemplo hijo:

 const ChildItemOne = ({ issuesHandler }) ={amp}gt; { // Imagine this is actually retrieved from a server const hasIssues = Math.random() {amp}lt;= 0.75; issuesHandler("child-item-1", hasIssues); return {amp}lt;div{amp}gt;{`child-item-1: ${hasIssues}`}{amp}lt;/div{amp}gt;; }; 

Por supuesto, este ejemplo superará la profundidad máxima de actualización. He intentado evitar esto usando lo siguiente, por ejemplo:

 useEffect(() ={amp}gt; { issuesHandler("child-item-1", hasIssues); }, [hasIssues, issuesHandler]); 

pero esto todavía no tiene el resultado esperado ya que el estado todavía se actualiza constantemente, pero sin exceder la profundidad máxima de actualización: ejemplo de Codesandbox .

También intenté usar useCallback , que no hizo nada. Cambiar las dependencias de useEffect a una matriz vacía (ESLint no me deja hacer eso localmente de todos modos), tampoco funcionó:

ingrese la descripción de la imagen aquí

¿Cuál es la mejor manera de establecer el estado de un padre a partir de un componente hijo sin que se produzca una representación constante?

Mover const hasIssues = Math.random() {amp}lt;= 0.75; en la devolución de llamada useEffect() . Esto evitará el ciclo de reproducción, ya que el valor solo se generará una vez.

Esto también simulará mejor la llamada al servidor, ya que solo sucederá una vez, cuando el componente esté montado.

 const ChildItemOne = ({ issuesHandler, hasIssues }) ={amp}gt; { useEffect(() ={amp}gt; { // Imagine this is actually retrieved from a server issuesHandler("child-item-1", Math.random() {amp}lt;= 0.75); }, [issuesHandler]); return {amp}lt;div{amp}gt;{`child-item-1: ${hasIssues}`}{amp}lt;/div{amp}gt;; }; 

El padre también debe pasar los hasIssues vuelta a los problemas de envoltura de los hijos issuesHandler con useCallback() , y usar la devolución de llamada del actualizador en setHasIssues() . Esto evitará que se vuelvan a issuesHandler los problemas que issuesHandler en cada render, lo que a su vez hará que se useEffect() los niños, etc.

 const Parent = () ={amp}gt; { const [hasIssues, setHasIssues] = useState({ "child-item-1": false, "child-item-2": false, "child-item-3": false }); const issuesHandler = useCallback((childName, childStatus) ={amp}gt; { setHasIssues(state ={amp}gt; ({ ...state, [childName]: childStatus })) }, [setHasIssues]); return ( {amp}lt;div{amp}gt; {amp}lt;pre{amp}gt;{JSON.stringify(hasIssues, null, 2)}{amp}lt;/pre{amp}gt; {amp}lt;div{amp}gt; {amp}lt;ChildItemOne issuesHandler={issuesHandler} hasIssues={hasIssues['child-item-1']} /{amp}gt; {amp}lt;ChildItemTwo issuesHandler={issuesHandler} hasIssues={hasIssues['child-item-2']} /{amp}gt; {amp}lt;ChildItemThree issuesHandler={issuesHandler} hasIssues={hasIssues['child-item-3']} /{amp}gt; {amp}lt;/div{amp}gt; {amp}lt;/div{amp}gt; ); }; 

Ejemplo en vivo:

 const { useState, useCallback, useEffect } = React; const ChildItemOne = ({ issuesHandler, hasIssues }) ={amp}gt; { useEffect(() ={amp}gt; { // Imagine this is actually retrieved from a server issuesHandler("child-item-1", Math.random() {amp}lt;= 0.75); }, [issuesHandler]); return {amp}lt;div{amp}gt;{`child-item-1: ${hasIssues}`}{amp}lt;/div{amp}gt;; }; const Parent = () ={amp}gt; { const [hasIssues, setHasIssues] = useState({ "child-item-1": false, "child-item-2": false, "child-item-3": false }); const issuesHandler = useCallback((childName, childStatus) ={amp}gt; { setHasIssues(state ={amp}gt; ({ ...state, [childName]: childStatus })) }, [setHasIssues]); return ( {amp}lt;div{amp}gt; {amp}lt;pre{amp}gt;{JSON.stringify(hasIssues, null, 2)}{amp}lt;/pre{amp}gt; {amp}lt;div{amp}gt; {amp}lt;ChildItemOne issuesHandler={issuesHandler} hasIssues={hasIssues['child-item-1']} /{amp}gt; {amp}lt;/div{amp}gt; {amp}lt;/div{amp}gt; ); }; ReactDOM.render( {amp}lt;Parent /{amp}gt;, root ); 
 {amp}lt;script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"{amp}gt;{amp}lt;/script{amp}gt; {amp}lt;script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"{amp}gt;{amp}lt;/script{amp}gt; {amp}lt;div id="root"{amp}gt;{amp}lt;/div{amp}gt; 

Esto parece un caso para useContext. React Context se utiliza para proporcionar acceso al estado / función para los componentes primarios y secundarios sin el uso del infierno de devolución de llamada.

Primero cree un componente de contexto ficticio en algún lugar de la aplicación, luego impórtelo al componente principal y úselo para envolver los componentes que necesitan pasar. Y luego, en lugar de pasar sus estados / funciones al hijo, simplemente se lo pasa al componente de contexto.

En sus componentes secundarios, puede acceder a esos datos utilizando useContext.

issueContext.js (archivo de contexto ficticio)

 // app/context/issueContext.js import { createContext } from 'react'; export default createContext(); 

componente padre

 import IssueContext from 'issueContext'; const Parent = () ={amp}gt; { const [hasIssues, setHasIssues] = useState({ ... }); const issuesHandler = (childName, childStatus) ={amp}gt; { ... }; return ( {amp}lt;{amp}gt; {amp}lt;IssueContext.Provider value={{ hasIssues, setHasIssues, issuesHandler }} {amp}gt; {amp}lt;ChildItemOne /{amp}gt; {amp}lt;ChildItemTwo /{amp}gt; {amp}lt;ChildItemThree /{amp}gt; {amp}lt;/IssueContext{amp}gt; {amp}lt;/{amp}gt; ); }; 

Componente hijo

 import React, {useContext} from 'react'; import IssueContext from 'issueContext'; const ChildItemOne = () ={amp}gt; { const {hasIssues, setHasIssues, issuesHandler} = useContext(IssueContext); if (something wrong) { sethasIssues(); issuesHandler("child-item-1", hasIssues); } };