#reactjs #context-api
Вопрос:
Таким образом, у меня есть иерархия компонентов React, как показано ниже в App.js
lt;MyContextProvidergt; lt;HeaderComponentgt; lt;Route path="/path1"gt; lt;SomeComp1 /gt; lt;/Routegt; lt;/MyContextProvidergt;
Теперь в моем компоненте HeaderComponent я задаю значение контекста. Для некоторых сценариев он устанавливается дважды — один раз с помощью вызова API и второй, после загрузки этого компонента (на основе некоторых файлов cookie).
Поэтому я как бы переопределяю это значение контекста из вызова API значением локального файла cookie (только если файл cookie существует).
Теперь проблема в «SomeComp1», я использую этот контекст и имею некоторый общий код, который выполняет некоторые вызовы API, используя значение контекста в его параметре path. Поскольку контекст обновляется дважды, я вижу, что вызовы выполняются дважды в SomeComp1
Как лучше всего справиться с этим сценарием? В принципе, я хочу, чтобы в моем компоненте-потребителе был выполнен только один вызов API (т. Е. SomeComp1)
Итак, мой SomeComp1 выглядит так, как показано ниже (показан только соответствующий код)
const [contextData] = React.useContext(MyContext); const [stateString1, setStateString1] = useState(); useEffect(()=gt;{ setStateString1(contextData.stateString1); }, [contextData.stateString1]) useEffect(()=gt;{ let url = "http://someURL/${stateString1}"; fetch(url); }, [stateString1])
Комментарии:
1. Привет! Не могли бы вы, пожалуйста, добавить часть кода, которая вызывает API из SomeComp1?
2. @MilaA — Добавлен соответствующий код
3. опубликовал ответ, надеюсь, это поможет! 🙂
Ответ №1:
Я полагаю, что вызов выполняется в соответствии с эффектом использования lt;SomeComponent/gt;
компонента.
Что делать, если вы отслеживаете количество рендеров/имя типа, на котором основано текущее значение.
В Context
, например, создайте что-то вроде:
initialState = { fetched : false }; const [fetched, setFetched ] = useState( initialState.fetched );
и в lt;SomeComponent1/gt;
:
const { fetched, setFetched } = useContext( yourContextHere ); useEffect( () =gt; { if ( fetched ) { return; } axios.post.....then( () =gt; { setFetched( true ) } ) }, [] );
С вашим обновленным кодом это будет что-то вроде:
const [ apiCalled, setApiCalled ] = useState( false ) const [contextData] = React.useContext(MyContext); const [stateString1, setStateString1] = useState(); useEffect(()=gt;{ setStateString1(contextData.stateString1); }, [contextData.stateString1]) useEffect(()=gt;{ if ( apiCalled ) { return; } let url = "http://someURL/${stateString1}"; fetch(url).then( () =gt; { setApiCalled( true ) } ); }, [stateString1])
Зачем создавать такую «вспомогательную» переменную
Вы stateString1 is undefined by default and we cannot limit useEffect
должны быть вызваны только при первоначальном рендеринге.
Вот почему мы должны оставить массив зависимостей как [stateString1]
, но вести учет того, вызывали ли мы API уже или нет, в отдельной переменной.
СОХРАНИТЕ ЭТУ ПЕРЕМЕННУЮ В КОНТЕКСТЕ, ЕСЛИ КОМПОНЕНТ ИНОГДА УДАЛЯЕТСЯ ИЗ DOM.
В ПРОТИВНОМ СЛУЧАЕ ХРАНИТЕ ЕГО НЕПОСРЕДСТВЕННО В КОМПОНЕНТЕ
Ответ №2:
В конечном счете, если lt;SomeComp1 /gt;
это зависит от MyContext
того, то вполне допустимо, чтобы он перерисовывался так часто, как он меняется.
Поэтому вместо того, чтобы предотвращать или обходить это, вам нужно изменить lt;SomeComp1 /gt;
зависимости. Например:
- сделайте так, чтобы это зависело от какого-либо другого значения контекста или свойства, для которого можно повторно отобразить при его изменении,
- введите сигнал, который указывает на то, что значение еще не готово и, как таковое
lt;SomeComp1 /gt;
, вообще не должно отображаться (или отображать запасной вариант).
Например (в надежде, что этого тезиса будет достаточно):
lt;MyContextProvidergt; //- current context lt;ExtraContextProvidergt; //- new context, defaults to null (the signal) lt;HeaderComponentgt; //- consumes MyContext // does it's thing with regards to cookies // and only after that sets ExtraContext to the final value lt;Route path="/path1"gt; lt;SomeComp1/gt; //- consumes ExtraContext // but renders fallback (or nothing) as when it's null lt;/Routegt; lt;/ExtraContextProvidergt; lt;/MyContextProvidergt;
В качестве альтернативы то же самое можно сделать только с одним контекстом, включив сигнал в его значение.
Например: MyContext
может быть {someValue: string; ready: bool}
с некоторым начальным значением, например {someValue: 'foo'; ready: false}
.
lt;HeaderComponentgt;
будет потреблять это значение и, возможно, обновит его, но установитready: true
после того, как это будет сделано.lt;SomeComp1/gt;
будет, как и выше, отображаться только тогда , когдаready === true
, и в этом случае использоватьsomeValue
для своей работы.
Кстати, ваш эффект lt;SomeComp1/gt;
немного избыточен, это может быть просто:
const [contextData] = React.useContext(MyContext); useEffect(()=gt;{ let url = `http://someURL/${contextData.stateString1}`; fetch(url); }, [contextData.stateString1]);