Die Komponente wird nicht erneut bereitgestellt, wenn sich die Routenparameter ändern

97

Ich arbeite an einer React-Anwendung mit React-Router. Ich habe eine Projektseite mit einer URL wie folgt:

myapplication.com/project/unique-project-id

Wenn die Projektkomponente geladen wird, löse ich eine Datenanforderung für dieses Projekt aus dem Ereignis componentDidMount aus. Ich stoße jetzt auf ein Problem, bei dem sich nur die ID so ändert, wenn ich direkt zwischen zwei Projekten wechsle ...

myapplication.com/project/982378632
myapplication.com/project/782387223
myapplication.com/project/198731289

componentDidMount wird nicht erneut ausgelöst, sodass die Daten nicht aktualisiert werden. Gibt es ein anderes Lebenszyklusereignis, mit dem ich meine Datenanforderung auslösen sollte, oder eine andere Strategie, um dieses Problem anzugehen?

Konstelliert
quelle
1
Könnten Sie Ihren Komponentencode veröffentlichen?
Pierre Criulanscy

Antworten:

81

Wenn der Link mit nur einem anderen Parameter auf dieselbe Route verweist, wird er nicht erneut bereitgestellt, sondern erhält stattdessen neue Requisiten. Sie können also die componentWillReceiveProps(newProps)Funktion verwenden und suchen newProps.params.projectId.

Wenn Sie versuchen, Daten zu laden, würde ich empfehlen, die Daten abzurufen, bevor der Router die Übereinstimmung mit statischen Methoden für die Komponente verarbeitet. Schauen Sie sich dieses Beispiel an. Reagiere auf Router Mega Demo . Auf diese Weise lädt die Komponente die Daten und aktualisiert sie automatisch, wenn sich die Routenparameter ändern, ohne sich darauf verlassen zu müssen componentWillReceiveProps.

Brad B.
quelle
2
Danke dir. Ich konnte dies mit componentWillReceiveProps zum Laufen bringen. Ich verstehe den Datenfluss der React Router Mega Demo immer noch nicht wirklich. Ich sehe die statischen fetchData, die für einige Komponenten definiert sind. Wie werden diese Statiken von der Router-Client-Seite aufgerufen?
Sternbilder
1
Gute Frage. Überprüfen Sie die Datei client.js und die Datei server.js. Während des Runcycle des Routers rufen sie die Datei fetchData.js auf und übergeben sie statean sie. Es ist zunächst etwas verwirrend, wird dann aber etwas klarer.
Brad B
12
Zu Ihrer Information: "componentWillReceiveProps" gilt als Vermächtnis
Idan Dagan
4
Sie sollten ComponentDidUpdate mit React 16 verwenden, da componentWillReceiveProps veraltet ist
Pato Loco
1
Dies funktioniert, hat jedoch das Problem, dass Sie componentWillReceiveProps () oder componentDidUpdate () hinzufügen müssen, um das erneute Rendern für jede Komponente zu handhaben, die Daten lädt. Stellen Sie sich beispielsweise eine Seite mit mehreren Komponenten vor, die Daten basierend auf demselben Pfadparameter laden.
Juha Syrjälä
96

Wenn Sie bei Routenänderungen eine erneute Bereitstellung von Komponenten benötigen, können Sie einen eindeutigen Schlüssel an das Schlüsselattribut Ihrer Komponente übergeben (der Schlüssel ist Ihrem Pfad / Ihrer Route zugeordnet). Jedes Mal, wenn sich die Route ändert, ändert sich auch der Schlüssel, wodurch die React-Komponente zum Aufheben / erneuten Bereitstellen ausgelöst wird. Ich habe die Idee von dieser Antwort

wei
quelle
2
Brillant! Vielen Dank für diese einfache und elegante Antwort
Z_z_Z
Nur fragen, wird dies nicht die Leistung der App beeinträchtigen?
Alpit Anand
1
@AlpitAnand das ist eine breite Frage. Das erneute Mounten ist definitiv langsamer als das erneute Rendern, da es mehr Lebenszyklusmethoden auslöst. Hier antworte ich nur, wie Remount durchgeführt werden kann, wenn sich die Route ändert.
Wei
Aber es ist keine große Wirkung? Was raten Sie uns, können wir es sicher und ohne große Wirkung verwenden?
Alpit Anand
@AlpitAnand Ihre Frage ist zu weit gefasst und anwendungsspezifisch. Ja, für einige Anwendungen wäre dies ein Leistungseinbruch. In diesem Fall sollten Sie beobachten, wie sich Requisiten ändern, und nur die Elemente aktualisieren, die aktualisiert werden sollen. Für die meisten ist das oben genannte eine gute Lösung
Chris
82

Hier ist meine Antwort, ähnlich wie bei einigen der oben genannten, jedoch mit Code.

<Route path="/page/:pageid" render={(props) => (
  <Page key={props.match.params.pageid} {...props} />)
} />
Haltepunkt25
quelle
4
Der Schlüssel spielt hier die größte Rolle, da das Bild, zu dem Sie auf einer Profilseite einen Link haben, beim Klicken einfach auf dieselbe Route umleitet, jedoch mit unterschiedlichen Parametern, ABER die Komponente wird nicht aktualisiert, da React keinen Unterschied feststellen kann. weil es einen SCHLÜSSEL geben muss, um das alte Ergebnis zu vergleichen
Georgi Peev
14

Sie müssen sich darüber im Klaren sein, dass eine Routenänderung keine Seitenaktualisierung verursacht. Sie müssen dies selbst tun.

import theThingsYouNeed from './whereYouFindThem'

export default class Project extends React.Component {

    componentWillMount() {

        this.state = {
            id: this.props.router.params.id
        }

        // fire action to update redux project store
        this.props.dispatch(fetchProject(this.props.router.params.id))
    }

    componentDidUpdate(prevProps, prevState) {
         /**
         * this is the initial render
         * without a previous prop change
         */
        if(prevProps == undefined) {
            return false
        }

        /**
         * new Project in town ?
         */
        if (this.state.id != this.props.router.params.id) {
           this.props.dispatch(fetchProject(this.props.router.params.id))
           this.setState({id: this.props.router.params.id})
        }

    }

    render() { <Project .../> }
}
Chickenchilli
quelle
Danke, diese Lösung funktioniert bei mir. Aber meine Eslint-Einstellung beschwert sich darüber: github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/… Was denkst du darüber?
Konekoya
Ja, das ist eigentlich keine bewährte Methode. Tut mir leid, nachdem Ihr Reduzierer das "fetchProject" gesendet hat, das Ihre Requisiten sowieso aktualisiert. Ich habe dies nur verwendet, um meinen Standpunkt zu verdeutlichen, ohne Redux
einzusetzen
Hallo Chickenchilli, ich stecke eigentlich immer noch darin fest ... hast du andere Vorschläge oder empfohlene Tutorials? Oder ich bleibe vorerst bei Ihrer Lösung. Danke für Ihre Hilfe!
Konekoya
12

Wenn Sie haben:

<Route
   render={(props) => <Component {...props} />}
   path="/project/:projectId/"
/>

In React 16.8 und höher können Sie mit Hooks Folgendes tun:

import React, { useEffect } from "react";
const Component = (props) => {
  useEffect(() => {
    props.fetchResource();
  }, [props.match.params.projectId]);

  return (<div>Layout</div>);
}
export default Component;

Dort lösen Sie nur fetchResourcebei props.match.params.idÄnderungen einen neuen Anruf aus .

Alfonso Pérez
quelle
4
Dies ist die bessere Antwort, da die meisten anderen Antworten auf der jetzt veralteten und unsicheren Antwort beruhencomponentWillReceiveProps
pjb
7

Basierend auf den Antworten von @wei, @ Breakpoint25 und @PaulusLimma habe ich diese Ersatzkomponente für die <Route>. Dadurch wird die Seite erneut bereitgestellt, wenn sich die URL ändert. Dadurch werden alle Komponenten auf der Seite erstellt und erneut bereitgestellt und nicht nur neu gerendert. Alle componentDidMount()und alle anderen Start-Hooks werden auch bei der URL-Änderung ausgeführt.

Die Idee ist, die Komponenteneigenschaft zu ändern, keywenn sich die URL ändert, und dies zwingt React, die Komponente erneut bereitzustellen.

Sie können es beispielsweise als Ersatz für <Route>Folgendes verwenden:

<Router>
  <Switch>
    <RemountingRoute path="/item/:id" exact={true} component={ItemPage} />
    <RemountingRoute path="/stuff/:id" exact={true} component={StuffPage} />
  </Switch>
</Router>

Die <RemountingRoute>Komponente ist wie folgt definiert:

export const RemountingRoute = (props) => {
  const {component, ...other} = props
  const Component = component
  return (
    <Route {...other} render={p => <Component key={p.location.pathname + p.location.search}
                                              history={p.history}
                                              location={p.location}
                                              match={p.match} />}
    />)
}

RemountingRoute.propsType = {
  component: PropTypes.object.isRequired
}

Dies wurde mit React-Router 4.3 getestet.

Juha Syrjälä
quelle
5

Die Antwort von @ wei funktioniert gut, aber in einigen Situationen finde ich es besser, keinen Schlüssel für die innere Komponente festzulegen, sondern sich selbst zu routen. Wenn der Pfad zur Komponente statisch ist, Sie jedoch möchten, dass die Komponente jedes Mal neu bereitgestellt wird, wenn der Benutzer zu ihr navigiert (möglicherweise um einen API-Aufruf bei componentDidMount () durchzuführen), ist es praktisch, location.pathname auf den Schlüssel der Route zu setzen. Damit wird die Route und der gesamte Inhalt erneut bereitgestellt, wenn sich der Standort ändert.

const MainContent = ({location}) => (
    <Switch>
        <Route exact path='/projects' component={Tasks} key={location.pathname}/>
        <Route exact path='/tasks' component={Projects} key={location.pathname}/>
    </Switch>
);

export default withRouter(MainContent)
Paulus Limma
quelle
5

So habe ich das Problem gelöst:

Diese Methode ruft das einzelne Element von der API ab:

loadConstruction( id ) {
    axios.get('/construction/' + id)
      .then( construction => {
        this.setState({ construction: construction.data })
      })
      .catch( error => {
        console.log('error: ', error);
      })
  }

Ich rufe diese Methode von componentDidMount auf. Diese Methode wird nur einmal aufgerufen, wenn ich diese Route zum ersten Mal lade:

componentDidMount() {   
    const id = this.props.match.params.id;
    this.loadConstruction( id )
  }

Und von componentWillReceiveProps, die seit dem zweiten Laden aufgerufen werden, laden wir dieselbe Route, aber unterschiedliche ID, und ich rufe die erste Methode auf, um den Status neu zu laden, und dann lädt die Komponente das neue Element.

componentWillReceiveProps(nextProps) {
    if (nextProps.match.params.id !== this.props.match.params.id) {
      const id = nextProps.match.params.id
      this.loadConstruction( id );
    }
  }
Oscar Jovanny
quelle
Dies funktioniert, hat jedoch das Problem, dass Sie componentWillReceiveProps () hinzufügen müssen, um das erneute Rendern für jede Komponente zu handhaben, die Daten lädt.
Juha Syrjälä
Verwenden Sie componentDidUpdate (prevProps). componentWillUpdate () componentWillReceiveProps () sind veraltet.
Mirek Michalak
0

Wenn Sie Class Component verwenden, können Sie componentDidUpdate verwenden

componentDidMount() {
    const { projectId } = this.props.match.params
    this.GetProject(id); // Get the project when Component gets Mounted
 }

componentDidUpdate(prevProps, prevState) {
    const { projectId } = this.props.match.params

    if (prevState.projetct) { //Ensuring This is not the first call to the server
      if(projectId !== prevProps.match.params.projectId ) {
        this.GetProject(projectId); // Get the new Project when project id Change on Url
      }
    }
 }
Angel Mas
quelle
componentDidUpdate ist jetzt veraltet. Es wäre gut, wenn Sie auch eine Alternative vorschlagen könnten.
Sahil
Bist du dir da sicher? Das ComponentDidUpdate ist nicht und wird in der kommenden Version nicht veraltet sein. Können Sie einen Link anhängen? Die zu verwerfenden Funktionen lauten wie folgt: componentWillMount - UNSAFE_componentWillMount componentWillReceiveProps - UNSAFE_componentWillReceiveProps componentWillUpdate - UNSAFE_componentWillUpdate
Angel Mas
0

Sie können die bereitgestellte Methode verwenden:

useEffect(() => {
  // fetch something
}, [props.match.params.id])

Wenn garantiert wird, dass die Komponente nach einer Routenänderung erneut gerendert wird, können Sie Requisiten als Abhängigkeit übergeben

Nicht der beste Weg, aber gut genug, um mit dem umzugehen, wonach Sie suchen, laut Kent C Dodds : Überlegen Sie mehr, was passiert sein soll.

Vince Sanchez Tañan
quelle
-3

react-router ist fehlerhaft, da Komponenten bei jeder Standortänderung erneut bereitgestellt werden müssen.

Ich habe es jedoch geschafft, eine Lösung für diesen Fehler zu finden:

https://github.com/ReactTraining/react-router/issues/1982#issuecomment-275346314

Kurz gesagt (siehe den Link oben für vollständige Informationen)

<Router createElement={ (component, props) =>
{
  const { location } = props
  const key = `${location.pathname}${location.search}`
  props = { ...props, key }
  return React.createElement(component, props)
} }/>

Dadurch wird es bei jeder URL-Änderung erneut bereitgestellt

Katamphetamin
quelle