Il componente non si rimonta quando cambiano i parametri del percorso


97

Sto lavorando a un'applicazione React utilizzando React-Router. Ho una pagina del progetto che ha un URL come segue:

myapplication.com/project/unique-project-id

Quando il componente del progetto viene caricato, faccio scattare una richiesta di dati per quel progetto dall'evento componentDidMount. Ora sto riscontrando un problema in cui se passo direttamente tra due progetti, solo l'id cambia in questo modo ...

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

componentDidMount non viene attivato di nuovo, quindi i dati non vengono aggiornati. C'è un altro evento del ciclo di vita che dovrei utilizzare per attivare la mia richiesta di dati o una strategia diversa per affrontare questo problema?


1
Potresti pubblicare il codice dei tuoi componenti?
Pierre Criulanscy

Risposte:


81

Se il collegamento si dirige verso la stessa rotta con solo un parametro diverso, non rimonta, ma riceve invece nuovi oggetti di scena. Quindi, potresti usare la componentWillReceiveProps(newProps)funzione e cercare newProps.params.projectId.

Se stai provando a caricare i dati, ti consiglio di recuperare i dati prima che il router gestisca la corrispondenza utilizzando metodi statici sul componente. Guarda questo esempio. React Router Mega Demo . In questo modo, il componente caricherà i dati e si aggiornerà automaticamente quando i parametri del percorso cambiano senza bisogno di fare affidamento componentWillReceiveProps.


2
Grazie. Sono stato in grado di farlo funzionare utilizzando componentWillReceiveProps. Ancora non capisco il flusso di dati Mega Demo di React Router. Vedo il fetchData statico definito su alcuni componenti. Come vengono chiamate queste statistiche dal lato client del router?
Costellazioni

1
Ottima domanda. Controlla il file client.js e il file server.js. Durante il ciclo di esecuzione del router chiamano il file fetchData.js e gli passano il file state. All'inizio è un po 'confuso, ma poi diventa un po' più chiaro.
Brad B

12
FYI: "componentWillReceiveProps" considerato eredità
Idan Dagan

4
Dovresti usare ComponentDidUpdate con React 16 poiché componentWillReceiveProps è deprecato
Pato Loco

1
Funziona, ma presenta il problema di dover aggiungere componentWillReceiveProps () o componentDidUpdate () per la gestione dei ri-rendering a ogni componente che sta caricando i dati. Ad esempio, pensa a una pagina che ha diversi componenti che caricano i dati in base allo stesso parametro di percorso.
Juha Syrjälä

97

Se è necessario rimontare un componente quando viene modificato il percorso, è possibile passare una chiave univoca all'attributo chiave del componente (la chiave è associata al percorso / percorso). Quindi ogni volta che il percorso cambia, cambierà anche la chiave che attiva lo smontaggio / rimontaggio del componente React. Ho avuto l'idea da questa risposta


2
Brillante! Grazie per questa risposta semplice ed elegante
Z_z_Z

Sto solo chiedendo, non influirà sulle prestazioni dell'app?
Alpit Anand

1
@AlpitAnand questa è una domanda ampia. Il re-montaggio è decisamente più lento del re-rendering in quanto attiva più metodi del ciclo di vita. Qui sto solo rispondendo su come fare in modo che il rimontaggio avvenga quando il percorso cambia.
venerdì

Ma non è un impatto enorme? Qual è il tuo consiglio possiamo usarlo in sicurezza senza molto effetto?
Alpit Anand

@AlpitAnand La tua domanda è troppo ampia e specifica per l'applicazione. Per alcune applicazioni, sì, sarebbe un calo delle prestazioni, nel qual caso dovresti guardare gli oggetti di scena cambiare te stesso e aggiornare solo gli elementi che dovrebbero essere aggiornati. Per la maggior parte, però, quanto sopra è una buona soluzione
Chris

83

Ecco la mia risposta, simile ad alcune delle precedenti ma con codice.

<Route path="/page/:pageid" render={(props) => (
  <Page key={props.match.params.pageid} {...props} />)
} />

4
La chiave qui gioca il ruolo più importante, perché l'immagine a cui hai un collegamento in una pagina del profilo, facendo clic su di essa, reindirizza semplicemente allo stesso percorso, ma con parametri diversi, MA il componente non si aggiorna, perché React non riesce a trovare una differenza, perché ci deve essere una CHIAVE per confrontare il vecchio risultato
Georgi Peev

14

Devi essere chiaro, che un cambio di percorso non provocherà un aggiornamento della pagina, devi gestirlo da solo.

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

Grazie, questa soluzione funziona per me. Ma la mia impostazione di eslint si lamenta di questo: github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/… Cosa ne pensi di questo?
konekoya

Sì, in realtà non è la migliore pratica, mi dispiace, dopo aver inviato il "fetchProject" il tuo riduttore aggiornerà comunque i tuoi oggetti di scena, l'ho usato solo per esprimere il mio punto senza mettere il redux in atto
chickenchilli

Ciao chickenchilli, in realtà sono ancora bloccato in questo ... hai altri suggerimenti o tutorial consigliati? O per ora mi atterrò alla tua soluzione. Grazie per l'aiuto!
konekoya

12

Se hai:

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

In React 16.8 e versioni successive, usando gli hook , puoi fare:

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

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

Lì, stai solo attivando una nuova fetchResourcechiamata ogni volta che props.match.params.idcambia.


4
Questa è la risposta migliore dato che la maggior parte delle altre risposte si basa componentWillReceiveProps
sull'ormai

7

Sulla base delle risposte di @wei, @ Breakpoint25 e @PaulusLimma ho realizzato questo componente sostitutivo per <Route>. Questo rimonterà la pagina quando l'URL cambia, costringendo tutti i componenti nella pagina a essere creati e montati di nuovo, non solo nuovamente renderizzati. Tutti componentDidMount()e tutti gli altri hook di avvio vengono eseguiti anche al cambio di URL.

L'idea è di cambiare la keyproprietà dei componenti quando cambia l'URL e questo forza React a rimontare il componente.

Puoi usarlo come sostituto immediato <Route>, ad esempio in questo modo:

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

Il <RemountingRoute>componente è definito così:

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
}

Questo è stato testato con React-Router 4.3.


5

La risposta di @wei funziona alla grande, ma in alcune situazioni trovo meglio non impostare una chiave di componente interna, ma instradare se stesso. Inoltre, se il percorso del componente è statico, ma si desidera solo che il componente rimonti ogni volta che l'utente vi naviga (forse per effettuare una chiamata api su componentDidMount ()), è utile impostare location.pathname sulla chiave del percorso. Con esso percorso e tutto il suo contenuto viene rimontato quando la posizione cambia.

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)

5

Ecco come ho risolto il problema:

Questo metodo ottiene il singolo elemento dall'API:

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

Chiamo questo metodo da componentDidMount, questo metodo verrà chiamato solo una volta, quando carico questa rotta per la prima volta:

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

E da componentWillReceiveProps che verrà chiamato dalla seconda volta che carichiamo lo stesso percorso ma ID diverso, e chiamo il primo metodo per ricaricare lo stato e poi il componente caricherà il nuovo elemento.

componentWillReceiveProps(nextProps) {
    if (nextProps.match.params.id !== this.props.match.params.id) {
      const id = nextProps.match.params.id
      this.loadConstruction( id );
    }
  }

Funziona ma ha il problema che è necessario aggiungere componentWillReceiveProps () per la gestione dei ri-rendering a ogni componente che sta caricando i dati.
Juha Syrjälä

Usa componentDidUpdate (prevProps). componentWillUpdate () componentWillReceiveProps () sono deprecati.
Mirek Michalak

0

Se stai usando Class Component, puoi usare 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
      }
    }
 }

componentDidUpdate ora è deprecato, sarebbe bene se tu potessi suggerire anche qualche alternativa.
Sahil

Sei sicuro di questo? il ComponentDidUpdate non è e non sarà deprecato nella prossima versione, puoi allegare qualche link? le funzioni da deprecare come segue: componentWillMount - UNSAFE_componentWillMount componentWillReceiveProps - UNSAFE_componentWillReceiveProps componentWillUpdate - UNSAFE_componentWillUpdate
Angel Mas

0

Puoi utilizzare il metodo fornito:

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

quando è garantito che il componente riesca di nuovo dopo il cambio di rotta, puoi passare gli oggetti di scena come dipendenza

Non è il modo migliore, ma abbastanza buono per gestire ciò che stai cercando secondo Kent C. Dodds : Considera di più quello che vuoi che sia successo.


-3

react-router è rotto perché deve rimontare i componenti su qualsiasi cambiamento di posizione.

Tuttavia, sono riuscito a trovare una correzione per questo bug:

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

In breve (vedi il link sopra per tutte le informazioni)

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

Questo lo farà rimontare su qualsiasi modifica dell'URL

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.