O componente não remonta quando os parâmetros da rota mudam


97

Estou trabalhando em um aplicativo react usando o react-router. Tenho uma página de projeto que tem o seguinte url:

myapplication.com/project/unique-project-id

Quando o componente do projeto é carregado, aciono uma solicitação de dados para esse projeto a partir do evento componentDidMount. Agora estou tendo um problema em que, se eu alternar diretamente entre dois projetos, apenas a id muda assim ...

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

componentDidMount não é disparado novamente, então os dados não são atualizados. Existe outro evento de ciclo de vida que devo usar para acionar minha solicitação de dados ou uma estratégia diferente para resolver esse problema?


1
Você poderia postar o código de seus componentes?
Pierre Criulanscy

Respostas:


81

Se o link está direcionando para a mesma rota com apenas um parâmetro diferente, ele não está remontando, mas recebendo novos adereços. Então, você pode usar a componentWillReceiveProps(newProps)função e procurar newProps.params.projectId.

Se você está tentando carregar dados, eu recomendaria buscar os dados antes de o roteador lidar com a correspondência usando métodos estáticos no componente. Veja este exemplo. Mega Demo do React Router . Dessa forma, o componente carregaria os dados e atualizaria automaticamente quando os parâmetros da rota mudassem sem a necessidade de depender deles componentWillReceiveProps.


2
Obrigado. Consegui fazer isso funcionar usando componentWillReceiveProps. Ainda não entendi direito o fluxo de dados do Mega Demo do React Router. Eu vejo o fetchData estático definido em alguns componentes. Como essas estatísticas estão sendo chamadas do lado do cliente do roteador?
Constelados em

1
Ótima pergunta. Verifique o arquivo client.js e o arquivo server.js. Durante o ciclo de execução do roteador, eles chamam o arquivo fetchData.js e passam o statepara ele. É um pouco confuso no início, mas depois fica um pouco mais claro.
Brad B,

12
Para sua informação: "componentWillReceiveProps" considerado legado
Idan Dagan

4
Você deve usar ComponentDidUpdate com React 16, pois componentWillReceiveProps está obsoleto
Pato Loco

1
Isso funciona, mas tem o problema de que você precisa adicionar componentWillReceiveProps () ou componentDidUpdate () para manipular re-renderizações para cada componente que está carregando dados. Por exemplo, pense em uma página que possui vários componentes que estão carregando dados com base no mesmo parâmetro de caminho.
Juha Syrjälä

97

Se você precisar remontar um componente quando a rota mudar, você pode passar uma chave exclusiva para o atributo de chave do seu componente (a chave é associada ao seu caminho / rota). Portanto, toda vez que a rota muda, a chave também muda, o que aciona o componente React para desmontar / remontar. Tive a ideia desta resposta


2
Brilhante! Obrigado por esta resposta simples e elegante
Z_z_Z

Só perguntando, isso não afetará o desempenho do aplicativo?
Alpit Anand

1
@AlpitAnand esta é uma questão ampla. A remontagem é definitivamente mais lenta do que a remontagem, pois aciona mais métodos de ciclo de vida. Aqui estou apenas respondendo como fazer a remontagem acontecer quando a rota muda.
wei

Mas não é um grande impacto? Qual é o seu conselho, podemos usá-lo com segurança, sem muito efeito?
Alpit Anand

@AlpitAnand Sua pergunta é muito ampla e específica do aplicativo. Para alguns aplicativos, sim, seria um golpe de desempenho, caso em que você deve observar os adereços mudando a si mesmo e atualizar apenas os elementos que devem ser atualizados. Para a maioria, porém, o acima é uma boa solução
Chris,

83

Aqui está minha resposta, semelhante a algumas das perguntas acima, mas com código.

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

4
A chave aqui desempenha o papel principal, porque a imagem para a qual você tem Link em uma página de perfil, ao clicar nela apenas redireciona para a mesma rota, mas com parâmetros diferentes, MAS o componente não atualiza, porque o React não encontra a diferença, porque deve haver uma CHAVE para comparar o resultado antigo
Georgi Peev

14

Você tem que estar claro, que uma mudança de rota não causará uma atualização de página, você tem que lidar com isso sozinho.

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

Obrigado, esta solução funciona para mim. Mas minha configuração eslint está reclamando disso: github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/… O que você acha disso?
konekoya

Sim, essa não é a prática recomendada, desculpe por isso, depois de enviar o "fetchProject", seu redutor irá atualizar seus adereços de qualquer maneira, eu apenas usei isso para deixar meu ponto de vista sem colocar o redux no lugar
chickenchilli

Oi chickenchilli, Na verdade ainda estou preso nisso ... você tem outras sugestões ou tutoriais recomendados? Ou vou ficar com sua solução por enquanto. Obrigado pela ajuda!
konekoya

12

Se você tem:

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

No React 16.8 e superior, usando ganchos , você pode fazer:

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

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

Lá, você só aciona uma nova fetchResourcechamada sempre que props.match.params.idmuda.


4
Esta é a melhor resposta, visto que a maioria das outras respostas dependem do agora obsoleto e insegurocomponentWillReceiveProps
pjb

7

Com base nas respostas de @wei, @ Breakpoint25 e @PaulusLimma, fiz este componente de substituição para o <Route>. Isso remontará a página quando o URL mudar, forçando todos os componentes da página a serem criados e montados novamente, não apenas renderizados novamente. Todos componentDidMount()e todos os outros ganchos de inicialização são executados também na mudança de URL.

A ideia é alterar as keypropriedades dos componentes quando a URL muda e isso força o React a remontar o componente.

Você pode usá-lo como um substituto imediato para <Route>, por exemplo:

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

O <RemountingRoute>componente é definido assim:

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
}

Isso foi testado com React-Router 4.3.


5

A resposta de @wei funciona muito bem, mas em algumas situações acho melhor não definir uma chave de componente interno, mas rotear a si mesmo. Além disso, se o caminho para o componente for estático, mas você apenas quiser que o componente remonte sempre que o usuário navegar até ele (talvez para fazer uma chamada de api em componentDidMount ()), é útil definir location.pathname como a chave da rota. Com ele, a rota e todo o seu conteúdo são remontados quando o local muda.

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

Foi assim que resolvi o problema:

Este método obtém o item individual da API:

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

Eu chamo esse método de componentDidMount, este método será chamado apenas uma vez, quando eu carregar esta rota pela primeira vez:

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

E de componentWillReceiveProps que será chamado desde a segunda vez que carregamos a mesma rota, mas ID diferente, e eu chamo o primeiro método para recarregar o estado e então o componente carregará o novo item.

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

Isso funciona, mas tem o problema de que você precisa adicionar componentWillReceiveProps () para manipular re-renderizações para cada componente que está carregando dados.
Juha Syrjälä

Use componentDidUpdate (prevProps). componentWillUpdate () componentWillReceiveProps () estão obsoletos.
Mirek Michalak

0

Se você estiver usando o Class Component, você pode usar o 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 está obsoleto agora, seria bom se você pudesse sugerir alguma alternativa também.
Sahil

Você tem certeza sobre isso? o ComponentDidUpdate não está e não será descontinuado nas próximas versões, você pode anexar algum link? as funções a serem descontinuadas da seguinte forma: componentWillMount - UNSAFE_componentWillMount componentWillReceiveProps - UNSAFE_componentWillReceiveProps componentWillUpdate - UNSAFE_componentWillUpdate
Angel Mas

0

Você pode usar o método fornecido:

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

quando o componente é garantido para renderizar novamente após a mudança de rota, então você pode passar props como dependência

Não é a melhor maneira, mas é boa o suficiente para lidar com o que você está procurando, de acordo com Kent C Dodds : Considere mais o que você deseja que aconteça.


-3

react-router está quebrado porque deve remontar os componentes em qualquer mudança de local.

Eu consegui encontrar uma correção para este bug:

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

Resumindo (veja o link acima para informações completas)

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

Isso fará com que seja remontado em qualquer alteração de URL

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.