React: Fehler: zu schwer zu beheben "Eine Komponente kann nicht aus dem Funktionskörper einer anderen Komponente aktualisiert werden."

Erstellt am 28. Feb. 2020  ·  109Kommentare  ·  Quelle: facebook/react

# Hinweis: React 16.13.1 hat einige Fälle behoben, in denen dies übertrieben war. Wenn das Upgrade von React und ReactDOM auf 16.13.1 die Warnung nicht behebt, lesen Sie dies: https://github.com/facebook/react/issues/18178#issuecomment -595846312

Reaktionsversion:

16.13.0

Schritte zum Reproduzieren

  1. Baue eine Zeitmaschine.
  2. Gehe ins Jahr 2017.
  3. Erstellen Sie eine riesige Anwendung von 10.000 Codezeilen.
  4. Holen Sie sich 80 (!) Abhängigkeiten in der Datei package.json, einschließlich derer, die nicht mehr gepflegt werden.
  5. Aktualisieren Sie React am 27. Februar 2020 auf die neueste Version.
  6. Erhalten Sie Tonnen von Fehlern, von denen Sie nicht wissen, wie Sie sie beheben können.
  7. Sagen Sie Ihrem Kunden, dass Fixes unbekannte Zeit in Anspruch nehmen werden und $$$ + Tage oder Wochen Untersuchung kosten werden, oder wir werden für immer mit der veralteten Version von React und verwandten Bibliotheken stecken bleiben, was mehr $$$ kostet aber später.

Im Ernst, das Geschäft, für das ich arbeite, interessiert sich überhaupt nicht dafür. Offensichtlich hatte ich es nie passieren lassen, dass solche Warnungen erscheinen würden, wenn ich sie früher bekommen würde. Derzeit ist es unglaublich schwer, die Fehler zu beheben, da ich sie in vielen verschiedenen Fällen und mit einem riesigen Stack-Trace bekomme. Ich habe versucht, mindestens einen der auftretenden Fehler zu beheben, und es hat schon viel Zeit gedauert. Ich habe versucht, einige der verwendeten Bibliotheken zu debuggen, hatte aber kein Glück.

Nur ein Beispiel:

image

Dort können wir die Verwendung eines veralteten React-Routers, eines veralteten Redux-Connects (den ich an die Projektquelle stellen musste, um Fehler der veralteten componentWillReceiveProps Methode zu beheben), einige durch Recompose erstellte HOCs usw. feststellen ist nicht nur ein einfacher virtueller DOM-Baum, in dem ich durch die von mir entwickelten Komponenten gehen und nach setState String suchen kann, um den Fehler zu beheben, das ist viel komplizierter.

Bitte stellen Sie eine "UNSICHER"-Option ein, um diesen Fehler zu deaktivieren oder eine einfachere Möglichkeit bereitzustellen, um herauszufinden, wo der Fehler ausgelöst wird 🙏

Discussion

Hilfreichster Kommentar

An die zukünftigen Kommentatoren. Ich verstehe, dass es frustrierend ist, eine neue Warnung zu sehen. Aber es weist auf legitime Probleme hin, die wahrscheinlich Fehler in Ihrem Code verursachen . Wir würden uns sehr freuen, wenn Sie auf Sarkasmus und Ärger verzichten könnten.

Wenn Sie in den Stack-Traces nicht verstehen können, woher es kommt, posten Sie bitte Screenshots oder erstellen Sie reproduzierende Sandboxen und wir werden versuchen, Ihnen zu helfen. Die meisten davon stammen wahrscheinlich aus einigen Bibliotheken, daher ist es am produktivsten, diese Fälle zu reduzieren und dann Probleme mit diesen Bibliotheken zu melden.

Danke euch allen.

Alle 109 Kommentare

Ich wünschte, wir hätten diese Warnung früher hinzugefügt. Es tut mir leid, dass wir es nicht getan haben. Es war ein Versehen bei der Einführung von Hooks. Ich glaube, dass dies durch neueren Code verursacht werden muss, der Hooks verwendet, da es die gleiche Warnung für Klassen bereits viel früher gab, sodass jeder frühere Code diese Warnung bereits gesehen hätte.

Beachten Sie, dass dies in zukünftigen Versionen von React wahrscheinlich schwer fehlschlagen wird. Absichtlich oder nicht (wir hatten viele Fehler mit diesem Muster). Unabhängig davon könnten Sie also bei einer alten Version stecken bleiben. Wenn es nicht möglich ist, das Problem zu beheben, würde ich empfehlen, an eine ältere Version von React anzuheften.

Trotzdem möchten wir Ihnen helfen und es so einfach wie möglich machen, diese zu beheben, einschließlich der Suche nach Hilfe von Bibliotheksautoren, um behobene Versionen zu veröffentlichen.

Wenn Sie den kleinen > Pfeil in der Chrome-Konsole erweitern, sollten Sie auch einen zusätzlichen Stack-Trace sehen (zusätzlich zum Komponentenstapel in Ihrem Screenshot). Kannst du das auch posten? Das sollte Ihnen die genaue Callsite anzeigen, die einen Nebeneffekt beim Rendern verursacht.

Bei mir erscheint dies, wenn ich formik verwende

image

@sebmarkbage danke für die Antwort. Der Stacktrace, der nach dem Klicken auf > erscheint, ist lächerlich. Es enthält über 200 Artikel!

Ich wollte es dort einfügen oder einen Link zu Pastebin geben, habe aber eine andere Richtung versucht. Ich ging durch die Github-Probleme einiger verwendeter Bibliotheken und fand heraus, dass einer der Verdächtigen redux-form ist: https://github.com/redux-form/redux-form/issues/4619. Ich hoffe, dass dies die einzige Bibliothek ist, die die Fehler verursacht, und ich werde auf eine Fehlerbehebung warten, bevor ich React aktualisiere.

Trotzdem möchte ich darum bitten, dieses Problem nicht zu schließen und schlage anderen Entwicklern vor, hier Bibliotheken zu erwähnen, die ebenfalls diese Fehler verursachen.

@RodolfoSilva bist du dir sicher, dass es von formik verursacht wird? Wenn ja, können Sie dort bitte ein Issue erstellen und hier einen Link dazu posten? Ich werde am Anfang meiner ersten Nachricht eine Liste solcher Probleme erstellen, wenn die Liste mehr als ein Element enthält.

Dies muss wirklich so schnell wie möglich angegangen werden. Es macht ein Upgrade unmöglich. Die Fehlerverfolgung ist so gut wie unmöglich.

Hm. Ich frage mich, ob es helfen würde, zu beschreiben, nach welcher Zeile in der Fehlermeldung zu suchen ist.

In diesem Fall ist die erste zu suchende Zeile die Zeile nach dispatchAction . Das sollte die Sache sein, die in React ruft.

@RodolfoSilva kannst du die Quelle von FormItemInput.js posten, wenn du sie teilen kannst? Das scheint in Zeile 71 Dispatch oder setState aufzurufen.

Ich denke, es ist unbedingt erforderlich, dass diese Fehlermeldung so geändert wird, dass sie genau enthält, welche Codezeile den Fehler verursacht. Es ist fast unmöglich festzustellen, ob lokaler Code oder Bibliothekscode das Problem verursacht. Externe Bibliotheken Bibliotheken wie React-Redux und React-Router sind höchstwahrscheinlich die Schuldigen, aber es ist unmöglich, dies leicht zu bestimmen.

Ich bin mir ziemlich sicher, dass React-Redux hier nicht schuld ist, stimmte aber zu, dass die Fehlermeldung und der Komponentenstapel es schwierig machen, zu wissen, was tatsächlich vor sich geht.

Ich habe das gleiche Problem mit Redux-Form!

Ich habe das gleiche Problem und sehe, dass die Warnung angezeigt wird, wenn ich zum ersten Mal in mein Redux-Feld schreibe oder wenn ich alles lösche

Ich habe das Problem auch, das ist meine Komponente:

`const [price, setPrice] = useState(0);

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

In diesem Fall benachrichtigt meine CardContainer-Komponente die übergeordnete Komponente, wenn der Preis aktualisiert wird und die übergeordnete Komponente den neuen Prinzen rendert.
Ich denke, React warnt mich, dass ich versuche, den Zustand einer Komponente mithilfe der Funktion einer anderen Komponente zu aktualisieren.
Ich bin neu in React, daher bin ich mir nicht sicher, ob dies ein React-Muster oder tatsächlich ein Fehler ist.

Wenn Sie einen Vorschlag zur Lösung dieser Warnung haben, wäre ich Ihnen dankbar

@l0gicgate

Ich denke, es ist unbedingt erforderlich, dass diese Fehlermeldung so geändert wird, dass sie genau enthält, welche Codezeile den Fehler verursacht.

Es gibt Grenzen für das, was wir in JavaScript tun können. Aber alle Informationen befinden sich im Stack, den Sie im Browser sehen . Alles, was Sie tun müssen, ist, die Zeilen in React zu überspringen.

Um den JavaScript-Stack anzuzeigen, müssen Sie auf einen kleinen Pfeil neben der Fehlermeldung klicken .

Sehen Sie sich zum Beispiel diesen Screenshot von vorhin an:

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

Ich weiß, dass es ein bisschen nervig ist, sich durch den Stack zu wühlen, aber es sollte nicht so schwer sein, die ersten Frames zu überspringen. Der nächste Frame ist die Quelle des Problems. In diesem Fall scheint es etwas in der Formik-Bibliothek zu sein. Sie können also ein Problem damit einreichen.

@martinezwilmer Dieses Beispiel ist zu klein, um zu helfen. Erstellen Sie bitte eine Sandbox.

An die zukünftigen Kommentatoren. Ich verstehe, dass es frustrierend ist, eine neue Warnung zu sehen. Aber es weist auf legitime Probleme hin, die wahrscheinlich Fehler in Ihrem Code verursachen . Wir würden uns sehr freuen, wenn Sie auf Sarkasmus und Ärger verzichten könnten.

Wenn Sie in den Stack-Traces nicht verstehen können, woher es kommt, posten Sie bitte Screenshots oder erstellen Sie reproduzierende Sandboxen und wir werden versuchen, Ihnen zu helfen. Die meisten davon stammen wahrscheinlich aus einigen Bibliotheken, daher ist es am produktivsten, diese Fälle zu reduzieren und dann Probleme mit diesen Bibliotheken zu melden.

Danke euch allen.

Die genaue Zeile, die von der Warnung betroffen ist, ist hier schwer zu finden:

@gaearon hast du nochmal einen

image

Die genaue Zeile, die von der Warnung betroffen ist, ist hier schwer zu finden:

Was macht es schwer? Wie ich oben angemerkt habe, ist es die erste Zeile, die nicht "reagieren" auf der rechten Seite sagt. In diesem Fall ist es connectAdvanced von Redux. Bitte melden Sie ein Problem in React Redux, damit die Betreuer die Möglichkeit haben, sich das anzusehen.

Wie ich schon sagte, wäre ich _sehr_ überrascht, wenn das, was hier passiert, ein Problem mit React-Redux wäre.

Allerdings bin ich mir auch nicht sicher, was diese Nachricht überhaupt auslöst. Ich verstehe halbwegs, was die Fehlermeldung sagt, aber welche Art von App-Codemuster wäre tatsächlich ein Beispiel für dieses Verhalten?

Ich bin kürzlich darauf gestoßen und der Fix wickelte setState Handleraufruf-Sites in useEffect , etwa so: https://github.com/airbnb/lunar/commit/db08613d46ea21089ead3e7b5cfff995f15c69a7#diff onChange und onSubmit verwenden setState höher in der Kette).

@martinezwilmer Wo wird onPriceUpdated aufgerufen? Vielleicht versuchen Sie es in useEffect einzupacken?

Das gleiche Problem scheint bei urql

Wir verwenden use-subscription + Wonka (für Streams), um unsere Updates zu orchestrieren, ein Update kann jedoch synchron eintreffen. Hier haben wir bereits die todos abgerufen. Wenn wir also auf die Schaltfläche Open klicken, sollte das Ergebnis sofort erscheinen, dies scheint jedoch den folgenden Fehler auszulösen.

image

In unserem Fall ist dies beabsichtigt, wir können nicht einfach fetching: true für ein Sync-Ergebnis anzeigen, das würde zu sprunghaften Schnittstellen führen.

Dies taucht jetzt immer häufiger in Bibliotheken von Drittanbietern auf: urql , Apollo .

Ich bin darauf gestoßen und habe mehrere Stunden lang angenommen, dass das Problem in meinem Code liegt. Der komprimierte Stacktrace zeigt auf meine Komponenten, und es ist nicht ungewöhnlich für mich, Bibliotheken von Drittanbietern im erweiterten Stacktrace zu sehen, wenn ich _did_ explizit einen Fehler auslöste. Aus meiner (wenn auch begrenzten) Recherche zu dieser speziellen Warnung geht hervor, dass die meisten Entwickler dieses Problem nicht selbst verursachen, sondern vom Code abhängen, der dies tut. Normalerweise ist es eine gute Praxis, davon auszugehen, dass es sich nicht um einen Upstream-Bug handelt, aber wenn es sich um einen Upstream-Bug handelt, ist es ziemlich frustrierend, Zeit damit zu verschwenden, ein Problem in Ihrem Code aufzuspüren, das nicht existiert. Gibt es irgendetwas, das React tun kann, um einem durchschnittlichen Benutzer zu helfen, festzustellen, ob es Code war, den er geschrieben hat, oder Code, von dem er abhängt, der das Problem verursacht hat?

Eine Sache, die ich in der Apollo-Ausgabe anmerke, ist:

Der Stacktrace der Warnung zeigt die Komponente, die die Änderungen initialisiert hat, nicht die, die durch diese Änderungen neu gerendert wird

Wenn das richtig ist, kann React hier weitere Informationen geben? Vielleicht teilen Sie uns sowohl die initiierende Komponente als auch die Komponenten mit, die sie aktualisiert hat?

Wie @hugo bin ich beim Testen einer neuen Ionic-Anwendung auf Folgendes gestoßen:

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

Tatsächlich liegt die Ursache in der Mitte und am unteren Rand des Stack-Trace.

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)

Dieses Problem war das nächste, was ich zu diesem Problem finden konnte.

Wir haben ein bestimmtes Muster gefunden, das dieses Problem mit mobx in https://github.com/mobxjs/mobx-react/issues/846 verursacht

@sebmarkbage Ich kann dieses Problem nicht mehr reproduzieren. Wir haben einige Bibliotheken aktualisiert und die Probleme behoben.

@jgoux wir scheinen vor dem gleichen Problem zu stehen @Clovis ^^ Spotted

Ich bekam diesen Fehler, nachdem die Aktualisierung auf react 16.13.0 reagiert hatte. Das Problem ist ziemlich klar, da eine meiner Komponenten eine andere aktualisiert, nachdem eine bestimmte Aktion ausgeführt wurde. Ich bin mir jedoch nicht sicher, warum dies eine Warnung auslösen würde. Irgendein Vorschlag, wie man das umgehen kann?

@gaearon danke für die Details zum Debuggen vom Stack, ich persönlich hätte nicht herausfinden können, woher dieser Fehler kommt, wenn Sie dieses Beispiel nicht gegeben hätten. 🙏

Ich bin mir jedoch nicht sicher, ob mein Problem damit zusammenhängt. Ich versuche, den Status meiner Formularkomponente zu aktualisieren, aber jedes Mal, wenn ich versuche, einen onChange-Handler hinzuzufügen, erhalte ich diesen Fehler. Wohlgemerkt, ich verwende "react-jsonschema-form" und habe die Form-Komponente importiert, und ich verwende ihre onChange-Eigenschaft, um den Zustand zu aktualisieren.

Für mich ist dies das Muster, das das Problem verursacht.

image

Es kann einen Weg geben, dies zu umgehen. Aber das Konsolenprotokoll hat mich direkt zu Zeile 385 geführt

Ich bin neu zu reagieren, aber ich hatte Code wie diesen:

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;

Was ergab: Cannot update a component from inside the function body of a different component.

Alles, was ich tun musste, ist eine Pfeilfunktion hinzuzufügen, um MobileNavOpen in MobileNav wie folgt zu setzen:

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;

Und das behebt das Problem, hoffe das hilft jemandem!

Ich bin neu zu reagieren, aber ich hatte Code wie diesen:

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;

Was ergab: Cannot update a component from inside the function body of a different component.

Alles, was ich tun musste, ist eine Pfeilfunktion hinzuzufügen, um MobileNavOpen in MobileNav wie folgt zu setzen:

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;

Und das behebt das Problem, hoffe das hilft jemandem!

Ihr Beispiel ist tatsächlich einer der ersten Fehler, mit denen Menschen reagieren. Ich bin mir nicht sicher, ob es genau das gleiche Thema ist, das hier diskutiert wird. Ihre Zeile hier: onClick={setMobileNavOpen(false) ruft die Funktion während des Button-Tenders auf, nicht beim Klicken. Aus diesem Grund wird es behoben, wenn es in eine Pfeilfunktion eingeschlossen wird.

Ich bin auf dieses Problem mit React Router gestoßen, bei dem ich eine Redux-Aktion senden musste, bevor <Redirect> den Benutzer an einen anderen Ort schickte. Das Problem schien zu sein, dass die Weiterleitung erfolgte, bevor der Versand abgeschlossen war. Ich habe das Problem behoben, indem ich meine Aktion versprochen habe.

Vor:

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

Nach:

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

Ich denke, es wäre erforderlich, dass die Fehlermeldung Namen sowohl für die Komponente enthält, die gerade rendert, als auch für die Komponente, deren setState aufgerufen wird. Möchte jemand eine PR dafür schicken?

Ich denke, es wäre erforderlich, dass die Fehlermeldung Namen sowohl für die Komponente enthält, die gerade rendert, als auch für die Komponente, deren setState aufgerufen wird. Möchte jemand eine PR dafür schicken?

Das schaue ich mir gerne an. Irgendwelche Hinweise, die ich beachten sollte?

@samcooke98 Ich habe dafür eine PR geöffnet https://github.com/facebook/react/pull/18316

Wie andere darauf hingewiesen haben, können Sie auf dieses Problem stoßen, wenn Sie ein Abonnement in einem Hook wie bei Apollo haben und versuchen, einen Store beim Empfangen von Daten zu aktualisieren. Einfache Lösung ist die Verwendung von useEffect zB

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

Wir hatten dieses Problem in Hyper, verwenden jedoch keine Hooks und konnten im Renderaufruf aus der Aufrufliste nichts finden. Aber es gab einen Call in UNSAFE_componentWillReceiveProps im Stack. Das Aktualisieren mit componentDidUpdate das Problem für uns behoben https://github.com/zeit/hyper/pull/4382
Habe es hier gepostet, falls es jemandem helfen könnte

Auch hier gab es einen UNSAFE_componentWillMount-Aufruf, der das Problem durch Ändern/Entfernen behoben hat

aber wir verwenden keine Hooks und konnten nichts im Render-Aufruf aus der Aufrufliste finden

Das klingt seltsam. Ich bin mir nicht sicher, wie Sie diese Warnung dann bekommen. Es wird nur ausgelöst, wenn setState zu einer Funktionskomponente gehört. Wie sieht dein Stapel aus?

aber wir verwenden keine Hooks und konnten nichts im Render-Aufruf aus der Aufrufliste finden

Das klingt seltsam. Ich bin mir nicht sicher, wie Sie diese Warnung dann bekommen. Es wird nur ausgelöst, wenn setState zu einer Funktionskomponente gehört. Wie sieht dein Stapel aus?

Klassenkomponenten mit Redux und Funktionen, die Plugins auf die Komponenten anwenden. Vielleicht wird es deshalb als Funktionskomponente gezählt? Aber warum behebt es dann das Aktualisieren der Lifecycle-Hooks?

Ich bin mir nicht sicher. Können Sie versuchen, ein isoliertes Beispiel in einer Sandbox zu erstellen? Ich habe die Vermutung, dass Sie etwas anderes Unerwartetes tun könnten.

Ich bin mir nicht sicher, ob ich es in einem isolierten Beispiel replizieren könnte. Es fiel mir schwer, die Ursache zu finden, und hatte gerade den Lifecycle-Hook aktualisiert, da er sich im Stack-Trace befand und zur Aktualisierung anstand. Das hat das Problem irgendwie behoben.
Sie können sich das Repo zum Zeitpunkt des Problems hier ansehen

Kann es daran liegen, dass sich zu diesem Zeitpunkt sowohl UNSAFE_componentWillReceiveProps als auch componentDidUpdate in der Komponente befanden (unsichere wurde irrtümlicherweise dringelassen)?

Ich erhalte auch diese Warnung, und der Stack-Trace zeigt auf den Hook setScriptLoaded (siehe unten für meinen benutzerdefinierten Hook). Es scheint also, dass React trotz der Verwendung von useEffect immer noch eine Warnung ausgibt, wenn der setState Handler in anderen asynchronen Callbacks verschachtelt ist (in meinem Fall ist er in einem setTimeout verschachtelt). load Ereignishandler)? Ich benutze Hooks zum ersten Mal, daher würde ich mich über jeden Rat freuen. Dankeschön!

/**
 * 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

Kann es daran liegen, dass sich zu diesem Zeitpunkt sowohl UNSAFE_componentWillReceiveProps als auch componentDidUpdate in der Komponente befanden (unsichere wurde versehentlich drin gelassen)?

Ich glaube nicht, dass irgendwelche Lifecycle-Methoden hier überhaupt relevant sind. Deshalb sage ich, dass in Ihrem Beispiel etwas seltsam ist.

@suhanw Bitte geben Sie ein vollständiges Beispiel in CodeSandbox an. Ich sehe kein Problem mit Ihrem Code, das diese Warnung verursachen sollte.

@LabhanshAgrawal Können Sie bitte Ihren vollständigen Stapel posten? Ich denke, was passieren kann, ist, dass Ihr UNSAFE_componentWillReceiveProps (was dem Rendern entspricht) setState für eine andere Komponente aufruft.

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 freut sich über die schnelle Antwort. Ich werde einen Weg finden, ein Beispiel in CodeSandbox zu erstellen (es ist eine Herausforderung), aber in der Zwischenzeit ist dies mein vollständiger Stack-Trace

Die Zeile, die auf meinen benutzerdefinierten Hook zeigt, ist diese: https://gist.github.com/suhanw/bcc2688bba131df8301dae073977654f#file -stack-trace-L144

Es wäre großartig, wenn Sie einen Blick darauf werfen und mich wissen lassen könnten, ob sich vor / nach meinem benutzerdefinierten Hook etwas im Stack-Trace befindet, das ich weiter untersuchen sollte. Vielen Dank!

@LabhanshAgrawal In Ihrem Stack ruft UNSAFE_componentWillReceiveProps einige fitResize die eine Redux-Aktion auslöst, die wiederum eine Reihe von Komponenten aktualisiert. Daher das Problem. Also ja, die Änderung in componentDidUpdate funktioniert.

@suhanw In Ihrem Stack erscheint etwas namens ModuleImpressionTracker , um während eines Konstruktors eine Redux-Aktion auszulösen . Konstruktoren sollten keine Nebenwirkungen haben. Ich denke, das ist die Ursache des Problems, nicht dein Hook.

Ich verstehe, also nehme ich davon ab, dass UNSAFE_componentWillReceiveProps als Rendering gezählt wird, componentDidUpdate jedoch nicht.
Können Sie ein wenig klarstellen, dass es sich um ein Problem mit funktionalen Komponenten und Hooks handelt, die setState ausführen, konnte dies aus der obigen Diskussion nicht klar hervorgehen.
Entschuldigung, wenn die Frage etwas albern ist oder meine Aussage falsch ist, ich bin ein bisschen neu darin.

@LabhanshAgrawal

Können Sie ein wenig klarstellen, dass es sich um ein Problem mit funktionalen Komponenten und Hooks handelt, die setState ausführen, konnte dies aus der obigen Diskussion nicht klar hervorgehen.

Ehrlich gesagt bin ich mir wegen des ganzen Redux-Zeugs in der Mitte nicht sicher. Es ist ziemlich verwirrend aufgrund der Implementierung von React Redux. Es wäre schön, wenn jemand eine klare Reproduktion bekommen könnte, die nur React beinhaltet, aber Klassenkomponenten verwendet.

Es sieht immer noch so aus, als ob viele Stack-Traces eingefügt werden und nicht viele tatsächliche Reproduktionen des tatsächlichen Problems, unabhängig vom Typ.

Ich denke, der für urql ist für @gaearon gedacht, also passiert

  • Wir haben unser <Todos /> gemountet, das die Daten abholt und rendert
  • Der vorherige hat einen Stream eingerichtet, der mit dem urqlClient
  • Wir rendern unser zweites <Todos /> Dies erzeugt dieselbe Kombination aus Abfrage und Variablen, sodass das Ergebnis für <Todos /> des ersten Schritts aktualisiert wird.
  • use-subscription wird für beide ausgelöst.

Repro - Drücken Sie oben auf "Öffnen", wenn die erste Abfrage gerendert wird.

Wir könnten Updates in die Warteschlange stellen, aber solange wir keine top-down Renderings mit Kontext durchführen, können wir dieses Problem nicht umgehen, nehme ich an? Theoretisch ist dies das beabsichtigte Verhalten für diesen Fall. Neugierig, wie Relay dies umgeht.

BEARBEITEN: Wir haben möglicherweise eine Lösung für den Fall von urql gefunden, indem wir nicht alle Abonnenten dazu gebracht haben, während der getCurrentValue eines use-subscription zu aktualisieren

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

OK, wenn wir uns das genauer ansehen, warnen wir meiner Meinung nach in mehr Fällen (zB für Klassen) als ursprünglich beabsichtigt. Wir werden versuchen, einige davon zum Schweigen zu bringen.

@JoviDeCroock Dieses Beispiel ist nicht sehr hilfreich, da ich keine Ahnung habe, was urql tut. :-) Wenn Sie Feedback zu einem Muster wünschen, könnten Sie eine Sandbox vorbereiten, die dieses Muster isoliert demonstriert?

@JoviDeCroock Ja, getCurrentValue soll definitiv keine Nebenwirkungen haben. Wir dachten, dass dieser Name ziemlich eindeutig ist.

Ich hatte ein Problem, bei dem ich diese Warnung beim Senden einer Redux-Aktion innerhalb des Root-Bereichs eines benutzerdefinierten Hooks erhielt.

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

Ich habe dies behoben, indem ich den Versand in ein useEffect verpackt habe.

@Glinkis Das klingt

@gaearon ja, das Problem , das wir versuchen zu lösen damit besser erklärte hier und scheint durchaus üblich.

@Glinkis Das klingt

Ich muss meinen Redux-Status mit den IDs für meine Route synchron halten, daher versende ich dieses Update, wenn sich meine Route ändert.

Ich weiß, dass dies möglicherweise suboptimal ist, aber ich habe keine andere Möglichkeit gefunden, auf die Routingdaten in meinen Selektoren zuzugreifen.

@Glinkis Woher kommen die

@JoviDeCroock Ich denke, unsere neueste Empfehlung für dieses Muster ist ein benutzerdefinierter geplanter Garbage-Collection-Pass.

@Glinkis Woher kommen die

Bin mir nicht sicher ob das in diese Diskussion gehört, aber das ist mein Haken.

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

Später verwende ich diesen Zustand für Selektoren wie:

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

@Glinkis Ja, vielleicht useRouteMatch in der übergeordneten Komponente zu lesen, die ID als Requisite zu übergeben und dann Requisiten im Selektor zu lesen, wie Sie es normalerweise tun würden. (Ich weiß nicht wirklich, wie das heutzutage in Redux gemacht wird, aber es muss einen Weg geben.)

Ich verstehe, also nehme ich davon ab, dass UNSAFE_componentWillReceiveProps als Rendering gezählt wird, componentDidUpdate jedoch nicht.

@LabhanshAgrawal Das ist richtig. Einige Hintergrunderklärungen hier :

Konzeptionell arbeitet React in zwei Phasen:

  • Die Renderphase bestimmt, welche Änderungen zB am DOM vorgenommen werden müssen. Während dieser Phase ruft React render und vergleicht dann das Ergebnis mit dem vorherigen Rendering.
  • In der Commit- Phase wendet React alle Änderungen an. (Im Fall von React DOM ist dies der Fall, wenn React DOM-Knoten einfügt, aktualisiert und entfernt.) React ruft in dieser Phase auch Lebenszyklen wie componentDidMount und componentDidUpdate auf.

Die Commit-Phase ist normalerweise sehr schnell, aber das Rendern kann langsam sein. Aus diesem Grund bricht der kommende gleichzeitige Modus (der standardmäßig noch nicht aktiviert ist) die Rendering-Arbeit in Teile, pausiert und setzt die Arbeit fort, um eine Blockierung des Browsers zu vermeiden. Das bedeutet, dass React die Lebenszyklen der Render-Phase mehr als einmal vor dem Commit aufrufen kann, oder es kann sie ohne Commit aufrufen (aufgrund eines Fehlers oder einer Unterbrechung mit höherer Priorität).

Die Lebenszyklen der Renderphase umfassen die folgenden Klassenkomponentenmethoden:

  • constructor
  • componentWillMount (oder UNSAFE_componentWillMount )
  • componentWillReceiveProps (oder UNSAFE_componentWillReceiveProps )
  • componentWillUpdate (oder UNSAFE_componentWillUpdate )
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • setState Updater-Funktionen (das erste Argument)

Da die oben genannten Methoden mehr als einmal aufgerufen werden können, ist es wichtig, dass sie keine Nebenwirkungen enthalten. Das Ignorieren dieser Regel kann zu einer Vielzahl von Problemen führen, einschließlich Speicherverlusten und ungültigem Anwendungsstatus. Leider kann es schwierig sein, diese Probleme zu erkennen, da sie oft nicht deterministisch sind .

OK, wenn wir uns das genauer ansehen, warnen wir meiner Meinung nach in mehr Fällen (zB für Klassen) als ursprünglich beabsichtigt. Wir werden versuchen, einige davon zum Schweigen zu bringen.

Ich denke, auch wenn es mehr als beabsichtigt ist, ist es meiner Meinung nach eine gute Sache, da es hilft, unsere Projekte mit Klassen zu verbessern.

Diese selbsterklärende Fehlermeldung "Kann eine Komponente nicht aus dem Funktionskörper einer anderen Komponente aktualisieren"

Das bedeutet, dass sie als Funktion ausgeführt wird, anstatt eine Komponente aufzurufen.

Beispiel wie dieses:

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

@rpateld Ich glaube nicht, dass das von Ihnen

https://github.com/facebook/react/pull/18330 wird die Fälle lösen, die wir nicht mit dem Schießen beginnen wollten.

Ich stehe auch vor diesem Problem mit react@experimental + react-redux + redux .
image

Code sieht so aus:

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() sendet eine asynchrone Aktion des dreiphasigen Renderings, wie es in gleichzeitigen Dokumenten heißt, und gibt ein Objekt mit read() prop zurück.
Allerdings kann ich hier keine Eltern-Updates sehen.

@h0tw4t3r Sie Renderns einer Komponente. Dies wird nicht unterstützt, und darum geht es in der Warnung. Am besten fragen Sie die Experten von React Router, wie Sie mit diesem Fall (Umleitung) elegant umgehen können, kann bei diesem Teil nicht helfen.

Bezüglich des Concurrent-Modus beachten Sie bitte, dass Redux derzeit im Allgemeinen nicht damit kompatibel ist. Sie sollten es also vermeiden, wenn Sie mit CM experimentieren.

Kleines Update zu diesem Thread

Wir werden bald einen Patch von React veröffentlichen, der das Überfeuern dieser Warnung für Klassen behebt. Wenn Sie so etwas erleben, sollten Sie ein paar Tage warten und dann 16.13.1 ausprobieren, wenn es herauskommt.

Wenn Sie weiter nach den Ursachen suchen möchten, hoffe ich, dass https://github.com/facebook/react/issues/18178#issuecomment -595846312 erklärt, wie.

Ich finde es sehr seltsam, dass die gleiche Logik bei Verwendung in einer Klassenkomponente keine Warnungen ausgibt, während funktional (Hooks) dies tut:

Funktionskomponente (Haken):

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

Klassenkomponente:

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 Dies ist an sich kein sehr hilfreiches Beispiel. Ich habe versucht, es in CodeSandbox einzufügen, aber es funktioniert nicht: https://codesandbox.io/s/clever-taussig-9xixs. Können Sie ein Beispiel vorbereiten, das wir ausprobieren können?

@gaearon Ich wollte eine Codesandbox erstellen, aber die neueste Version der Bibliothek hat einige Probleme damit. In den alten Versionen wird kein Fehler ausgegeben. Im Moment scheint es, dass die einzige Möglichkeit, es zu reproduzieren, darin besteht, es lokal in der Create React App hochzufahren :(

@radulle Welche Version kann ich ausprobieren, die mit CodeSandbox funktioniert?

@gaearon 2.6.2 funktioniert und gibt den Fehler / die Warnung mit diesem Setup aus:
image
Also für das gleiche Setup:
Funktionskomponente: Fehler/Warnungen
Klassenkomponente: keine Fehler/Warnungen
Vielleicht habe ich etwas verpasst und sie sind nicht gleichwertig.

Ja, dies ist einer der Fälle, auf die ich in https://github.com/facebook/react/issues/18178#issuecomment -600369392 verwiesen habe. In diesem Fall schalten wir die Warnung stumm. Die Warnung selbst ist legitim und, wie Sie richtig sagen, konzeptionell auch im Unterricht ein Problem. Die Diskrepanz macht jedoch keinen Sinn, daher werden wir sie vorerst für beide Fälle stummschalten, wenn sie von einer Klasse stammt (was in diesem Beispiel der Fall ist).

Ich glaube, es gibt einen legitimen Anwendungsfall für das Auslösen von Statusaktualisierungen aus der Renderfunktion und nicht aus einem Effekt, und zwar um die Ausführungsreihenfolge beizubehalten.

Zur Veranschaulichung an einem praktischen Beispiel: In unserer App haben wir ein eigentümliches System zur Verwaltung von Breadcrumbs.

  • Wir haben einen globalen Kontext, der alle Breadcrumbs enthält
  • Wir haben einen Hook, mit dem wir diesem Kontext Breadcrumbs hinzufügen können, wie folgt:

    const addBreadcrumb = useAddBreadcrumb();
    addBreadcrumb(<Breadcrumb>{item.name}</Breadcrumb>, [item.name]);
    
  • Wir haben eine Komponente, die die Informationen aus dem Kontext liest und alle Breadcrumbs rendert. Wenn wir also die Breadcrumbs rendern möchten, müssen wir sie einfach ohne Parameter aufrufen.

Dies ist sehr praktisch, da wir keine Breadcrumb-Struktur pflegen müssen: Diese Struktur wird im Code selbst deklariert. Wenn wir eine Route in einen anderen Teil der Anwendung verschieben möchten, funktioniert das Breadcrumb-System weiter.

In Kombination mit react-router können wir also etwa Folgendes tun:

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

Wenn wir nun zu /movies/gone-with-the-wind , sehen unsere Breadcrumbs wie folgt aus:

Home > Movies > Gone with the wind

Nun, hier ist mein Punkt: Damit das funktioniert, müssen wir die Ausführungsreihenfolge garantieren. In diesem Fall ist die Ausführungsreihenfolge offensichtlich: Zuerst rendert Main , dann rendert es seine untergeordneten Elemente, die Movies , und schließlich MovieDetail . In diesem Fall wird der addBreadcrumb Aufruf in der richtigen Reihenfolge ausgeführt.

Im Changelog steht nun folgendes:

In dem seltenen Fall, dass Sie den Zustand einer anderen Komponente als Ergebnis des Renderns absichtlich ändern möchten, können Sie den setState-Aufruf in useEffect umschließen.

Dies ist tatsächlich einer der seltenen Fälle, in denen wir absichtlich den Zustand einer anderen Komponente ändern möchten. Wenn wir dies jedoch im Changelog vorschlagen und addBreadcrumb (was am Ende ein verherrlichtes setState ) in useEffect umschließen, ist die Ausführungsreihenfolge nicht mehr garantiert. Alle drei setStates werden ausgeführt, nachdem das Rendering abgeschlossen ist, was eine Race-Bedingung erzeugt und unser System unterbricht.

Ich weiß nicht, ob dieses eigenartige System als Anti-Pattern angesehen wird oder nicht, aber für mich macht es Sinn, und ich habe keine einfacheren Alternativen gefunden.

Abschließend begrüße ich diese neue Warnung, aber ich denke, dass die optimale Lösung für uns darin besteht, eine Möglichkeit zu haben, sie zu unterdrücken. Vielleicht würde ein zweiter optionaler Parameter für setState den Zweck erfüllen.

@MeLlamoPablo

Sich darauf zu verlassen, dass die Renderaufrufe in der Reihenfolge der Geschwister- oder Eltern-/Kind-Renderreihenfolge liegen, klingt sehr fragil. React garantiert dies nicht wirklich. Kinder können (und werden) ohne ihre Eltern neu rendern und umgekehrt. Wenn Kinder mit einer Verzögerung gerendert werden (zB Code-Splitting), würde dieser Code ebenfalls brechen. Oder wenn einige Kinder dynamisch eingefügt oder gelöscht werden. Ganz zu schweigen davon, dass dies in Bezug auf die Leistung ziemlich schlecht ist, da Sie so viele kaskadierende Renderings haben.

Ich habe Verständnis für das Problem, das Sie zu lösen versuchen – tatsächlich gibt es heute keine idiomatische Lösung in React. Wir haben einige Ideen dazu, aber eine richtige Lösung müsste ziemlich anders sein. In der Zwischenzeit raten wir dringend von dieser Problemumgehung ab.

@gaearon , danke für deinen Einblick. Ich habe eine Frage: Ist die Reihenfolge des allerersten Renderings garantiert? Denn das ist alles, was wir brauchen, um richtig zu funktionieren (sobald wir die Reihenfolge der Breadcrumbs kennen, kümmern wir uns nicht um die Reihenfolge der nachfolgenden Renderings).

Es erscheint mir logisch, dass die Reihenfolge des ersten Renderns garantiert ist. Wie würde React sonst wissen, dass eine übergeordnete Komponente untergeordnete Elemente hat?

Was die Leistung von kaskadierenden Renderings angeht, haben Sie absolut Recht. Ich werde nach Möglichkeiten suchen, unser System zu verbessern.

@MeLlamoPablo

Ich habe eine Frage: Ist die Reihenfolge des allerersten Renderings garantiert? Denn das ist alles, was wir brauchen, um richtig zu funktionieren (sobald wir die Reihenfolge der Breadcrumbs kennen, kümmern wir uns nicht um die Reihenfolge der nachfolgenden Renderings).

Nicht stark. Ich denke, es funktioniert meistens in der aktuellen Version von React, aber das kann sich in Zukunft ändern. Auch heute noch ist dies in Kombination mit Funktionen wie lazy und Suspense nicht garantiert.

Wie würde React sonst wissen, dass eine übergeordnete Komponente untergeordnete Elemente hat?

Geschwisterreihenfolge ist nicht garantiert. Für Eltern / Kind - Ordnung, du hast Recht Eltern zuerst zu machen bevor es zu einem untergeordneten Element gelangt oder sogar nachdem es das erste untergeordnete Element gerendert hat, jedoch vor dem zweiten. Auch hier verlieren Sie Garantien noch schneller, sobald Sie Funktionen wie Code-Splitting hinzufügen.

Das ist zerbrechlich.

@gaearon , danke nochmal. Dies wird sehr geschätzt.

Vielleicht sollte es eine ESLint-Regel geben, die davor warnt, useState Mutatoren in einem Render-Body aufzurufen?

Das Aufrufen von setState Ihrer eigenen Komponente während des Renderns ist ein unterstütztes Muster (obwohl es sparsam verwendet werden sollte). Es ist setState für andere Komponenten, die fehlerhaft sind. Diese können Sie statisch nicht erkennen.

Theoretisch könnte dies mit ts-eslint erfolgen, vorausgesetzt, sie verwenden die richtigen Upstream-React-Typen, aber ja, wahrscheinlich mehr Aufwand, als es wert ist.

Ich glaube nicht, dass es ohne eine Art von Effekt-Tracking möglich wäre. Sobald Sie eine Funktion in der Mitte haben, verlieren Sie die Informationen.

Ich stehe auch vor diesem Problem mit react@experimental + react-redux + redux .
image

Code sieht so aus:

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() sendet eine asynchrone Aktion des dreiphasigen Renderings, wie es in gleichzeitigen Dokumenten heißt, und gibt ein Objekt mit read() prop zurück.
Allerdings kann ich hier keine Eltern-Updates sehen.

Für alle anderen, die dieses Problem haben, habe ich es behoben, indem ich die Verteilung von Redux-Aktionen auf eine zurückgegebene Komponente verschoben habe. So sieht es jetzt aus:

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

Ich mag diese Warnung, weil sie Sie dazu zwingt, die richtigen Designmuster zu wählen. Jetzt funktioniert alles einwandfrei!

Wir haben die Fälle behoben, in denen die Warnung in 16.13.1 zu stark war. Die restlichen Fälle sind legitim und müssen behoben werden.

@gaearon hat gerade ein Upgrade durchgeführt und der Fehler ist verschwunden! Danke Jungs für eure Arbeit!

@gaearon danke. du hast mir gerade den tag gerettet :-)

Das Upgrade hat mein Problem zwar nicht behoben, aber es gab mir mehr Informationen in der Konsole, um mein Problem zu finden. Danke @gaearon !

Was passiert, wenn Sie eine Aktion auslösen, die bewirkt, dass die andere Komponente denselben Status wie beim letzten Mal zurückgibt? Ist das schlecht? Ich würde denken, dass es in diesem Fall einen Kurzschluss gibt.

Kann ich das nur sagen, obwohl ich die Logik hinter dieser Warnung völlig verstehe ... es fühlt sich fast wie ein Verrat an dem an, was das React-Team der Community erzählt hat, weil es sich anfühlt, als hätte das Team diese wichtigen Wahrheiten über das Programmieren gelehrt? Reagieren:

1) Halten Sie Ihren Status in Ihrer Hierarchie so hoch, wie Sie es benötigen (nicht höher), und übergeben Sie dann Daten und Setter an untergeordnete Komponenten

2) Funktionskomponenten sind genial! Vergessen Sie diesen Klassenlärm, machen Sie Ihre gesamte Komponente in einer Funktion!

Und jetzt, wenn die Leute diese beiden Regeln befolgen und ihre State Setter an ihre Funktionskomponenten weitergeben und sie in diesen Komponenten aufrufen ... machen".

Nichts davon ändert etwas an den technischen Anforderungen hier, und ich sage nicht, dass diese Änderung falsch oder schlecht ist ... Ich habe nur das Gefühl, dass es ein Nachrichtenproblem gibt, da Sie keine guten klaren Regeln kommunizieren (wie die beiden Ich habe es gerade erwähnt) für die Codierung in dieser neuen Welt. Wenn Sie die Regeln für uns ändern möchten, wäre es meiner Meinung nach hilfreich, dies zu tun, indem Sie zunächst die bewährten Verfahren erläutern.

Also, alles, was ich wirklich frage, ist ... Ich denke, es wäre idealer, wenn jemand aus dem Team etwas (wie einen Artikel) schreiben würde, in dem er sagt: "Ich weiß, wir haben dir diese beiden Regeln schon einmal gegeben: Hier sind die neue Regeln" und fügen Sie dann an jeder Stelle in den Dokumenten/Versionshinweisen, die auf diese neue Warnung verweisen, einen Link zu diesem Artikel hinzu (damit jeder, der "WTF ist das?" googelt, in der "neuen" Welt").

@machineghost : Ich denke, Sie missverstehen, wovor die Meldung warnt.

Es ist nichts falsch daran, Rückrufe an Kinder weiterzugeben, die den Status der Eltern aktualisieren. Das war immer gut.

Das Problem tritt auf, wenn eine Komponente eine Aktualisierung in eine andere Komponente einreiht, _während die erste Komponente rendert_.

Mit anderen Worten, tun Sie dies nicht:

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

Aber das ist in Ordnung:

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

Und wie Dan mehrmals betont hat, ist es auch in Ordnung, ein Update in der _same_ Komponente während des Renderns in die Warteschlange zu stellen:

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

Richtig, aber wenn Sie Ihre Komponente codieren, denken Sie nicht an das Timing der übergeordneten Komponente. Das ist ein Teil der Schönheit der React-Komponenten: die Kapselung.

Also noch einmal, ich sage nicht, dass die neue Warnung überhaupt schlecht ist, ich sage, bevor wir zwei Regeln hatten, die jeder gute React-Entwickler befolgen konnte. Unter X-Bedingungen gehen diese Regeln jetzt aus dem Fenster, aber nur unter X (wo es wie X = "während die Elternkomponente auch aktualisiert wird") klingt.

Ich denke nur, dass man sich mehr darauf konzentrieren muss, dies zu erklären und das Problem zu vermeiden, anstatt nur zu sagen "Das ist jetzt ein Problem!".

@machineghost : Du verstehst _wirklich_ nicht, was ich hier sage.

Das Timing von Eltern/Kind ist nicht das Problem.

Das Problem besteht darin, Statusaktualisierungen _in anderen Komponenten während des Renderns einer Funktionskomponente_ in die Warteschlange zu stellen.

Per Definition muss es ein Elternteil/Kind (oder Enkelkind) sein: Wie sonst hätte es den Staatssetzer weitergeben können?

Ich sage nicht, dass dies nicht auch bei anderen Komponentenbeziehungen ein Problem sein kann, aber ich spreche speziell von Leuten, die Best Practices von React befolgen, Zustandssetzer übergeben und dann diese Warnung erhalten.

Das ist alles, worüber ich rede, und ich sage nur: "Es könnte besser erklärt werden, mit mehr Fokus darauf, wie man gut codiert, anstatt nur 'Hier ist ein neuer Fehler und das bedeutet er'".

Zeitliche Koordinierung. Nicht. Problem.

Eine Funktionskomponente darf eine Statusaktualisierung während des Renderns nur für sich selbst in die Warteschlange stellen. Wie mein Beispiel gezeigt hat, entspricht dies getDerivedStateFromProps .

Das Einreihen eines Updates für _jede_ andere Komponente aus dem eigentlichen Rendering-Body einer Funktionskomponente heraus ist unzulässig.

Das sagt Ihnen diese Warnung.

Ich weiß nicht, wie ich das klarer erklären kann.

Das Timing ist nicht das Problem: nicht deines, nicht meins. Mein Problem ist die Dokumentation oder deren Fehlen.

Aber Sie haben sich entschieden, in einem Themen-Thread mit einem Fremden im Internet einen Krieg zu beginnen, anstatt zuzuhören, was sie sagen, und ... Ich habe keine Lust, mich weiterhin mit Ihnen zu beschäftigen.

Der Punkt ist, dass sich keine Regeln geändert haben. Dies war immer ein falsches Muster. Es wird gerade hervorgehoben, damit Sie wissen, dass Ihr Code fehlerhaft ist.

Und buchstäblich nichts, was Sie gerade gesagt haben, stimmt mit allem überein, was ich geschrieben habe. Tatsächlich ist es fast so, als hätte ich von Anfang an genau das Gleiche gesagt ... und alles, was ich die ganze Zeit verlangt habe, war eine bessere Erklärung derselben Regeln, die Sie sagen, sie haben sich nicht geändert (und natürlich sie haben nicht ... was sich geändert und "eine neue Welt geschaffen" war die Warnung).

PS Sie scheinen die Ironie hier auch nicht zu erkennen. Wenn ich etwas nicht verstehe, spricht dies dafür, dass die Dokumentation verbessert werden könnte. Mich anzuschreien, wie schlecht ich die Dinge verstehe, stärkt nur meine Position; es verbessert nicht auf magische Weise die Dokumentation.

Hallo Leute, lasst uns etwas abkühlen. 🙂

@markerikson Ich einspringen, aber ich denke, diese Diskussion wird etwas zu hitzig.

@machineghost Vielen Dank, dass Sie Ihre Bedenken geäußert haben. Ich weiß, dass es ärgerlich ist, wenn neue Warnungen für Muster erscheinen, die zuvor vielleicht harmlos erschienen sind.

Ich stimme zu, dass diese Warnung einen vorherigen Kontext erfordert. Im Wesentlichen musste man aus der Klassenzeit zwei Dinge wissen:

  • Dass Sie während des Renderns nicht den Status festlegen sollten. Die Klassen haben immer davor gewarnt.

  • Dieser Funktionskomponentenkörper ist im Wesentlichen dasselbe wie die Rendermethode der Klassenkomponente.

Es ist in der Tat unser Versäumnis, dass setState auf eine andere Komponente während der Funktionskomponentenkörper vorher nicht gewarnt hat. Sie könnten aus den beiden obigen Punkten schließen, dass es sich um ein schlechtes Muster handelt, aber es ist fair zu sagen, dass man es nicht erkennen konnte. Wir entschuldigen uns für die Unannehmlichkeiten.

Wenn Sie der Meinung sind, dass dies an einer bestimmten Stelle in den Dokumenten erwähnt werden sollte, wenden Sie sich bitte an das Dokumenten-Repository. Wir planen eine Neufassung der Dokumente, die auf Hooks basieren sollen, damit wir das im Hinterkopf behalten können. Vielen Dank!

Ich möchte niemandem ein schlechtes Gewissen machen, und ich weigere mich, Ihre Entschuldigung anzunehmen ;) Hooks sind Genies, und ihr alle seid Genies, wenn ihr sie erfunden habt. Und jeder Ingenieur, der einem anderen Ingenieur vorwirft, dass er sich nicht jedes Ergebnis perfekt vorstellt, ist ... ein Idiot.

Alles, was ich zu kommunizieren versucht habe, ist, dass ich, als ich diese Warnung erhielt, das tat, was alle tun: Ich habe es gegoogelt. Dann fand ich eine Seite, auf der stand: "Wir haben diese neue Warnung".

Ich denke nur, es wäre besser gewesen (und könnte immer noch besser sein), wenn es einen Link in dieser Ankündigung oder ähnlichen Stellen, die jemand durch Googeln finden könnte, zu einer "höheren Diskussion" von "hier" geben könnte (könnte). warum wir diesen Fehler eingeführt haben und wie Sie ein großartiger React-Entwickler sein und einige grundlegende Richtlinien befolgen können, um nie selbst darauf zu stoßen."

Aber noch einmal, Hooks sind großartig, das React-Team ist großartig und sogar diese neue Warnung ist großartig (ich bin mir sicher, dass es die Hölle macht, den Fehler zu entdecken, vor dem es zu schützen versucht). Wenn jemand jemandem eine Entschuldigung schuldet, dann bin ich es, dass ich damit nicht leite.

Sicher, keine harten Gefühle. Die Antwort auf das „Warum“ ist nicht komplexer als „Wenn eine Komponente während des Renderns Updates auf anderen Komponenten auslöst, wird es sehr schwierig, den Datenfluss Ihrer App zu verfolgen, da er nicht mehr von oben nach unten verläuft“. Wenn Sie das tun, verschenken Sie die Vorteile von React.

Nochmals, um es klarzustellen, dies ist an sich nicht neu – Klassen hatten immer die gleiche Warnung. Wir haben es mit Hooks verpasst und korrigieren den Fehler. Ich nehme an, dass die Art und Weise, wie Sie das Problem beheben, vom Fall abhängt – daher können wir Ihnen keine genauen Anweisungen geben – aber Sie helfen gerne, wenn Sie ein Beispiel nennen, mit dem Sie zu kämpfen haben. Im Allgemeinen würden Sie es auf ähnliche Weise beheben, wie Sie die entsprechende Klassenwarnung beheben würden, die immer existierte.

Hoffe das hilft etwas!

Ich werde meine zwei Cent zu dieser Diskussion hinzufügen und stimme @machineghost zu, dass es seit der Einführung von Funktionskomponenten und Hooks viel Verwirrung gegeben hat. Die Community sucht nach Führung durch das Reaktionsteam, aber Dinge, die früher einfach waren, werden verworren und es scheint an Kommunikation und klaren Beispielen zu mangeln.

Fall und Punkt sind ComponentDidMount und Unmount, zuerst wird uns gesagt, Funktionskomponenten zu verwenden, dann useEffect mit einem leeren Array, dann wird uns gesagt, dass das nicht gut ist, jetzt haben wir diese Fehlermeldung. Ich weiß nicht, ich schätze all die harte Arbeit, die in die Reaktion gesteckt wird, aber wir müssen wirklich mehr Anstrengungen in die Dokumentation und Best Practices stecken.

Ich bin schon so lange auf dem Funktionszug (versuchte Klassen mit Recompose und dergleichen sogar vor Hooks zu vermeiden), dass ich mich nicht einmal an diese Klassenwarnungen erinnere.

Und obwohl ich Ihre Antwort schätze, habe ich hauptsächlich auf "Faustregeln", Richtlinien, Best Practices usw. gehofft. Dinge wie "Halten Sie Ihren Zustand so hoch wie nötig, um alle Komponenten abzudecken, die ihn verwenden, aber nicht höher". " oder "übergeben Sie Zustandssetzer unter Verwendung der Umkehrung des Kontrollmusters an untergeordnete Komponenten".

Vielleicht gibt es hier einfach keine, aber vielleicht so etwas wie "Wenn die funktionale Komponente A den Zustand ändert, sollte sie die untergeordnete Komponente B nicht rendern, an die sie einen Zustandssetzer übergibt (sie sollte dann sofort zurückkehren oder so), denn dann, wenn die untergeordnete Komponente rendert und ändert den Status, dass Sie Probleme haben werden".

Oder vielleicht ist es schon spät am Sonntag, ich habe den ganzen Tag an einem persönlichen Projekt gearbeitet und mein Gehirn ist einfach zu gebraten und macht aus etwas Einfachem etwas Schweres. Wie auch immer, ich werde mich zurückmelden, wenn ich weitere Vorschläge für Richtlinien habe, aber ansonsten möchte ich sicherlich nicht, dass jemand anderes seinen Sonntagabend damit verbringt.

Danke aber nochmal!

Ich denke, wir kommen an den Punkt, an dem dieser Thread seine Nützlichkeit überlebt hat.

Fall und Punkt sind ComponentDidMount und Unmount, zuerst wird uns gesagt, Funktionskomponenten zu verwenden, dann useEffect mit einem leeren Array, dann wird uns gesagt, dass das nicht gut ist, jetzt haben wir diese Fehlermeldung.

Es tut mir leid, dass Ihnen unsere Dokumentation nicht geholfen hat, aber es ist sehr schwer zu sagen, auf welche spezifischen Probleme Sie gestoßen sind. Dies ist leider sehr vage und eine Verzerrung dessen, was wir zu sagen versucht haben. Wie ein Spiel mit kaputtem Telefon. Wenn Sie ein bestimmtes Problem haben, melden Sie bitte ein neues Problem und versuchen Sie es genauer zu beschreiben. Wir werden versuchen, Ihnen zu helfen, wenn Sie genauere Angaben machen können.

Und obwohl ich Ihre Antwort schätze, habe ich hauptsächlich auf "Faustregeln", Richtlinien, Best Practices usw. gehofft

Die Faustregel lautete immer: „Beim Rendern keine Nebenwirkungen ausführen“. Stellen Sie sich das Rendering als reine Berechnung vor. Nebenwirkungen gehen an eine andere Stelle (Lifecycle-Methoden in Klassen oder useEffect in Funktionskomponenten). Mehr ist nicht drin.

"Wenn die funktionale Komponente A den Zustand ändert, sollte sie die untergeordnete Komponente B nicht rendern, an die sie einen Zustandssetzer übergibt (sie sollte dann richtig zurückgeben oder so), denn dann haben Sie Probleme, wenn die untergeordnete Komponente rendert und den Zustand ändert."

Ich glaube, hier gibt es noch einige Missverständnisse. Es ist völlig in Ordnung, Zustandssetzer an eine untergeordnete Komponente zu übergeben. War immer gut und wird es immer sein.

Das Problem besteht darin , sie während des Renderns aufzurufen . Das sollte generell völlig überflüssig sein. Es ist schwer für mich zu erraten, warum Sie es ohne ein konkretes Beispiel tun. Es ist also schwer zu helfen.

Das allgemeine Thema in diesen beiden Gesprächen ist, dass wir uns im Kreis drehen. Die Diskussion hat sich zu einer Meta-Diskussion entwickelt, und anstatt über spezifische Fälle zu sprechen, diskutieren wir vage Allgemeingültigkeiten. Es ist wahrscheinlich, dass wir uns missverstehen, aber das Fehlen konkreter Beispiele macht es unmöglich, dieses Missverständnis aufzulösen.

Aus diesem Grund werde ich diesen Thread sperren. Ich schätze die Beiträge aller hier sehr, und wir würden gerne mehr von Ihnen hören, wenn Sie Schwierigkeiten haben, diese Warnung zu beheben. Sie erhalten Hilfe, indem Sie ein Problem mit einem minimal reproduzierbaren Beispiel melden. Dann können wir Ihr konkretes Problem besprechen und bei der Ideenfindung helfen. Dies wird für alle Beteiligten produktiver sein und es wird auch vermieden, E-Mails an Dutzende von Personen zu senden, die diesen Thread bereits kommentiert und am Ende abonniert haben. Dankeschön!

Als kleines Update (sorry, dass ich alle gepingt habe), habe ich gehört, dass https://github.com/final-form/react-final-form/issues/751#issuecomment -606212893 eine Reihe dieser Fehler für Leute behoben hat, die das verwendet haben Bücherei.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen