Le composant ne se remonte pas lorsque les paramètres d'itinéraire changent

99

Je travaille sur une application de réaction utilisant react-router. J'ai une page de projet qui a une URL comme suit:

myapplication.com/project/unique-project-id

Lorsque le composant de projet se charge, je déclenche une demande de données pour ce projet à partir de l'événement componentDidMount. Je rencontre maintenant un problème où si je change directement entre deux projets, seul l'id change comme ça ...

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

componentDidMount n'est pas déclenché à nouveau, donc les données ne sont pas actualisées. Dois-je utiliser un autre événement de cycle de vie pour déclencher ma demande de données ou une stratégie différente pour résoudre ce problème?

Constellés
la source
1
Pourriez-vous publier le code de vos composants?
Pierre Criulanscy

Réponses:

81

Si le lien dirige vers la même route avec juste un paramètre différent, il ne s'agit pas de remontage, mais de recevoir de nouveaux accessoires. Ainsi, vous pouvez utiliser la componentWillReceiveProps(newProps)fonction et rechercher newProps.params.projectId.

Si vous essayez de charger des données, je vous recommande de récupérer les données avant que le routeur ne gère la correspondance en utilisant des méthodes statiques sur le composant. Regardez cet exemple. React Router Mega Demo . De cette façon, le composant chargerait les données et se mettrait à jour automatiquement lorsque les paramètres d'itinéraire changeraient sans avoir besoin de s'appuyer sur componentWillReceiveProps.

Brad B
la source
2
Je vous remercie. J'ai pu faire fonctionner cela en utilisant componentWillReceiveProps. Je ne comprends toujours pas vraiment le flux de données React Router Mega Demo. Je vois le fetchData statique défini sur certains composants. Comment ces statiques sont-elles appelées du côté client du routeur?
Constellates
1
Excellente question. Extrayez le fichier client.js et le fichier server.js. Pendant le cycle d'exécution du routeur, ils appellent le fichier fetchData.js et lui transmettent state. C'est un peu déroutant au début mais devient ensuite un peu plus clair.
Brad B
12
FYI: "componentWillReceiveProps" considéré comme héritage
Idan Dagan
4
Vous devez utiliser ComponentDidUpdate avec React 16 car componentWillReceiveProps est obsolète
Pato Loco
1
Cela fonctionne mais présente le problème que vous devez ajouter componentWillReceiveProps () ou componentDidUpdate () pour gérer les rendus à chaque composant qui charge des données. Par exemple, pensez à une page qui a plusieurs composants qui chargent des données en fonction du même paramètre de chemin.
Juha Syrjälä
98

Si vous avez besoin d'un remontage de composant lorsque la route change, vous pouvez transmettre une clé unique à l'attribut clé de votre composant (la clé est associée à votre chemin / route). Ainsi, chaque fois que l'itinéraire change, la clé changera également ce qui déclenchera le démontage / remontage du composant React. J'ai eu l'idée de cette réponse

wei
la source
2
Brillant! Merci pour cette réponse simple et élégante
Z_z_Z
Il suffit de demander, cela n'affectera-t-il pas les performances de l'application?
Alpit Anand
1
@AlpitAnand c'est une vaste question. Le remontage est certainement plus lent que le re-rendu car il déclenche plus de méthodes de cycle de vie. Ici, je réponds simplement à la manière de procéder au remontage lorsque l'itinéraire change.
wei
Mais ce n'est pas un impact énorme? Quel est votre conseil pourrions-nous l'utiliser en toute sécurité sans grand effet?
Alpit Anand
@AlpitAnand Votre question est trop large et spécifique à une application. Pour certaines applications, oui, ce serait un problème de performance, auquel cas vous devriez regarder les accessoires changer vous-même et mettre à jour uniquement les éléments qui doivent être mis à jour. Pour la plupart cependant, ce qui précède est une bonne solution
Chris
84

Voici ma réponse, similaire à certaines des réponses ci-dessus mais avec du code.

<Route path="/page/:pageid" render={(props) => (
  <Page key={props.match.params.pageid} {...props} />)
} />
Point d'arrêt25
la source
4
La clé ici joue le rôle le plus important, car l'image à laquelle vous avez un lien dans une page de profil, en cliquant dessus, il suffit de la rediriger vers le même itinéraire, mais avec des paramètres différents, MAIS le composant ne se met pas à jour, car React ne peut pas trouver de différence, car il doit y avoir une CLÉ pour comparer l'ancien résultat
Georgi Peev
14

Vous devez être clair, qu'un changement d'itinéraire ne provoquera pas une actualisation de la page, vous devez le gérer vous-même.

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
la source
Merci, cette solution fonctionne pour moi. Mais mon paramètre eslint se plaint à ce sujet: github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules / ... Que pensez-vous de cela?
konekoya
Oui, ce n'est en fait pas la meilleure pratique, désolé pour ça, après avoir envoyé le "fetchProject", votre réducteur mettra à jour vos accessoires de toute façon, je viens de l'utiliser pour faire valoir mon point sans mettre le redux en place
chickenchilli
Salut chickenchilli, je suis en fait toujours coincé dans ce ... avez-vous d'autres suggestions ou tutoriels recommandés? Ou je m'en tiendrai à votre solution pour le moment. Merci de votre aide!
konekoya
12

Si tu as:

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

Dans React 16.8 et au-dessus, en utilisant des hooks , vous pouvez faire:

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

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

À l'intérieur, vous ne déclenchez un nouvel fetchResourceappel que chaque fois que cela props.match.params.idchange.

Alfonso Pérez
la source
4
C'est la meilleure réponse étant donné que la plupart des autres réponses reposent sur le désormais obsolète et non sécurisécomponentWillReceiveProps
pjb
8

Sur la base des réponses de @wei, @ Breakpoint25 et @PaulusLimma, j'ai créé ce composant de remplacement pour le <Route>. Cela remontera la page lorsque l'URL changera, forçant tous les composants de la page à être créés et montés à nouveau, pas simplement re-rendus. Tous componentDidMount()et tous les autres hooks de démarrage sont également exécutés lors du changement d'URL.

L'idée est de changer la keypropriété des composants lorsque l'URL change et cela oblige React à remonter le composant.

Vous pouvez l'utiliser comme remplacement instantané pour <Route>, par exemple, comme ceci:

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

Le <RemountingRoute>composant est défini comme ceci:

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
}

Cela a été testé avec React-Router 4.3.

Juha Syrjälä
la source
5

La réponse de @ wei fonctionne très bien, mais dans certaines situations, je trouve préférable de ne pas définir de clé de composant interne, mais de se diriger. De plus, si le chemin d'accès au composant est statique, mais que vous voulez simplement que le composant soit remonté chaque fois que l'utilisateur y accède (peut-être pour faire un appel API à componentDidMount ()), il est pratique de définir location.pathname sur la clé de la route. Avec son itinéraire et tout son contenu est remonté lorsque l'emplacement change.

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
la source
5

Voici comment j'ai résolu le problème:

Cette méthode obtient l'élément individuel de l'API:

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

J'appelle cette méthode à partir de componentDidMount, cette méthode ne sera appelée qu'une seule fois, lorsque je charge cette route pour la première fois:

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

Et à partir de componentWillReceiveProps qui sera appelé depuis la deuxième fois, nous chargeons la même route mais un ID différent, et j'appelle la première méthode pour recharger l'état, puis le composant chargera le nouvel élément.

componentWillReceiveProps(nextProps) {
    if (nextProps.match.params.id !== this.props.match.params.id) {
      const id = nextProps.match.params.id
      this.loadConstruction( id );
    }
  }
Oscar Jovanny
la source
Cela fonctionne mais présente le problème que vous devez ajouter componentWillReceiveProps () pour gérer les ré-rendus sur chaque composant qui charge des données.
Juha Syrjälä
Utilisez componentDidUpdate (prevProps). componentWillUpdate () componentWillReceiveProps () sont obsolètes.
Mirek Michalak
0

Si vous utilisez Class Component, vous pouvez utiliser componentDidUpdate

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
      }
    }
 }
Ange Mas
la source
componentDidUpdate est maintenant obsolète, ce serait bien si vous pouviez également suggérer une alternative.
Sahil
Êtes-vous sûr de cela? le ComponentDidUpdate n'est pas et ne sera pas obsolète dans la prochaine version, pouvez-vous joindre un lien? les fonctions à déconseiller comme suit: componentWillMount - UNSAFE_componentWillMount componentWillReceiveProps - UNSAFE_componentWillReceiveProps componentWillUpdate - UNSAFE_componentWillUpdate
Angel Mas
0

Vous pouvez utiliser la méthode fournie:

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

lorsque le composant est garanti de nouveau rendu après le changement d'itinéraire, vous pouvez passer des accessoires en tant que dépendance

Ce n'est pas le meilleur moyen mais assez bon pour gérer ce que vous recherchez selon Kent C Dodds : Considérez davantage ce que vous voulez qu'il se passe.

Vince Sanchez Tañan
la source
-3

react-router est cassé car il doit remonter les composants à tout changement d'emplacement.

J'ai réussi à trouver un correctif pour ce bogue:

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

En bref (voir le lien ci-dessus pour plus d'informations)

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

Cela le fera remonter à tout changement d'URL

catamphétamine
la source