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?

Constelados
fonte
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.

Brad B
fonte
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

wei
fonte
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} />)
} />
Breakpoint 25
fonte
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 .../> }
}
chickenchilli
fonte
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.

Alfonso Pérez
fonte
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.

Juha Syrjälä
fonte
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)
Paulus Limma
fonte
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 );
    }
  }
Oscar Jovanny
fonte
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
      }
    }
 }
Angel Mas
fonte
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.

Vince Sanchez Tañan
fonte
-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

catanfetamina
fonte