El componente no se vuelve a montar cuando cambian los parámetros de ruta

100

Estoy trabajando en una aplicación de reacción usando react-router. Tengo una página de proyecto que tiene una URL de la siguiente manera:

myapplication.com/project/unique-project-id

Cuando se carga el componente del proyecto, activo una solicitud de datos para ese proyecto desde el evento componentDidMount. Ahora me encuentro con un problema en el que si cambio directamente entre dos proyectos, solo la identificación cambia así ...

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

componentDidMount no se activa de nuevo, por lo que los datos no se actualizan. ¿Hay algún otro evento del ciclo de vida que deba utilizar para activar mi solicitud de datos o una estrategia diferente para abordar este problema?

Constelaciones
fuente
1
¿Podrías publicar el código de tus componentes?
Pierre Criulanscy

Respuestas:

81

Si el enlace se dirige a la misma ruta con solo un parámetro diferente, no se está volviendo a montar, sino que recibe nuevos accesorios. Entonces, podría usar la componentWillReceiveProps(newProps)función y buscar newProps.params.projectId.

Si está intentando cargar datos, le recomendaría obtener los datos antes de que el enrutador maneje la coincidencia utilizando métodos estáticos en el componente. Mira este ejemplo. React Router Mega Demo . De esa manera, el componente cargaría los datos y se actualizaría automáticamente cuando cambiaran los parámetros de ruta sin necesidad de depender de ellos componentWillReceiveProps.

Brad B
fuente
2
Gracias. Pude hacer que esto funcionara usando componentWillReceiveProps. Todavía no entiendo realmente el flujo de datos de React Router Mega Demo. Veo fetchData estático definido en algunos componentes. ¿Cómo se llaman estas estadísticas desde el lado del cliente del enrutador?
Constelaciones
1
Gran pregunta. Consulte el archivo client.js y el archivo server.js. Durante el ciclo de ejecución del enrutador, llaman al archivo fetchData.js y le pasan el state. Es un poco confuso al principio, pero luego se vuelve un poco más claro.
Brad B
12
Para su información: "componentWillReceiveProps" considerado heredado
Idan Dagan
4
Debe usar ComponentDidUpdate con React 16 ya que componentWillReceiveProps está obsoleto
Pato Loco
1
Esto funciona, pero tiene el problema de que debe agregar componentWillReceiveProps () o componentDidUpdate () para manejar re-renderizaciones en cada componente que está cargando datos. Por ejemplo, piense en una página que tiene varios componentes que cargan datos en función del mismo parámetro de ruta.
Juha Syrjälä
99

Si necesita volver a montar un componente cuando cambia la ruta, puede pasar una clave única al atributo de clave de su componente (la clave está asociada con su ruta / ruta). Entonces, cada vez que cambia la ruta, la clave también cambiará, lo que activa el componente React para desmontar / volver a montar. Tengo la idea de esta respuesta

wei
fuente
2
¡Brillante! Gracias por esta respuesta simple y elegante
Z_z_Z
Solo pregunto, ¿esto no afectará el rendimiento de la aplicación?
Alpit Anand
1
@AlpitAna Y esta es una pregunta amplia. El volver a montar es definitivamente más lento que volver a renderizar, ya que activa más métodos de ciclo de vida. Aquí solo estoy respondiendo cómo hacer que se vuelva a montar cuando cambia la ruta.
wei
¿Pero no es un gran impacto? ¿Cuál es tu consejo? ¿Podemos usarlo de forma segura sin mucho efecto?
Alpit Anand
@AlpitAnand Su pregunta es demasiado amplia y específica de la aplicación. Para algunas aplicaciones, sí, sería un golpe de rendimiento, en cuyo caso debería ver cómo cambian los accesorios y actualizar solo los elementos que deberían actualizarse. Sin embargo, para la mayoría, lo anterior es una buena solución
Chris
85

Aquí está mi respuesta, similar a algunas de las anteriores pero con código.

<Route path="/page/:pageid" render={(props) => (
  <Page key={props.match.params.pageid} {...props} />)
} />
Punto de interrupción25
fuente
4
La clave aquí juega el papel más importante, porque la imagen a la que tiene un enlace en una página de perfil, al hacer clic en ella, simplemente redirige a la misma ruta, pero con diferentes parámetros, PERO el componente no se actualiza, porque React no puede encontrar una diferencia, porque debe haber una CLAVE para comparar el resultado anterior
Georgi Peev
15

Tienes que tener claro que un cambio de ruta no provocará una actualización de página, tienes que manejarlo tú mismo.

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 .../> }
}
pollo con chile
fuente
Gracias, esta solución me funciona. Pero mi configuración de eslint se queja de esto: github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/… ¿Qué opinas sobre esto?
konekoya
Sí, eso en realidad no es la mejor práctica, lo siento, después de enviar el "fetchProject", su reductor actualizará sus accesorios de todos modos, solo usé esto para hacer mi punto sin poner el redux en su lugar
chickenchilli
Hola pollo, todavía estoy atrapado en esto ... ¿tienes otras sugerencias o tutoriales recomendados? O me quedo con tu solución por ahora. ¡Gracias por tu ayuda!
konekoya
13

Si usted tiene:

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

En React 16.8 y superior, usando ganchos , puede hacer:

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

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

Allí, solo está activando una nueva fetchResourcellamada cada vez que props.match.params.idcambia.

Alfonso Pérez
fuente
4
Esta es la mejor respuesta dado que la mayoría de las otras respuestas se basan en el ahora obsoleto e insegurocomponentWillReceiveProps
pjb
8

Basado en las respuestas de @wei, @ Breakpoint25 y @PaulusLimma, hice este componente de reemplazo para <Route>. Esto volverá a montar la página cuando cambie la URL, lo que obligará a que todos los componentes de la página se creen y monten de nuevo, no solo que se vuelvan a representar. Todos componentDidMount()y todos los demás enlaces de inicio se ejecutan también en el cambio de URL.

La idea es cambiar la keypropiedad de los componentes cuando cambia la URL y esto obliga a React a volver a montar el componente.

Puede usarlo como un reemplazo directo para <Route>, por ejemplo, esto:

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

El <RemountingRoute>componente se define así:

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
}

Esto ha sido probado con React-Router 4.3.

Juha Syrjälä
fuente
5

La respuesta de @ wei funciona muy bien, pero en algunas situaciones me parece mejor no establecer una clave del componente interno, sino la ruta en sí. Además, si la ruta al componente es estática, pero solo desea que el componente se vuelva a montar cada vez que el usuario navegue hacia él (tal vez para hacer una llamada de API en componentDidMount ()), es útil establecer location.pathname en la clave de la ruta. Con su ruta y todo su contenido se vuelve a montar cuando cambia la ubicación.

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
fuente
5

Así es como resolví el problema:

Este método obtiene el elemento individual de la API:

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

Llamo a este método desde componentDidMount, este método se llamará solo una vez, cuando cargue esta ruta por primera vez:

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

Y desde componentWillReceiveProps que se llamará desde la segunda vez que cargamos la misma ruta pero diferente ID, y llamo al primer método para recargar el estado y luego el componente cargará el nuevo elemento.

componentWillReceiveProps(nextProps) {
    if (nextProps.match.params.id !== this.props.match.params.id) {
      const id = nextProps.match.params.id
      this.loadConstruction( id );
    }
  }
Oscar Jovanny
fuente
Esto funciona, pero tiene el problema de que necesita agregar componentWillReceiveProps () para manejar las re-renderizaciones en cada componente que está cargando datos.
Juha Syrjälä
Utilice componentDidUpdate (prevProps). componentWillUpdate () componentWillReceiveProps () están en desuso.
Mirek Michalak
0

Si está usando Class Component, puede usar 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
      }
    }
 }
Ángel Mas
fuente
componentDidUpdate está obsoleto ahora, sería bueno si también pudiera sugerir alguna alternativa.
Sahil
¿Estás seguro de eso? ComponentDidUpdate no está ni va a quedar obsoleto en la próxima versión, ¿puede adjuntar algún enlace? las funciones quedarán obsoletas de la siguiente manera: componentWillMount - UNSAFE_componentWillMount componentWillReceiveProps - UNSAFE_componentWillReceiveProps componentWillUpdate - UNSAFE_componentWillUpdate
Angel Mas
0

Puede utilizar el método proporcionado:

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

cuando se garantiza que el componente volverá a renderizarse después del cambio de ruta, puede pasar los accesorios como dependencia

No es la mejor manera, pero es lo suficientemente buena para manejar lo que está buscando según Kent C. Dodds : Considere más lo que desea que haya sucedido.

Vince Sánchez Tañan
fuente
-3

react-router está roto porque debe volver a montar componentes en cualquier cambio de ubicación.

Sin embargo, logré encontrar una solución para este error:

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

En resumen (consulte el enlace de arriba para obtener información completa)

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

Esto hará que se vuelva a montar en cualquier cambio de URL.

catanfetamina
fuente