Komponent nie jest montowany ponownie po zmianie parametrów trasy


102

Pracuję nad aplikacją React przy użyciu routera React-router. Mam stronę projektu, która ma następujący adres URL:

myapplication.com/project/unique-project-id

Po załadowaniu komponentu projektu wyzwalam żądanie danych dla tego projektu ze zdarzenia componentDidMount. Mam teraz problem, w którym jeśli przełączę się bezpośrednio między dwoma projektami, zmieni się tylko identyfikator ...

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

Element componentDidMount nie jest ponownie wyzwalany, więc dane nie są odświeżane. Czy istnieje inne zdarzenie cyklu życia, którego powinienem użyć, aby wywołać moje żądanie danych lub inna strategia rozwiązania tego problemu?


1
Czy możesz opublikować kod komponentów?
Pierre Criulanscy

Odpowiedzi:


83

Jeśli łącze kieruje do tej samej trasy z tylko innym parametrem, nie jest ponownie montowane, ale zamiast tego otrzymuje nowe właściwości. Możesz więc użyć componentWillReceiveProps(newProps)funkcji i poszukać newProps.params.projectId.

Jeśli próbujesz załadować dane, polecam pobranie danych, zanim router obsłuży dopasowanie przy użyciu metod statycznych na komponencie. Sprawdź ten przykład. React Router Mega Demo . W ten sposób komponent załadowałby dane i automatycznie zaktualizowałby się, gdy parametry trasy ulegną zmianie, bez konieczności polegania na nich componentWillReceiveProps.


2
Dziękuję Ci. Udało mi się to uruchomić za pomocą componentWillReceiveProps. Nadal nie rozumiem przepływu danych w React Router Mega Demo. Widzę statyczne fetchData zdefiniowane w niektórych komponentach. W jaki sposób te statystyki są wywoływane po stronie klienta routera?
Constellates

1
Świetne pytanie. Wyewidencjonuj plik client.js i server.js. Podczas cyklu pracy routera wywołują plik fetchData.js i przekazują statedo niego plik . Na początku jest to trochę zagmatwane, ale potem staje się trochę jaśniejsze.
Brad B

12
Do Twojej wiadomości: „componentWillReceiveProps” uważane za dziedzictwo
Idan Dagan

4
Powinieneś używać ComponentDidUpdate z React 16, ponieważ komponent componentWillReceiveProps jest przestarzały
Pato Loco

1
To działa, ale występuje problem polegający na tym, że musisz dodać componentWillReceiveProps () lub componentDidUpdate () w celu obsługi ponownego renderowania do każdego komponentu, który ładuje dane. Na przykład pomyśl o stronie, która ma kilka komponentów ładujących dane na podstawie tego samego parametru ścieżki.
Juha Syrjälä

102

Jeśli potrzebujesz ponownego zamontowania komponentu po zmianie trasy, możesz przekazać unikalny klucz do atrybutu klucza komponentu (klucz jest powiązany ze ścieżką / trasą). Więc za każdym razem, gdy zmienia się trasa, klucz również się zmienia, co wyzwala komponent React do odmontowania / ponownego zamontowania. Wpadłem na pomysł z tej odpowiedzi


3
Znakomity! Dziękuję za tę prostą i elegancką odpowiedź
Z_z_Z

Po prostu pytam, czy nie wpłynie to na wydajność aplikacji?
Alpit Anand

1
@AlpitAn i to jest szerokie pytanie. Ponowne montowanie jest zdecydowanie wolniejsze niż ponowne renderowanie, ponieważ uruchamia więcej metod cyklu życia. Tutaj tylko odpowiadam, jak wykonać ponowne wsiadanie, gdy zmienia się trasa.
wei

Ale to nie jest ogromny wpływ? Jaka jest Twoja rada, czy możemy go bezpiecznie używać bez większego efektu?
Alpit Anand

1
@AlpitAnand Twoje pytanie jest zbyt szerokie i dotyczy konkretnej aplikacji. W przypadku niektórych aplikacji byłby to hit wydajnościowy, w takim przypadku należy obserwować zmieniające się rekwizyty i aktualizować tylko te elementy, które powinny się aktualizować. Jednak dla większości powyższe jest dobrym rozwiązaniem
Chris

85

Oto moja odpowiedź, podobna do niektórych z powyższych, ale z kodem.

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

4
Klucz tutaj odgrywa największą rolę, ponieważ obraz, do którego masz Link na stronie profilu, po kliknięciu go po prostu przekierowuje na tę samą trasę, ale z innymi parametrami, ALE komponent nie aktualizuje się, ponieważ React nie może znaleźć różnicy, ponieważ musi istnieć KLUCZ do porównania starego wyniku
Georgi Peev

15

Musisz mieć jasność, że zmiana trasy nie spowoduje odświeżenia strony, musisz sobie z tym poradzić.

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

Dzięki, to rozwiązanie u mnie działa. Ale moje ustawienie eslint narzeka na to: github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/… Co o tym sądzisz?
konekoya

Tak, to właściwie nie jest najlepsza praktyka, przepraszam za to, po wysłaniu "fetchProject" twój reduktor i tak zaktualizuje twoje rekwizyty, po prostu użyłem tego, aby przedstawić mój punkt widzenia bez umieszczania reduxu na miejscu
chickenchilli

Cześć chili kurczak, Właściwie nadal tkwię w tym ... czy masz inne sugestie lub zalecane tutoriale? Albo na razie trzymam się twojego rozwiązania. Dzięki za pomoc!
konekoya

14

Jeśli masz:

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

W React 16.8 i nowszych, używając hooków , możesz:

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

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

Tam wywołujesz nowe fetchResourcepołączenie tylko wtedy, gdy się props.match.params.idzmieni.


4
Jest to lepsza odpowiedź, biorąc pod uwagę, że większość innych odpowiedzi opiera się na obecnie przestarzałym i niepewnymcomponentWillReceiveProps
pjb

9

Na podstawie odpowiedzi @wei, @ Breakpoint25 i @PaulusLimma stworzyłem ten komponent zastępczy dla <Route>. Spowoduje to ponowne zamontowanie strony, gdy zmieni się adres URL, wymuszając ponowne utworzenie i zamontowanie wszystkich komponentów na stronie, a nie tylko ponowne renderowanie. Wszystkie componentDidMount()inne haki startowe są wykonywane również przy zmianie adresu URL.

Pomysł polega na zmianie keywłaściwości komponentów, gdy zmieni się adres URL, co zmusza Reacta do ponownego zamontowania komponentu.

Możesz go użyć jako zamiennika typu drop-in <Route>, na przykład w ten sposób:

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

<RemountingRoute>Składowa jest zdefiniowana w następujący sposób:

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
}

Zostało to przetestowane z React-Router 4.3.


5

Odpowiedź @ wei działa świetnie, ale w niektórych sytuacjach uważam, że lepiej nie ustawiać klucza komponentu wewnętrznego, ale samą trasę. Ponadto, jeśli ścieżka do komponentu jest statyczna, ale chcesz, aby komponent był montowany ponownie za każdym razem, gdy użytkownik do niego przechodzi (na przykład w celu wykonania wywołania api w componentDidMount ()), przydatne jest ustawienie location.pathname na klucz trasy. Dzięki niemu trasa i cała jej zawartość zostanie ponownie zamontowana, gdy zmieni się lokalizacja.

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

Oto jak rozwiązałem problem:

Ta metoda pobiera pojedynczy element z interfejsu API:

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

Wywołuję tę metodę z componentDidMount, ta metoda zostanie wywołana tylko raz, kiedy załaduję tę trasę po raz pierwszy:

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

I z componentWillReceiveProps, które będą wywoływane od drugiego razu, ładujemy tę samą trasę, ale inny identyfikator, i wywołuję pierwszą metodę, aby ponownie załadować stan, a następnie komponent załaduje nowy element.

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

To działa, ale występuje problem polegający na tym, że musisz dodać komponent componentWillReceiveProps () do obsługi ponownego renderowania do każdego komponentu, który ładuje dane.
Juha Syrjälä

Użyj componentDidUpdate (prevProps). componentWillUpdate () componentWillReceiveProps () są przestarzałe.
Mirek Michalak

0

Jeśli używasz komponentu klasy, możesz użyć komponentu 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
      }
    }
 }

Komponent componentDidUpdate jest obecnie przestarzały, byłoby dobrze, gdybyś mógł również zasugerować jakąś alternatywę.
Sahil

Czy jesteś tego pewien? ComponentDidUpdate nie jest i nie będzie przestarzały w nadchodzącej wersji, czy możesz dołączyć jakiś link? funkcje, które mają być przestarzałe w następujący sposób: componentWillMount - UNSAFE_componentWillMount componentWillReceiveProps - UNSAFE_componentWillReceiveProps componentWillUpdate - UNSAFE_componentWillUpdate
Angel Mas

0

Możesz skorzystać z podanej metody:

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

kiedy gwarantowane jest ponowne renderowanie komponentu po zmianie trasy, możesz przekazać właściwości jako zależność

Według Kent C. Dodds nie jest to najlepszy sposób, ale wystarczająco dobry, aby poradzić sobie z tym, czego szukasz : Zastanów się bardziej, co chcesz, aby się wydarzyło.


-3

react-router jest uszkodzony, ponieważ musi ponownie montować komponenty przy każdej zmianie lokalizacji.

Udało mi się jednak znaleźć poprawkę dla tego błędu:

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

W skrócie (zobacz powyższy link, aby uzyskać pełne informacje)

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

Spowoduje to ponowne zamontowanie przy każdej zmianie adresu URL

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.