React: Ошибка: слишком сложно исправить «Невозможно обновить компонент изнутри тела функции другого компонента».

Созданный на 28 февр. 2020  ·  109Комментарии  ·  Источник: facebook/react

# Примечание: React 16.13.1 исправил некоторые случаи, когда это происходило с перегрузкой. Если обновление React и ReactDOM до 16.13.1 не устраняет предупреждение, прочтите это: https://github.com/facebook/react/issues/18178#issuecomment -595846312

Версия React:

16.13.0

Действия по воспроизведению

  1. Постройте машину времени.
  2. Перейти в 2017 год.
  3. Создайте огромное приложение из 10К строк кода.
  4. Получите 80 (!) Зависимостей в файле package.json, включая те, которые больше не обслуживаются.
  5. Обновите React до последней версии 27 февраля 2020 года.
  6. Получите массу ошибок, которые вы не знаете, как исправить.
  7. Сообщите своему клиенту, что исправления займут неизвестное время, и это будет стоить $$$ + дней или недель исследования, или мы навсегда застрянем с устаревшей версией React и связанных библиотек, что будет стоить больше $$$ но позже.

Если серьезно, то бизнес, в котором я работаю, это совершенно не интересует. Очевидно, я никогда бы не смог получить такие предупреждения, если бы получил их раньше. В настоящее время невозможно исправить ошибки, потому что я получаю их во многих разных случаях и с огромной трассировкой стека. Я попытался исправить хотя бы одну из возникающих ошибок, и это уже заняло много времени. Я попытался отладить некоторые из используемых библиотек, но безуспешно.

Только один пример:

image

Здесь мы можем заметить использование устаревшего реактивного маршрутизатора, устаревшего redux-connect (который мне пришлось поместить в исходный код проекта, чтобы исправить ошибки устаревшего метода componentWillReceiveProps ), некоторых HOC, созданных путем перекомпоновки и т. Д. Это не просто виртуальное дерево DOM, в котором я могу пройти через разработанные мной компоненты и выполнить поиск по строке setState чтобы исправить ошибку, это намного сложнее.

Выберите вариант «НЕ БЕЗОПАСНО», чтобы отключить эту ошибку или предоставить более простой способ найти, где возникла ошибка 🙏

Discussion

Самый полезный комментарий

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

Если вы не можете понять, откуда это происходит в трассировках стека, разместите снимки экрана или создайте воспроизводящие песочницы, и мы постараемся помочь. Большинство из них, вероятно, поступают из нескольких библиотек, поэтому наиболее продуктивно уменьшить количество таких случаев, а затем устранить проблемы с этими библиотеками.

Спасибо вам всем.

Все 109 Комментарий

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

Обратите внимание, что это, скорее всего, приведет к серьезным сбоям в будущих версиях React. Преднамеренно или нет (у нас было много ошибок с этим шаблоном). Так или иначе, вы можете застрять на старой версии. Если исправить это невозможно, я бы рекомендовал установить более старую версию React.

Однако при этом мы хотим помочь вам и максимально упростить их исправление, в том числе посмотреть, сможем ли мы получить помощь от авторов библиотеки для публикации фиксированных версий.

Если вы развернете маленькую стрелку > в консоли Chrome, вы также должны увидеть дополнительную трассировку стека (в дополнение к стеку компонентов на снимке экрана). Вы тоже можете это опубликовать? Это должно показать вам точный callite, вызывающий побочный эффект при рендеринге.

У меня это появляется, когда я использую формик

image

@sebmarkbage благодарим за ответ. Трассировка стека, появляющаяся после нажатия>, просто смешна. Он содержит 200+ предметов!

Собирался наклеить туда или дать ссылку на pastebin, но попробовал другое направление. Я прошел через проблемы Github некоторых используемых библиотек и обнаружил, что одним из подозреваемых является redux-form: https://github.com/redux-form/redux-form/issues/4619. Я надеюсь, что это единственная библиотека, которая вызывает ошибки, и я собираюсь дождаться исправления, прежде чем обновлять React.

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

@RodolfoSilva вы уверены, что это вызвано формиком? Если да, не могли бы вы создать там проблему и разместить ссылку на нее здесь? Я собираюсь создать список таких проблем в начале моего первого сообщения, если список будет содержать более одного элемента.

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

Хм. Интересно, поможет ли описание строки, которую следует искать в сообщении об ошибке.

В этом случае первая строка, которую нужно искать, - это строка после dispatchAction . Это должно быть то, что вызывает React.

@RodolfoSilva, не могли бы вы опубликовать источник FormItemInput.js , если это что-то, чем вы можете поделиться? Кажется, это вызывает dispatch или setState в строке 71.

Я считаю, что это сообщение об ошибке необходимо изменить, указав, какая именно строка кода вызывает ошибку. Практически невозможно точно определить, является ли причиной проблемы локальный код или код библиотеки. Внешние библиотеки librairies,

Я почти уверен, что здесь не виноват React-Redux, но согласился с тем, что сообщение об ошибке и стек компонентов затрудняют понимание того, что на самом деле происходит.

У меня такая же проблема с Redux-form!

У меня такая же проблема, и я вижу, что предупреждение отображается в первый раз, когда я пишу в своем поле redux или когда я все это очищаю

У меня тоже есть такая проблема, это мой компонент:

`const [цена, setPrice] = useState (0);

const updatePrice = (newPrice) => {
setPrice (newPrice)
}
<CardContainer onPriceUpdated = {updatePrice}> CardContainer>
`

Итак, в этом случае мой компонент CardContainer уведомляет родительский компонент, когда цена обновляется, и родительский компонент отображает нового принца.
Итак, я предполагаю, что React предупреждает меня, что я пытаюсь обновить состояние компонента, используя функцию другого компонента.
Я новичок в React, поэтому не уверен, что это шаблон React или на самом деле ошибка.

Если у вас есть предложения по устранению этого предупреждения, я был бы признателен ''

@ l0gicgate

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

Есть ограничения на то, что мы можем делать в JavaScript. Но вся информация находится в стеке, который вы видите в браузере . Все, что вам нужно сделать, это пропустить строки внутри React.

Чтобы увидеть стек JavaScript, вам нужно щелкнуть маленькую стрелку рядом с сообщением об ошибке .

Например, посмотрите на этот снимок экрана ранее:

75614021-cb812980-5b12-11ea-8a6e-a38f4cd6aeef

Я понимаю, что копаться в стеке немного раздражает, но пропустить первые несколько кадров не так уж и сложно. Уже следующий кадр - источник проблемы. В данном случае это похоже на что-то внутри библиотеки Formik. Так что вы можете подать жалобу на него.

@martinezwilmer Этот пример слишком мал, чтобы помочь. Создайте песочницу, пожалуйста.

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

Если вы не можете понять, откуда это происходит в трассировках стека, разместите снимки экрана или создайте воспроизводящие песочницы, и мы постараемся помочь. Большинство из них, вероятно, поступают из нескольких библиотек, поэтому наиболее продуктивно уменьшить количество таких случаев, а затем устранить проблемы с этими библиотеками.

Спасибо вам всем.

Здесь сложно найти точную строку, о которой идет речь в предупреждении:

@gaearon у вас есть еще один совет по этому поводу?

image

Здесь сложно найти точную строку, о которой идет речь в предупреждении:

Что делает это трудным? Как я уже отмечал выше, это первая строка, которая не говорит «реагировать» с правой стороны. В данном случае это connectAdvanced от Redux. Пожалуйста, сообщите о проблеме в React Redux, чтобы у сопровождающих была возможность ознакомиться с ней.

Как я уже сказал, я был бы _ очень_ удивлен, если бы все, что здесь происходит, было проблемой с React-Redux.

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

Я столкнулся с этим недавно, и исправление заключалось в том, чтобы оборачивать сайты вызовов обработчиков setState в useEffect , например: https://github.com/airbnb/lunar/commit/db08613d46ea21089ead3e7b5cfff995f15c69a7#diff -1c3ce7088456345 onChange и onSubmit используют setState выше по цепочке).

@martinezwilmer Куда onPriceUpdated ? Может попробовать обернуть в useEffect ?

Та же проблема, похоже, происходит для urql

Мы используем use-subscription + wonka (для потоков) для оркестровки наших обновлений, однако обновление может поступать синхронно. Здесь мы уже получили todos поэтому, если мы нажмем кнопку Open , результат должен сразу же появиться, однако это, похоже, вызывает следующую ошибку.

image

В нашем случае это предназначено, мы не можем просто показать fetching: true для результата синхронизации, что приведет к скачкообразным интерфейсам.

Сейчас это начинает появляться все больше и больше в сторонних библиотеках: urql , Apollo .

Я столкнулся с этим и в течение нескольких часов предполагал, что проблема в моем коде. Сжатая трассировка стека указывает на мои компоненты, и для меня нет ничего необычного в том, чтобы видеть сторонние библиотеки в расширенной трассировке стека, когда я _did_ явно инициирую ошибку. Из моего (хотя и ограниченного) исследования этого конкретного предупреждения кажется, что большинство разработчиков не сами вызывают эту проблему, а скорее зависят от кода, который это делает. Обычно хорошей практикой является предположение, что это не ошибка восходящего потока, но когда это ошибка восходящего потока, тратить время на отслеживание проблемы в вашем коде, которой не существует, довольно неприятно. Может ли React что-нибудь сделать, чтобы помочь среднему пользователю определить, был ли это код, который он написал, или код, от которого он зависит, вызвал проблему?

Одна вещь, которую я отмечаю в выпуске Apollo:

Трассировка стека предупреждения показывает компонент, который инициировал изменения, а не тот, который повторно отрисовывается [sic] этими изменениями.

Если это правильно, может ли React предоставить здесь дополнительную информацию? Возможно, сообщая нам как о начальном компоненте, так и о компонентах, которые он вызвал к обновлению?

Как и @hugo , я столкнулся с этим при тестировании нового приложения Ionic :

  1. npx ionic start demo sidemenu --type=react
  2. react-scripts test

Действительно, причина кроется в середине и внизу трассировки стека.

console.error node_modules/react-dom/cjs/react-dom.development.js:88
    Warning: Cannot update a component from inside the function body of a different component.
        in Route (at App.tsx:37)
        in View (created by StackManagerInner)
        in ViewTransitionManager (created by StackManagerInner)
        in ion-router-outlet (created by IonRouterOutlet)
        in IonRouterOutlet (created by ForwardRef(IonRouterOutlet))
        in ForwardRef(IonRouterOutlet) (created by StackManagerInner)
        in StackManagerInner (created by Context.Consumer)
        in Unknown (created by Component)
        in Component (created by ForwardRef(IonRouterOutlet))
        in ForwardRef(IonRouterOutlet) (at App.tsx:36)
        in ion-split-pane (created by IonSplitPane)
        in IonSplitPane (created by ForwardRef(IonSplitPane))
        in ForwardRef(IonSplitPane) (at App.tsx:34)
        in NavManager (created by RouteManager)
        in RouteManager (created by Context.Consumer)
        in RouteManager (created by IonReactRouter)
        in Router (created by BrowserRouter)
        in BrowserRouter (created by IonReactRouter)
        in IonReactRouter (at App.tsx:33)
        in ion-app (created by IonApp)
        in IonApp (created by ForwardRef(IonApp))
        in ForwardRef(IonApp) (at App.tsx:32)
        in App (at App.test.tsx:6)

Эта проблема была наиболее близкой к этой проблеме, которую я мог найти.

Мы нашли конкретный шаблон, который вызывает эту проблему с mobx, в https://github.com/mobxjs/mobx-react/issues/846

@sebmarkbage Я больше не могу воспроизвести эту проблему. Мы обновили некоторые библиотеки, и проблемы исчезли.

@jgoux, похоже, мы сталкиваемся с той же проблемой @Clovis ^^

Я начал получать эту ошибку после того, как обновление реагировало на react 16.13.0 . Проблема довольно ясна, поскольку один из моих компонентов обновляет другой после выполнения определенного действия. Однако не уверен, почему это вызывает предупреждение. Любые предложения о том, как это обойти?

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

Не уверен, связана ли моя проблема, однако я пытаюсь обновить состояние моего компонента формы, но всякий раз, когда я пытаюсь добавить обработчик onChange, он продолжает выдавать мне эту ошибку. Имейте в виду, я использую response-jsonschema-form и импортировал компонент Form, и я использую его свойство onChange для обновления состояния ..

Для меня это шаблон, который вызывает проблему.

image

Есть способ обойти это. Но журнал консоли указал мне прямо на строку 385

Я новичок в реакции, но у меня был такой код:

import React, { useState } from "react";

function Home() {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav setMobileNavOpen={setMobileNavOpen}/>}
    </div>
  );
}

function MobileNav() {
  return (
    <div>
      <button
        onClick={setMobileNavOpen(false)} //problem here
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;

В результате получилось: Cannot update a component from inside the function body of a different component.

Все, что мне нужно было сделать, это добавить функцию стрелки для setMobileNavOpen в MobileNav, например:

import React, { useState } from "react";

function Home() {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav setMobileNavOpen={setMobileNavOpen}/>}
    </div>
  );
}

function MobileNav() {
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(false)} //fixes problem
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;

И это решает проблему, надеюсь, это кому-то поможет!

Я новичок в реакции, но у меня был такой код:

import React, { useState } from "react";

function Home() {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav setMobileNavOpen={setMobileNavOpen}/>}
    </div>
  );
}

function MobileNav() {
  return (
    <div>
      <button
        onClick={setMobileNavOpen(false)} //problem here
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;

В результате получилось: Cannot update a component from inside the function body of a different component.

Все, что мне нужно было сделать, это добавить функцию стрелки для setMobileNavOpen в MobileNav, например:

import React, { useState } from "react";

function Home() {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav setMobileNavOpen={setMobileNavOpen}/>}
    </div>
  );
}

function MobileNav() {
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(false)} //fixes problem
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;

И это решает проблему, надеюсь, это кому-то поможет!

На самом деле ваш пример - одна из первых ошибок, на которые люди реагируют. Я не уверен, что здесь обсуждается точно такая же проблема. Ваша строка здесь: onClick={setMobileNavOpen(false) вызывает функцию во время тендера кнопки, а не при нажатии. Вот почему включение его в функцию стрелки исправляет это.

Я столкнулся с этой проблемой с React Router, когда мне нужно было отправить действие Redux до того, как <Redirect> перенаправит пользователя в другое место. Проблема, похоже, заключалась в том, что перенаправление произошло до завершения отправки. Я решил проблему, пообещав свои действия.

До:

<Route
  render={routeProps => {
    setRedirectionTarget(somePath(routeProps));
    return <Redirect to={someOtherPath} />;
  }}
/>;

function mapDispatchToProps(dispatch: ThunkDispatch) {
  return {
    setRedirectionTarget: (target: string | null) => dispatch(setRedirectionTarget(target))
  };
}

export const setRedirectionTarget = (location: string | null): SetRedirectionTarget => {
  return {
    type: SET_REDIRECTION_TARGET,
    location
  };
};

После:

function mapDispatchToProps(dispatch: ThunkDispatch) {
  return {
    setRedirectionTarget: async (target: string | null) => dispatch(await setRedirectionTarget(target))
  };
}

export const setRedirectionTarget = (location: string | null): Promise<SetRedirectionTarget> => {
  return Promise.resolve({
    type: SET_REDIRECTION_TARGET,
    location
  });
};

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

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

Я счастлив взглянуть на это. Любые указатели, о которых мне следует знать?

@ samcooke98 Я открыл PR для этого https://github.com/facebook/react/pull/18316

Как указывали другие, вы можете столкнуться с этой проблемой, если у вас есть подписка в хуке, например, с Apollo, и вы пытаетесь обновить хранилище при получении данных. Простое исправление - использовать useEffect например.

export const useOrderbookSubscription = marketId => {
  const { data, error, loading } = useSubscription(ORDERBOOK_SUBSCRIPTION, {
    variables: {
      marketId,
    },
  })

  const formattedData = useMemo(() => {
    // Don't dispatch in here.
  }, [data])

  // Don't dispatch here either

  // Dispatch in a useEffect
  useEffect(() => {
    orderbookStore.dispatch(setOrderbookData(formattedData))
  }, [formattedData])

  return { data: formattedData, error, loading }
}

Мы столкнулись с этой проблемой в Hyper, но мы не используем хуки и не смогли найти ничего в вызове рендеринга из стека вызовов. Но был вызов в стеке UNSAFE_componentWillReceiveProps . Обновление с помощью componentDidUpdate решило проблему для нас https://github.com/zeit/hyper/pull/4382
Разместил здесь, если это может кому-то помочь

То же самое здесь, был вызов UNSAFE_componentWillMount, его изменение / удаление устранило проблему

но мы не используем хуки и не смогли найти ничего в вызове рендеринга из стека вызовов

Звучит странно. Тогда я не понимаю, как вы получили это предупреждение. Он срабатывает, только если setState принадлежит функциональному компоненту. На что похож ваш стек?

но мы не используем хуки и не смогли найти ничего в вызове рендеринга из стека вызовов

Звучит странно. Тогда я не понимаю, как вы получили это предупреждение. Он срабатывает, только если setState принадлежит функциональному компоненту. На что похож ваш стек?

Компоненты классов с Redux и функции, которые применяют плагины к компонентам. Может поэтому он считается функциональной составляющей? Но тогда почему обновление хуков жизненного цикла исправляет это?

Я не уверен. Можете попробовать создать изолированный пример в песочнице? У меня есть подозрение, что вы можете сделать что-то еще неожиданное.

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

Может ли это быть из-за того, что в то время в компоненте были и UNSAFE_componentWillReceiveProps и componentDidUpdate (небезопасный компонент был оставлен там по ошибке)?

Я также получаю это предупреждение, и трассировка стека указывает на хук setScriptLoaded (мой собственный хук см. Ниже). Таким образом, похоже, что даже если я использую useEffect , React все равно выдаст предупреждение, если обработчик setState вложен в другие асинхронные обратные вызовы (в моем случае он вложен в setTimeout и обработчик события load )? Я впервые использую Hooks, поэтому буду признателен за любой совет. Спасибо!

/**
 * Detect when 3rd party script is ready to use
 * 
 * <strong i="11">@param</strong> {function} verifyScriptLoaded Callback to verify if script loaded correctly
 * <strong i="12">@param</strong> {string} scriptTagId 
 */

export const useScriptLoadStatus = (verifyScriptLoaded, scriptTagId) => {
    let initLoadStatus = true; // HTML already includes script tag when rendered server-side
    if (__BROWSER__) {
        initLoadStatus = typeof verifyScriptLoaded === 'function' ? verifyScriptLoaded() : false;
    }
    const [isScriptLoaded, setScriptLoaded] = useState(initLoadStatus); 

    useEffect(() => {
        if (!isScriptLoaded) {
            // need to wrap in setTimeout because Helmet appends the script tags async-ly after component mounts (https://github.com/nfl/react-helmet/issues/146)
            setTimeout(() => {
                let newScriptTag = document.querySelector(`#${scriptTagId}`);
                if (newScriptTag && typeof verifyScriptLoaded === 'function') {
                    newScriptTag.addEventListener('load', () => { 
                        return verifyScriptLoaded() ? setScriptLoaded(true) : null;
                    });
                    // double check if script is already loaded before the event listener is added
                    return verifyScriptLoaded() ? setScriptLoaded(true) : null;
                }
            }, 100);
        }
    });

    return isScriptLoaded;
};

@LabhanshAgrawal

Может ли это быть из-за того, что в то время в компоненте были и UNSAFE_componentWillReceiveProps, и componentDidUpdate (небезопасный остался там по ошибке)?

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

@suhanw Пожалуйста, предоставьте полный пример в CodeSandbox. Я не вижу проблем с вашим кодом, которые должны вызывать это предупреждение.

@LabhanshAgrawal Не могли бы вы UNSAFE_componentWillReceiveProps (что эквивалентно рендерингу) вызывает setState на другом компоненте.

backend.js:6 Warning: Cannot update a component from inside the function body of a different component.
    in Term                           (created by _exposeDecorated(Term))
    in _exposeDecorated(Term)         (created by DecoratedComponent)
    in DecoratedComponent             (created by TermGroup_)
    in TermGroup_                     (created by ConnectFunction)
    in ConnectFunction                (created by Connect(TermGroup_))
    in Connect(TermGroup_)            (created by _exposeDecorated(TermGroup))
    in _exposeDecorated(TermGroup)    (created by DecoratedComponent)
    in DecoratedComponent             (created by Terms)
    in div                            (created by Terms)
    in div                            (created by Terms)
    in Terms                          (created by _exposeDecorated(Terms))
    in _exposeDecorated(Terms)        (created by DecoratedComponent)
    in DecoratedComponent             (created by ConnectFunction)
    in ConnectFunction                (created by Connect(DecoratedComponent))
    in Connect(DecoratedComponent)    (created by Hyper)
    in div                            (created by Hyper)
    in div                            (created by Hyper)
    in Hyper                          (created by _exposeDecorated(Hyper))
    in _exposeDecorated(Hyper)        (created by DecoratedComponent)
    in DecoratedComponent             (created by ConnectFunction)
    in ConnectFunction                (created by Connect(DecoratedComponent))
    in Connect(DecoratedComponent)
    in Provider
r                                     @ backend.js:6
printWarning                          @ react-dom.development.js:88
error                                 @ react-dom.development.js:60
warnAboutRenderPhaseUpdatesInDEV      @ react-dom.development.js:23260
scheduleUpdateOnFiber                 @ react-dom.development.js:21196
dispatchAction                        @ react-dom.development.js:15682
checkForUpdates                       @ connectAdvanced.js:88
handleChangeWrapper                   @ Subscription.js:97
(anonymous)                           @ Subscription.js:23
batchedUpdates$1                      @ react-dom.development.js:21887
notify                                @ Subscription.js:19
notifyNestedSubs                      @ Subscription.js:92
checkForUpdates                       @ connectAdvanced.js:77
handleChangeWrapper                   @ Subscription.js:97
(anonymous)                           @ Subscription.js:23
batchedUpdates$1                      @ react-dom.development.js:21887
notify                                @ Subscription.js:19
notifyNestedSubs                      @ Subscription.js:92
handleChangeWrapper                   @ Subscription.js:97
dispatch                              @ redux.js:222
e                                     @ VM64:1
(anonymous)                           @ effects.ts:11
(anonymous)                           @ write-middleware.ts:14
(anonymous)                           @ index.js:11
(anonymous)                           @ plugins.ts:538
(anonymous)                           @ plugins.ts:540
(anonymous)                           @ index.js:11
dispatch                              @ redux.js:638
(anonymous)                           @ sessions.ts:124
(anonymous)                           @ index.js:8
dispatch                              @ VM64:1
onResize                              @ terms.ts:62
(anonymous)                           @ term.js:179
e.fire                                @ xterm.js:1
t.resize                              @ xterm.js:1
e.resize                              @ xterm.js:1
e.fit                                 @ xterm-addon-fit.js:1
fitResize                             @ term.js:291
UNSAFE_componentWillReceiveProps      @ term.js:408
callComponentWillReceiveProps         @ react-dom.development.js:12998
updateClassInstance                   @ react-dom.development.js:13200
updateClassComponent                  @ react-dom.development.js:17131
beginWork                             @ react-dom.development.js:18653
beginWork$1                           @ react-dom.development.js:23210
performUnitOfWork                     @ react-dom.development.js:22185
workLoopSync                          @ react-dom.development.js:22161
performSyncWorkOnRoot                 @ react-dom.development.js:21787
(anonymous)                           @ react-dom.development.js:11111
unstable_runWithPriority              @ scheduler.development.js:653
runWithPriority$1                     @ react-dom.development.js:11061
flushSyncCallbackQueueImpl            @ react-dom.development.js:11106
flushSyncCallbackQueue                @ react-dom.development.js:11094
batchedUpdates$1                      @ react-dom.development.js:21893
notify                                @ Subscription.js:19
notifyNestedSubs                      @ Subscription.js:92
handleChangeWrapper                   @ Subscription.js:97
dispatch                              @ redux.js:222
e                                     @ VM64:1
(anonymous)                           @ effects.ts:11
(anonymous)                           @ write-middleware.ts:14
(anonymous)                           @ index.js:11
(anonymous)                           @ plugins.ts:538
(anonymous)                           @ plugins.ts:540
(anonymous)                           @ index.js:11
dispatch                              @ redux.js:638
effect                                @ ui.ts:60
(anonymous)                           @ effects.ts:13
(anonymous)                           @ write-middleware.ts:14
(anonymous)                           @ index.js:11
(anonymous)                           @ plugins.ts:538
(anonymous)                           @ plugins.ts:540
(anonymous)                           @ index.js:11
dispatch                              @ redux.js:638
(anonymous)                           @ ui.ts:54
(anonymous)                           @ index.js:8
dispatch                              @ VM64:1
(anonymous)                           @ index.tsx:162
emit                                  @ events.js:210
(anonymous)                           @ rpc.ts:31
emit                                  @ events.js:210
onMessage                             @ init.ts:50

@gaearon ценю быстрый ответ. Я найду способ создать пример в CodeSandbox (это сложно), но пока что это моя полная трассировка стека.

строка, указывающая на мой пользовательский хук, следующая: https://gist.github.com/suhanw/bcc2688bba131df8301dae073977654f#file -stack-trace-L144

Было бы здорово, если бы вы могли взглянуть и сообщить мне, есть ли что-нибудь в трассировке стека до / после моего пользовательского хука, что я должен исследовать дальше. Спасибо!

@LabhanshAgrawal В вашем стеке UNSAFE_componentWillReceiveProps вызывает некий fitResize который отправляет действие Redux, которое, в свою очередь, обновляет кучу компонентов. Отсюда проблема. Так что да, изменение на componentDidUpdate работает.

@suhanw В вашем стеке что-то под названием ModuleImpressionTracker видимому, отправляет действие Redux во время конструктора. У конструкторов не должно быть побочных эффектов. Я думаю, что это причина проблемы, а не ваш крючок.

Понятно, поэтому я убираю из этого, что UNSAFE_componentWillReceiveProps считается отрисовкой, а componentDidUpdate - нет.
Не могли бы вы немного пояснить, что это проблема с функциональными компонентами и хуками, выполняющими setState, не могли понять это из обсуждения выше.
Приносим извинения, если вопрос немного глупый или я ошибаюсь, я немного новичок в этом.

@LabhanshAgrawal

Не могли бы вы немного пояснить, что это проблема с функциональными компонентами и хуками, выполняющими setState, не могли понять это из обсуждения выше.

Честно говоря, я сам не уверен, из-за всего того, что Redux находится посередине. Это довольно сбивает с толку из-за того, как реализован React Redux. Было бы неплохо, если бы кто-то мог получить четкую репродукцию, которая включает только React, но использует компоненты класса.

По-прежнему кажется, что вставляется много трассировок стека, а не так много фактических воспроизведений фактической проблемы независимо от типа.

Я предполагаю, что urql предназначен для @gaearon, поэтому происходит следующее:

  • У нас есть смонтированный <Todos /> , который извлекает данные и отображает их.
  • Предыдущая настройка потока, подключенного к urqlClient
  • Мы визуализируем наш второй <Todos /> это даст ту же комбинацию запроса + переменных, поэтому обновим результат для <Todos /> первого шага.
  • use-subscription запускается для обоих.

Воспроизведение - нажмите «открыть» вверху, когда отобразится первый запрос.

Мы могли бы поставить обновления в очередь, но пока мы не выполняем рендеринг top-down с контекстом, мы не сможем обойти эту проблему, я полагаю? Теоретически это предназначено для этого случая. Любопытно, как Relay работает с этим.

РЕДАКТИРОВАТЬ: мы могли бы найти решение для случая urql, не вызывая всех подписчиков для обновления во время getCurrentValue из use-subscription

https://github.com/FormidableLabs/urql/commit/3a597dd92587ef852c18139e9781e853f763e930

Хорошо, так что если посмотреть на это глубже, я думаю, что мы предупреждаем в большем количестве случаев (например, для классов), чем мы изначально планировали. Мы рассмотрим, как заставить замолчать некоторые из них.

@JoviDeCroock Этот пример не очень полезен, потому что я понятия не имею, что делает urql . :-) Если вам нужна обратная связь по шаблону, не могли бы вы подготовить песочницу, демонстрирующую этот шаблон изолированно?

@JoviDeCroock Да, getCurrentValue определенно не имеет побочных эффектов. Мы думали, что это имя довольно ясно об этом.

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

function useCustomHook() {
   const dispatch = useDispatch()
   const value = useSomeOtherHook()
   dispatch(action(value))
}

Я исправил это, заключив отправку в useEffect .

@Glinkis В

@gaearon да, проблема, которую мы пытались решить, лучше объяснена здесь и кажется довольно распространенной.

@Glinkis В

Мне нужно, чтобы мое состояние redux синхронизировалось с идентификаторами моего маршрута, поэтому я отправляю это обновление при изменении моего маршрута.

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

@Glinkis Откуда

@JoviDeCroock Я думаю, что наша последняя рекомендация для этого шаблона - это настраиваемый запланированный проход сборки мусора.

@Glinkis Откуда

Не уверен, что это относится к этому обсуждению, но это моя зацепка.

export function useOpenFolder() {
  const dispatch = useDispatch()
  const match = useRouteMatch('/:workspace/(super|settings)?/:type?/:id?/(item)?/:item?')
  const { id, item } = match?.params || {}

  useEffect(() => {
    dispatch(
      openFolder({
        id: id || '',
        item: item || '',
      })
    )
  }, [dispatch, item, id])
}

Позже я использую это состояние для таких селекторов, как:

export const getActiveItem = createSelector(
  [getActiveFolderItem, getItems],
  (activeItem, items) => items.all[activeItem]
)

@Glinkis Да, возможно, давайте useRouteMatch в родительском компоненте, передать идентификатор как опору, а затем прочитать реквизиты в селекторе, как обычно. (На самом деле я не знаю, как это делается в Redux в наши дни, но должен быть какой-то способ.)

Понятно, поэтому я убираю из этого, что UNSAFE_componentWillReceiveProps считается отрисовкой, а componentDidUpdate - нет.

@LabhanshAgrawal Верно. Некоторое объяснение здесь :

Концептуально React работает в два этапа:

  • Фаза рендеринга определяет, какие изменения необходимо внести, например, в DOM. На этом этапе React вызывает render а затем сравнивает результат с предыдущим отрисовкой.
  • Фаза фиксации - это когда React применяет какие-либо изменения. (В случае React DOM это когда React вставляет, обновляет и удаляет узлы DOM.) На этом этапе React также вызывает жизненные циклы, такие как componentDidMount и componentDidUpdate .

Фаза фиксации обычно очень быстрая, но рендеринг может быть медленным. По этой причине предстоящий параллельный режим (который еще не включен по умолчанию) разбивает работу по рендерингу на части, приостанавливая и возобновляя работу, чтобы избежать блокировки браузера. Это означает, что React может вызывать жизненные циклы фазы рендеринга более одного раза перед фиксацией или может вызывать их вообще без фиксации (из-за ошибки или прерывания с более высоким приоритетом).

Жизненные циклы фазы отрисовки включают следующие методы компонентов класса:

  • constructor
  • componentWillMount (или UNSAFE_componentWillMount )
  • componentWillReceiveProps (или UNSAFE_componentWillReceiveProps )
  • componentWillUpdate (или UNSAFE_componentWillUpdate )
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • setState Updater functions (первый аргумент)

Поскольку вышеуказанные методы могут вызываться более одного раза, важно, чтобы они не содержали побочных эффектов. Игнорирование этого правила может привести к множеству проблем, включая утечку памяти и недопустимое состояние приложения. К сожалению, обнаружить эти проблемы может быть сложно, поскольку они часто могут быть недетерминированными .

Хорошо, так что если посмотреть на это глубже, я думаю, что мы предупреждаем в большем количестве случаев (например, для классов), чем мы изначально планировали. Мы рассмотрим, как заставить замолчать некоторые из них.

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

Это самоочевидное сообщение об ошибке «Невозможно обновить компонент из тела функции другого компонента».

Это означает выполнение как функцию вместо вызова компонента.

пример вроде этого:

const App = () => {  
  const fetchRecords = () => { 
    return <div>Loading..</div>;
  };  
  return fetchRecords() // and not like <FetchRecords /> unless it is functional component.
};
export default App;

@rpateld Я не думаю, что приведенный вами пример связан с этим предупреждением.

https://github.com/facebook/react/pull/18330 решит те случаи, которые мы не собирались запускать.

Я также сталкиваюсь с этой проблемой с react@experimental + react-redux + redux .
image

Код выглядит так:

import React, { Suspense } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { Redirect } from 'react-router-dom'
import PropTypes from 'prop-types'
import { userActions, cabinetActions, tokensActions } from '../../actions'
import { CircularProgress } from '@material-ui/core'
import { api } from './api'

const Cabinet = ({ resource }) => {
    resource.read()
    return <h1>cabinet</h1>
}

Cabinet.propTypes = {
    resource: PropTypes.shape({
        read: PropTypes.func
    })
}

const CabinetPage = ({
    failedToLoad,
    tokensRefreshFailed,
    logout,
    loadCabinet,
    clearTokens,
    clearCabinet
}) => {
    if (tokensRefreshFailed || failedToLoad) {
        clearTokens()
        clearCabinet()
        logout()
        return <Redirect to='/login' />
    }

    return (
        <Suspense fallback={<CircularProgress />}>
            <Cabinet resource={loadCabinet()} />
        </Suspense>
    )
}

CabinetPage.propTypes = {
    loadCabinet: PropTypes.func,
    failedToLoad: PropTypes.bool,
    tokensRefreshFailed: PropTypes.bool,
    logout: PropTypes.func,
    clearTokens: PropTypes.func,
    clearCabinet: PropTypes.func
}

const mapStateToProps = ({
    alert,
    tokens: { tokensRefreshFailed },
    cabinet: { failedToLoad }
}) => ({
    alert,
    tokensRefreshFailed,
    failedToLoad
})

const mapDispatchToProps = dispatch =>
    bindActionCreators(
        {
            cabinetLoad: cabinetActions.load,
            logout: userActions.logoutWithoutRedirect,
            loadCabinet: api.loadCabinet,
            clearCabinet: cabinetActions.clear,
            clearTokens: tokensActions.clear
        },
        dispatch
    )

export default connect(mapStateToProps, mapDispatchToProps)(CabinetPage)

loadCabinet() отправляет асинхронное действие трехфазного рендеринга, как говорят параллельные документы, и возвращает объект с read() prop.
Однако я не вижу здесь никаких родительских обновлений.

@ h0tw4t3r Вы отправляете действие Redux во время рендеринга компонента. Это не поддерживается, и именно об этом предупреждение. Лучше всего спросить экспертов React Router о том, как изящно обработать этот случай (перенаправление), не могу помочь с этой частью.

Что касается параллельного режима, обратите внимание, что Redux в настоящее время в целом несовместим с ним. Так что вы можете избежать этого, если экспериментируете с CM.

Небольшое обновление в этой теме

Вскоре мы выпустим патч React, который исправит чрезмерное срабатывание этого предупреждения для классов. Так что, если вы столкнулись с этим, подумайте о том, чтобы подождать несколько дней, а затем попробовать 16.13.1, когда он выйдет.

Если вы хотите продолжать искать причины, я надеюсь, что https://github.com/facebook/react/issues/18178#issuecomment -595846312 объяснит, как это сделать.

Мне очень странно, что та же самая логика при использовании в компоненте класса не дает никаких предупреждений, в то время как функциональная (хуки) делает:

Функциональная составляющая (хуки):

import React, { Component } from "react"
import SortableTree from "react-sortable-tree"
import "react-sortable-tree/style.css"

const data = [
  {
    title: "Windows 10",
    subtitle: "running",
    children: [
      {
        title: "Ubuntu 12",
        subtitle: "halted",
        children: [
          {
            title: "Debian",
            subtitle: "gone"
          }
        ]
      },
      {
        title: "Centos 8",
        subtitle: "hardening"
      },
      {
        title: "Suse",
        subtitle: "license"
      }
    ]
  }
]

const nodeInfo = row => console.log(row)

export default class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      searchString: "",
      searchFocusIndex: 0,
      searchFoundCount: null,
      treeData: data
    }
  }

  render() {
    const { searchString, searchFocusIndex, searchFoundCount } = this.state

    const customSearchMethod = ({ node, searchQuery }) =>
      searchQuery &&
      ((node.title &&
        node.title.toLowerCase().indexOf(searchQuery.toLowerCase()) > -1) ||
        (node.subtitle &&
          node.subtitle.toLowerCase().indexOf(searchQuery.toLowerCase()) > -1))

    const selectPrevMatch = () =>
      this.setState({
        searchFocusIndex:
          searchFocusIndex !== null
            ? (searchFoundCount + searchFocusIndex - 1) % searchFoundCount
            : searchFoundCount - 1
      })

    const selectNextMatch = () =>
      this.setState({
        searchFocusIndex:
          searchFocusIndex !== null
            ? (searchFocusIndex + 1) % searchFoundCount
            : 0
      })

    return (
      <div>
        <h2>Find the needle!</h2>
        <form
          style={{ display: "inline-block" }}
          onSubmit={event => {
            event.preventDefault()
          }}
        >
          <input
            id="find-box"
            type="text"
            placeholder="Search..."
            style={{ fontSize: "1rem" }}
            value={searchString}
            onChange={event =>
              this.setState({ searchString: event.target.value })
            }
          />
          &nbsp;
          <button
            type="button"
            disabled={!searchFoundCount}
            onClick={selectPrevMatch}
          >
            &lt;
          </button>
          &nbsp;
          <button
            type="submit"
            disabled={!searchFoundCount}
            onClick={selectNextMatch}
          >
            &gt;
          </button>
          &nbsp;
          <span>
            &nbsp;
            {searchFoundCount > 0 ? searchFocusIndex + 1 : 0}
            &nbsp;/&nbsp;
            {searchFoundCount || 0}
          </span>
        </form>

        <div style={{ height: 300 }}>
          <SortableTree
            treeData={this.state.treeData}
            onChange={treeData => this.setState({ treeData })}
            searchMethod={customSearchMethod}
            searchQuery={searchString}
            searchFocusOffset={searchFocusIndex}
            searchFinishCallback={matches =>
              this.setState({
                searchFoundCount: matches.length,
                searchFocusIndex:
                  matches.length > 0 ? searchFocusIndex % matches.length : 0
              })
            }
            generateNodeProps={row => {
              return {
                title: row.node.title,
                subtitle: (
                  <div style={{ lineHeight: "2em" }}>{row.node.subtitle}</div>
                ),
                buttons: [
                  <button
                    type="button"
                    className="btn btn-outline-success"
                    style={{
                      verticalAlign: "middle"
                    }}
                    onClick={() => nodeInfo(row)}
                  >
                    ℹ
                  </button>
                ]
              }
            }}
          />
        </div>
      </div>
    )
  }
}

image

Компонент класса:

import React from "react";
import SortableTree from "react-sortable-tree";
import "react-sortable-tree/style.css";

const data = [
  {
    title: "Windows 10",
    subtitle: "running",
    children: [
      {
        title: "Ubuntu 12",
        subtitle: "halted",
        children: [
          {
            title: "Debian",
            subtitle: "gone"
          }
        ]
      },
      {
        title: "Centos 8",
        subtitle: "hardening"
      },
      {
        title: "Suse",
        subtitle: "license"
      }
    ]
  }
];

const nodeInfo = row => console.log(row);

export default class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      searchString: "",
      searchFocusIndex: 0,
      searchFoundCount: null,
      treeData: data
    };
  }

  render() {
    const { searchString, searchFocusIndex, searchFoundCount } = this.state;

    const customSearchMethod = ({ node, searchQuery }) =>
      searchQuery &&
      ((node.title &&
        node.title.toLowerCase().indexOf(searchQuery.toLowerCase()) > -1) ||
        (node.subtitle &&
          node.subtitle.toLowerCase().indexOf(searchQuery.toLowerCase()) > -1));

    const selectPrevMatch = () =>
      this.setState({
        searchFocusIndex:
          searchFocusIndex !== null
            ? (searchFoundCount + searchFocusIndex - 1) % searchFoundCount
            : searchFoundCount - 1
      });

    const selectNextMatch = () =>
      this.setState({
        searchFocusIndex:
          searchFocusIndex !== null
            ? (searchFocusIndex + 1) % searchFoundCount
            : 0
      });

    return (
      <div>
        <h2>Find the needle!</h2>
        <form
          style={{ display: "inline-block" }}
          onSubmit={event => {
            event.preventDefault();
          }}
        >
          <input
            id="find-box"
            type="text"
            placeholder="Search..."
            style={{ fontSize: "1rem" }}
            value={searchString}
            onChange={event =>
              this.setState({ searchString: event.target.value })
            }
          />
          &nbsp;
          <button
            type="button"
            disabled={!searchFoundCount}
            onClick={selectPrevMatch}
          >
            &lt;
          </button>
          &nbsp;
          <button
            type="submit"
            disabled={!searchFoundCount}
            onClick={selectNextMatch}
          >
            &gt;
          </button>
          &nbsp;
          <span>
            &nbsp;
            {searchFoundCount > 0 ? searchFocusIndex + 1 : 0}
            &nbsp;/&nbsp;
            {searchFoundCount || 0}
          </span>
        </form>

        <div style={{ height: 300 }}>
          <SortableTree
            treeData={this.state.treeData}
            onChange={treeData => this.setState({ treeData })}
            searchMethod={customSearchMethod}
            searchQuery={searchString}
            searchFocusOffset={searchFocusIndex}
            searchFinishCallback={matches =>
              this.setState({
                searchFoundCount: matches.length,
                searchFocusIndex:
                  matches.length > 0 ? searchFocusIndex % matches.length : 0
              })
            }
            generateNodeProps={row => {
              return {
                title: row.node.title,
                subtitle: (
                  <div style={{ lineHeight: "2em" }}>{row.node.subtitle}</div>
                ),
                buttons: [
                  <button
                    type="button"
                    className="btn btn-outline-success"
                    style={{
                      verticalAlign: "middle"
                    }}
                    onClick={() => nodeInfo(row)}
                  >
                    ℹ
                  </button>
                ]
              };
            }}
          />
        </div>
      </div>
    );
  }
}

@radulle Сам по себе этот https://codesandbox.io/s/clever-taussig-9xixs. Можете ли вы подготовить пример, который мы можем попробовать?

@gaearon Я хотел создать codeandbox, но в последней версии библиотеки есть некоторые проблемы. В старых версиях ошибки не выкидываются. На данный момент кажется, что единственный способ воспроизвести это - развернуть его локально в приложении Create React :(

@radulle Какую версию можно попробовать, чтобы она работала в CodeSandbox?

@gaearon 2.6.2 работает и выдает ошибку / предупреждение с этой настройкой:
image
Итак, для той же настройки:
Функциональный компонент: ошибки / предупреждения
Компонент класса: без ошибок / предупреждений
Может я что-то упустил и они не равноценны.

Да, это один из случаев, о которых я упоминал в https://github.com/facebook/react/issues/18178#issuecomment -600369392. В этом случае мы отключим предупреждение. Само предупреждение является законным, и, как вы правильно сказали, концептуально это проблема и для классов. Однако это несоответствие не имеет смысла, поэтому мы отключим его для обоих случаев, когда оно исходит от класса (что в данном примере так и есть).

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

Чтобы проиллюстрировать это на практическом примере: в нашем приложении у нас есть своеобразная система для управления хлебными крошками.

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

    const addBreadcrumb = useAddBreadcrumb();
    addBreadcrumb(<Breadcrumb>{item.name}</Breadcrumb>, [item.name]);
    
  • У нас есть компонент, который считывает информацию из контекста и отображает все хлебные крошки. Итак, всякий раз, когда мы хотим отрендерить хлебные крошки, мы просто должны вызывать его без параметров.

Это очень практично, потому что нам не нужно поддерживать какую-либо структуру хлебных крошек: эта структура объявлена ​​в самом коде. Если мы хотим переместить маршрут в другую часть приложения, система навигации продолжит работать.

Итак, в сочетании с react-router мы можем сделать что-то вроде этого:

// Main/index.tsx
import { useAddBreadcrumb } from 'components/Breadcrumbs';
import React from 'react';
import Home from './Home';
import Movies from './Movies';

const Main = () => {
    const addBreadcrumb = useAddBreadcrumb();
    addBreadcrumb(<Breadcrumb to="/">Home</Breadcrumb>, []);

    return <Switch>
        <Route path="/movies">
            <Movies />
        </Route>
        <Route path="/" />
            <Home />
        </Route>
    </Switch>
}

// Movies/index.tsx
import { useAddBreadcrumb } from 'components/Breadcrumbs';
import React from 'react';
import Detail from './Detail';
import Master from './Master';

const Movies = ({ url }) => {
    const addBreadcrumb = useAddBreadcrumb();
    addBreadcrumb(<Breadcrumb to={url}>Movies</Breadcrumb>, [url]);

    return <Switch>
        <Route path="/:id">
            <Detail />
        </Route>
        <Route path="/" />
            <Master />
        </Route>
    </Switch>
}

// Movies/Detail/index.tsx
import Breadcrumbs, { useAddBreadcrumb } from 'components/Breadcrumbs';
import React from 'react';
import { useRouteMatch } from 'react-router-dom';

const MovieDetail = ({ url }) => {
    const addBreadcrumb = useAddBreadcrumb();
    const { params: { id } } = useRouteMatch<{ id: string; }>();
    const movie = useMovie(id);

    addBreadcrumb(
        <Breadcrumb to={url}>{movie?.name}</Breadcrumb>,
        [movie?.name, url]
    );

    return <div>
        <Breadcrumbs />
    </div>
}

Теперь, если мы перейдем к /movies/gone-with-the-wind , наши хлебные крошки будут выглядеть так:

Home > Movies > Gone with the wind

Итак, вот моя точка зрения: для того, чтобы это работало, нам нужен гарантированный порядок выполнения. В этом случае порядок выполнения очевиден: сначала Main , затем рендерится его дочерние элементы, в том числе Movies и, наконец, MovieDetail . В этом случае вызов addBreadcrumb будет выполнен в правильном порядке.

Теперь в журнале изменений указано следующее:

В том редком случае, когда вы намеренно хотите изменить состояние другого компонента в результате рендеринга, вы можете обернуть вызов setState в useEffect.

Это действительно один из редких случаев, когда мы намеренно хотим изменить состояние другого компонента. Однако, если мы сделаем это, журнал изменений предложит и обернет addBreadcrumb (который в итоге является прославленным setState ) в useEffect , порядок выполнения больше не гарантирован. Все три setStates будут выполнены после завершения рендеринга, что создаст состояние гонки и нарушит нашу систему.

Я не знаю, считается ли эта странная система антипаттерном, но для меня это имеет смысл, и я не нашел более простых альтернатив.

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

@MeLlamoPablo

Полагаться на то, что вызовы рендеринга находятся в порядке родственных или родительских / дочерних рендеров, звучит очень хрупко. На самом деле React не гарантирует этого. Дети могут (и будут) выполнять рендеринг без родителей, а также наоборот. Если дочерние элементы отображаются с задержкой (например, с разделением кода), этот код тоже сломается. Или если некоторые дочерние элементы вставляются или удаляются динамически. Не говоря уже о том, что это довольно плохо с точки зрения производительности, потому что у вас так много каскадных рендеров.

Я с пониманием отношусь к проблеме, которую вы пытаетесь решить - действительно, сегодня в React нет идиоматического решения. У нас есть некоторые идеи по этому поводу, но правильное решение должно быть совершенно другим. А пока мы настоятельно не рекомендуем использовать этот обходной путь.

@gaearon , спасибо за понимание. У меня один вопрос: гарантирован ли порядок самого первого рендера? Потому что это все, что нам нужно для правильной работы (когда мы знаем порядок хлебных крошек, нас не волнует порядок последующих отрисовок).

Мне кажется логичным, что порядок первого рендера гарантирован. Иначе как бы React узнал, что у родительского компонента есть дочерние элементы?

Насчет производительности каскадных рендеров вы абсолютно правы. Я буду искать способы улучшить нашу систему.

@MeLlamoPablo

У меня один вопрос: гарантирован ли порядок самого первого рендера? Потому что это все, что нам нужно для правильной работы (когда мы знаем порядок хлебных крошек, нас не волнует порядок последующих отрисовок).

Не сильно. Я думаю, что в основном это работает в текущей версии React, но это может измениться в будущем. Даже сегодня, в сочетании с такими функциями, как lazy и Suspense это не гарантируется.

Иначе как бы React узнал, что у родительского компонента есть дочерние элементы?

Порядок братьев и сестер не гарантируется. Что касается порядка родитель / потомок, вы правы, родители должны сначала до того,

Это хрупко.

@gaearon , еще раз спасибо. Это очень ценится.

Может быть, должно быть правило ESLint, которое предупреждает о вызове мутаторов useState внутри тела рендеринга?

Вызов setState вашего собственного компонента во время рендеринга является поддерживаемым шаблоном (хотя его следует использовать с осторожностью). Это setState на других плохих компонентах. Вы не можете обнаружить их статически.

Теоретически это можно сделать с помощью ts-eslint, при условии, что они используют правильные исходные типы React, но да, вероятно, больше усилий, чем оно того стоит.

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

Я также сталкиваюсь с этой проблемой с react@experimental + react-redux + redux .
image

Код выглядит так:

import React, { Suspense } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { Redirect } from 'react-router-dom'
import PropTypes from 'prop-types'
import { userActions, cabinetActions, tokensActions } from '../../actions'
import { CircularProgress } from '@material-ui/core'
import { api } from './api'

const Cabinet = ({ resource }) => {
  resource.read()
  return <h1>cabinet</h1>
}

Cabinet.propTypes = {
  resource: PropTypes.shape({
      read: PropTypes.func
  })
}

const CabinetPage = ({
  failedToLoad,
  tokensRefreshFailed,
  logout,
  loadCabinet,
  clearTokens,
  clearCabinet
}) => {
  if (tokensRefreshFailed || failedToLoad) {
      clearTokens()
      clearCabinet()
      logout()
      return <Redirect to='/login' />
  }

  return (
      <Suspense fallback={<CircularProgress />}>
          <Cabinet resource={loadCabinet()} />
      </Suspense>
  )
}

CabinetPage.propTypes = {
  loadCabinet: PropTypes.func,
  failedToLoad: PropTypes.bool,
  tokensRefreshFailed: PropTypes.bool,
  logout: PropTypes.func,
  clearTokens: PropTypes.func,
  clearCabinet: PropTypes.func
}

const mapStateToProps = ({
  alert,
  tokens: { tokensRefreshFailed },
  cabinet: { failedToLoad }
}) => ({
  alert,
  tokensRefreshFailed,
  failedToLoad
})

const mapDispatchToProps = dispatch =>
  bindActionCreators(
      {
          cabinetLoad: cabinetActions.load,
          logout: userActions.logoutWithoutRedirect,
          loadCabinet: api.loadCabinet,
          clearCabinet: cabinetActions.clear,
          clearTokens: tokensActions.clear
      },
      dispatch
  )

export default connect(mapStateToProps, mapDispatchToProps)(CabinetPage)

loadCabinet() отправляет асинхронное действие трехфазного рендеринга, как говорят параллельные документы, и возвращает объект с read() prop.
Однако я не вижу здесь никаких родительских обновлений.

Для всех, у кого есть эта проблема, я исправил ее, переместив диспетчеризацию действий redux в возвращенный компонент. Вот как это выглядит сейчас:

const CabinetPage = ({
    alert,
    failedToLoad,
    tokensRefreshFailed,
    logout,
    loadCabinet,
    clearTokens,
    clearCabinet,
    clearAlert
}) => (
    <Suspense fallback={<MUIBackdropProgress />}>
        {alert.message && (failedToLoad || tokensRefreshFailed) ? (
            <MUIAlertDialog
                title={alert.message}
                text={errorText}
                onClose={() => {
                    clearAlert()
                    clearCabinet()
                    clearTokens()
                    logout()
                }}
            />
        ) : (
            <Cabinet resource={loadCabinet()} />
        )}
    </Suspense>
)

Мне нравится это предупреждение, потому что оно заставляет вас выбирать правильные шаблоны проектирования. Теперь все работает безупречно!

Мы исправили случаи, когда предупреждение было чрезмерным в 16.13.1. Остальные случаи законны и требуют исправления.

@gaearon только что

@gaearon спасибо. ты только что спас мне день :-)

Хотя обновление не решило мою проблему, оно дало мне дополнительную информацию в консоли, чтобы помочь найти мою проблему. Спасибо @gaearon !

Что, если вы отправляете действие, которое заставляет другой компонент возвращать то же состояние, что и в прошлый раз? Это считается плохим? Я думаю, в этом случае произойдет короткое замыкание.

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

1) держите свое состояние настолько высоким, насколько вам нужно в вашей иерархии (не выше), а затем передавайте данные и сеттеры дочерним компонентам

2) Функциональные компоненты потрясающие! Забудьте об этом классовом шуме, сделайте весь свой компонент за одну функцию!

И теперь, когда люди следуют этим двум правилам и передают свои установщики состояния своим функциональным компонентам и вызывают их в этих компонентах ... вы вытаскиваете коврик и идете "ха", но на самом деле вы не можете сделать то, что мы вам сказали. делать".

Ничто из этого не меняет здесь технических потребностей, и я не говорю, что это изменение является неправильным или плохим ... Я просто чувствую, что существует проблема обмена сообщениями в том, что вы не сообщаете хорошие четкие правила (например, два Я только что упомянул) для программирования в этом новом мире. Если вы собираетесь изменить правила в отношении нас, я думаю, было бы полезно сделать это, сначала объяснив передовой опыт.

Итак, все, о чем я действительно прошу, это ... Я думаю, что было бы более идеальным, если бы кто-то из команды написал что-нибудь (например, статью), где они сказали бы: «Я знаю, что мы давали вам эти два правила раньше: вот новые правила », а затем добавьте ссылку на эту статью в каждое место в документации / примечаниях к выпуску, которые ссылаются на это новое предупреждение (чтобы каждый, кто гуглил« WTF is this? », мог узнать, как правильно кодировать React, в« новом Мир").

@machineghost : Я думаю, вы неправильно понимаете, о чем предупреждает сообщение.

Нет ничего плохого в передаче обратных вызовов детям, которые обновляют состояние в родителях. Это всегда было хорошо.

Проблема заключается в том, что один компонент ставит в очередь обновление в другом компоненте, _ в то время как первый компонент выполняет рендеринг_.

Другими словами, не делайте этого:

function SomeChildComponent(props) {
    props.updateSomething();
    return <div />
}

Но это нормально:

function SomeChildComponent(props) {
    // or make a callback click handler and call it in there
    return <button onClick={props.updateSomething}>Click Me</button>
}

И, как неоднократно указывал Дэн, установка очереди обновления в _same_ компоненте во время рендеринга тоже прекрасна:

function SomeChildComponent(props) {
  const [number, setNumber] = useState(0);

  if(props.someValue > 10 && number < 5) {
    // queue an update while rendering, equivalent to getDerivedStateFromProps
    setNumber(42);
  }

  return <div>{number}</div>
}

Верно, но когда вы кодируете свой компонент, вы не думаете о времени его родителя. В этом прелесть компонентов React: инкапсуляция.

Итак, я не говорю, что новое предупреждение вообще плохо, я говорю, что до того, как у нас было два правила, которым мог бы следовать любой хороший разработчик React. Теперь в условиях X эти правила выходят из окна, но только в X (где это звучит как X = «в то время как родительский компонент также обновляется»).

Я просто думаю, что нужно больше сосредоточиться на объяснении этого и на том, как избежать проблемы, вместо того, чтобы просто говорить «это проблема сейчас!».

@machineghost : ты действительно _не понимаешь, о чем я говорю.

Время родитель / ребенок не является проблемой.

Обновление состояния очереди _в других компонентах при визуализации функционального компонента_ является проблемой.

По определению это должен быть родитель / ребенок (или внук): как еще он мог передать установщик состояния?

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

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

Время. Нет. Проблема.

Компоненту функции разрешено ставить в очередь обновление состояния во время рендеринга только для себя . Как показал мой пример, это действует как эквивалент getDerivedStateFromProps .

Очередь обновления для _ любого_ другого компонента изнутри фактического тела рендеринга функционального компонента является незаконным.

Вот о чем вам говорит это предупреждение.

Я не знаю, как мне это объяснить яснее.

Вопрос не во времени: не в твоем, не в моем. Моя проблема - документация или ее отсутствие.

Но вы решили начать войну в теме с незнакомцем в Интернете, вместо того, чтобы слушать, что они говорят, и ... У меня нет желания продолжать с вами общаться.

Дело в том, что никаких правил не изменилось. Это всегда было неправильным образцом. Он только что выделен, чтобы вы знали, что ваш код содержит ошибки.

И буквально все, что вы только что сказали, не согласуется со всем, что я написал. Фактически, это почти как будто я сказал одно и то же с самого начала ... и все, что я когда-либо просил за все это время, было лучшее объяснение тех же самых правил, которые, как вы говорите, не изменились (и, конечно, они не ... то, что изменилось и "сотворили новый мир", было предупреждением).

PS Вы тоже, кажется, не понимаете иронии здесь. Если я ничего не понимаю, значит, документацию можно улучшить. Кричать на меня о том, как плохо я понимаю, только укрепляет мою позицию; это не улучшает документацию волшебным образом.

Привет, ребята, давайте немного остынем. 🙂

@markerikson Благодарю, что вы

@machineghost Спасибо за то, что выразили вашу озабоченность. Я знаю, что это раздражает, когда появляются новые предупреждения о шаблонах, которые раньше могли казаться безобидными.

Я согласен, что это предупреждение требует некоторого предварительного контекста. По сути, вам нужно было знать две вещи из эпохи классов:

  • То, что вы не должны устанавливатьState во время рендеринга. Об этом всегда предупреждали классы.

  • Это тело функционального компонента по сути то же самое, что и метод рендеринга компонента класса.

Это действительно наше упущение, что setState на другом компоненте во время тела компонента функции не предупреждал раньше. Вы могли сделать вывод, что это плохой образец из двух пунктов выше, но справедливо сказать, что никто не мог этого понять. Приносим извинения за неудобства.

Если вы считаете, что в документации есть какое-то конкретное место, где это следует упомянуть, поднимите вопрос о репозитории документации. Мы планируем переписать документацию, чтобы она была основана на хуках, чтобы мы могли помнить об этом. Спасибо!

Я ни в коем случае не хочу никого обижать и отказываюсь принимать ваши извинения;) Крючки - гениальны, а вы все гении их изобретаете. И любой инженер, который обвиняет другого инженера в том, что он не совсем точно представляет все исходы, является ... придурком.

Все, что я пытался сообщить, это то, что в настоящее время, когда я получил это предупреждение, я сделал то, что делают все: я погуглил. Затем я нашел страницу, на которой говорилось: «У нас есть новое предупреждение».

Я просто думаю, что было бы лучше (и все равно могло бы быть лучше), если бы в этом объявлении или подобных местах, которые кто-то может найти в Google, могла бы быть (может быть) ссылка на какое-то «обсуждение на более высоком уровне» «здесь» почему мы ввели эту ошибку, и вот как вы можете стать отличным разработчиком React и следовать некоторым основным рекомендациям, чтобы никогда не столкнуться с ней самостоятельно ».

Но опять же, хуки потрясающие, команда React потрясающая, и даже это новое предупреждение потрясающее (я уверен, что оно лучше, чем обнаружение ошибки, от которой оно пытается защититься). Если кто-то и должен кому-то аплогоя, так это мне за то, что я не стал лидером с этим.

Конечно, без обид. Ответ на «почему» не сложнее, чем «если один компонент запускает обновления для других компонентов во время рендеринга, становится очень трудно отслеживать поток данных вашего приложения, потому что он больше не идет сверху вниз». Так что если вы это сделаете, вы потеряете все преимущества React.

Опять же, чтобы уточнить, это не ново само по себе - классы всегда получали одно и то же предупреждение. Мы пропустили это с помощью хуков и исправляем ошибку. Я полагаю, что то, как вы это исправите, зависит от случая - поэтому мы не можем дать вам точных инструкций - но будем рады помочь, если вы поделитесь примером, с которым вы боретесь. Как правило, вы исправляете это аналогично тому, как вы исправляете предупреждение соответствующего класса, которое всегда существовало.

Надеюсь, что это немного поможет!

Я добавлю свои два цента к этому обсуждению и согласен с @machineghost, что с момента введения функциональных компонентов и хуков было много

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

Я так долго был на подножке этой функции (пытался избежать классов с Recompose и тому подобным даже до хуков), что я даже не помню этих предупреждений классов.

И хотя я ценю ваш ответ, я в основном просто надеялся на «практические правила», рекомендации, передовой опыт и т. Д. Такие вещи, как «держите свое состояние настолько высоким, насколько это необходимо, чтобы охватить все компоненты, которые его используют, но не выше». "или" передать установщики состояния дочерним компонентам, используя инверсию шаблона управления ".

Может быть, здесь их просто нет, но может быть что-то вроде «если функциональный компонент A меняет состояние, он не должен отображать дочерний компонент B, которому он передает установщик состояния (он должен вернуться сразу или что-то в этом роде), потому что тогда, если дочерний компонент отображает и меняет состояние, у вас будут проблемы ».

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

Еще раз спасибо!

Я думаю, мы подошли к тому моменту, когда эта ветка изжила себя.

Случай и точка - ComponentDidMount и Unmount, сначала нам говорят использовать функциональные компоненты, затем использовать useEffect с пустым массивом, затем нам говорят, что это бесполезно, теперь у нас есть этот беспорядок с сообщениями об ошибке.

Мне жаль, что наша документация вам не помогла, но очень сложно сказать, с какими именно проблемами вы столкнулись. К сожалению, это очень расплывчато и искажает то, что мы пытались сказать. Как игра в сломанный телефон. Если у вас есть конкретная проблема, напишите о новой проблеме и постарайтесь описать ее более подробно. Мы постараемся помочь вам, если вы можете быть более конкретными.

И хотя я ценю ваш ответ, в основном я просто надеялся на "практические правила", рекомендации, передовой опыт и т. Д.

Эмпирическим правилом всегда было «не вызывать побочных эффектов во время рендеринга». Думайте о рендеринге как о чистом вычислении. Побочные эффекты находятся в другом месте (методы жизненного цикла в классах или useEffect в функциональных компонентах). Больше нечего.

«если функциональный компонент A изменяет состояние, он не должен отображать дочерний компонент B, которому он передает установщик состояния (он должен возвращать сразу или что-то в этом роде), потому что тогда, если дочерний компонент отображает и изменяет состояние, у вас будут проблемы».

Думаю, здесь все еще есть какое-то недоразумение. Совершенно нормально передавать установщики состояния дочернему компоненту. Всегда было хорошо, и так будет всегда.

Проблема в их вызове при рендеринге . Как правило, в этом нет необходимости. Мне сложно без конкретного примера догадаться, зачем вы это делаете. Так что помочь трудно.

Общая тема обоих этих разговоров - то, что мы ходим по кругу. Обсуждение превратилось в мета, и вместо того, чтобы говорить о конкретных случаях, мы обсуждаем нечеткие общие положения. Вероятно, мы неправильно понимаем друг друга, но отсутствие конкретных примеров не позволяет разрешить это недоразумение.

По этой причине я собираюсь заблокировать эту тему. Я очень ценю вклад каждого здесь, и мы будем рады услышать от вас больше, если вы изо всех сил пытаетесь исправить это предупреждение. Чтобы получить помощь, укажите проблему с минимальным воспроизводимым примером. Затем мы сможем обсудить вашу конкретную проблему и помочь найти решение. Это будет более продуктивно для всех участников, а также позволит избежать отправки электронных писем десяткам людей, которые уже прокомментировали эту ветку и подписались. Спасибо!

В качестве небольшого обновления (извините за пинг) я слышал, что https://github.com/final-form/react-final-form/issues/751#issuecomment -606212893 исправил кучу этих ошибок для людей, которые использовали это библиотека.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги