У меня есть родительский компонент с парой дочерних компонентов внутри него. Я хочу иметь возможность установить статус родителя в зависимости от состояния детей.

Например, представьте родительский компонент с 3 дочерними элементами, каждый из которых получает свой статус с сервера. Затем они устанавливают статус родителя, чтобы родитель мог видеть, есть ли у всех детей проблемы или только пара, например.

родитель:

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

Детский пример:

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

Конечно, этот пример будет превышать максимальную глубину обновления. Я попытался предотвратить это, используя следующее, например:

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

но это все еще не дает ожидаемого результата, так как состояние постоянно обновляется, но без превышения максимальной глубины обновления: пример Codesandbox .

Я также попытался использовать useCallback , который ничего не делал. Изменение зависимостей useEffect на пустой массив (ESLint в любом случае не позволяет мне делать это локально) также не сработало:

введите описание изображения здесь

Каков наилучший способ установить состояние родителя для дочернего компонента без постоянного повторного рендеринга?

Move const hasIssues = Math.random() {amp}lt;= 0.75; в useEffect() вызов useEffect() . Это предотвратит цикл рендеринга, поскольку значение будет сгенерировано только один раз.

Это также будет лучше имитировать вызов к серверу, поскольку это произойдет только один раз, когда компонент смонтирован.

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

Родитель также должен передать hasIssues обертки с помощью useCallback() и использовать обратный вызов средства обновления в setHasIssues() . Это предотвратит issuesHandler на каждом issuesHandler , что, в свою очередь, вызовет использование useEffect() и т. Д.

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

Живой пример:

 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; 

Это выглядит как случай для useContext. React Context используется для предоставления доступа к состоянию / функции для родительских и дочерних компонентов без использования ада обратного вызова.

Сначала создайте фиктивный компонент контекста где-нибудь в приложении, затем импортируйте его в родительский компонент и используйте его, чтобы обернуть вокруг компонентов, которые необходимо передать. И затем вместо того, чтобы передавать ваши состояния / функции дочернему элементу, вы просто передаете его компоненту контекста.

В ваших дочерних компонентах вы можете получить доступ к этим данным с помощью useContext.

issueContext.js (фиктивный файл контекста)

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

родительский компонент

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

Дочерний компонент

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