admin管理员组

文章数量:1392110

I have a demo application that uses React (v.16.8.6) and HashRouter (a requirement to use hash router). My app simply fetches some weather data and displays the relevant ponents for weather in some cities.

The problem is that there is a search feature in index page and also similar feature on search page. When search term is given app redirects to #/search/{term} route. Everything is working as expected however when I am already on search page and trigger new search I redirect to same search page with different {term} slug (needed in order to update browser current url including hash).

As a result page is NOT reloaded and ponent is NOT remounted so lifecycle hooks (especially ponentDidMount where the setup is made) are not triggered.

I made a workaround to simulate ponent re-load, but it is sort of a hack (eg manipulating state directly and settting timeout see below relevant code).

Is there a better way of achieving redirect to same page (and changing browser url including hash) while ponent is reloaded/remounted correctly?

App.js

import React, { Component } from 'react';
import { HashRouter, Route, Switch } from 'react-router-dom';

import HomePage from './home';
import SearchPage from './search';
import WeatherPage from './weather';

import './App.css';

class App extends Component {
  render() {
    return (
    <HashRouter basename="/" hashType="slash">
    <Switch>
        <Route exact path={'/'} ponent={HomePage}></Route>
        <Route path={'/weather/:woeid'} ponent={WeatherPage}></Route>
        <Route path={'/search/:term'} ponent={SearchPage}></Route>
    </Switch>
    </HashRouter>
    );
  }
}

export default App;

SearchPage.js ponent

import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import Weather from '../ponents/Weather';

function fetchData( term ) {
    return fetch('{api_url_here}?keyword='+term)
    .then(response => response.json());
}

class SearchPage extends Component {
  constructor(props){
      super(props);
      this.state = {
          loading: true,
          text: this.props.match.params.term,
          redirect: false,
          data: null
      };
  }

  ponentDidMount() {
    fetchData(this.props.match.params.term).then(jsonResponse => {
        this.setState({
            loading: false,
            data: jsonResponse
        });
    });
  }

  handleOnClick = () => {
    this.setState({redirect: true});
  }

  handleInput = (evt) => {
    this.setState({text: evt.target.value});
  }

  render() {
    // any way to avoid this workaround while redirecting to same page?
    // [from here]
     if (this.state.redirect) {
        this.state.redirect = false;
        this.state.text = this.state.text.toLowerCase();
        this.state.data = null;
        this.state.loading = true;
        // simulate ponentDidMount, since it os not re-called on same page
        setTimeout(()=>(fetchData(this.state.text).then(jsonResponse => {
            this.setState({
                loading: false,
                data: jsonResponse
            });
        })), 40);
        // redirect and update url
        return <Redirect to={"/search/"+this.state.text} />;
       // [to here]
  }
    return (
      <div>
      <h1>Search Page</h1>
      <div>
      <input type="text" placeholder="search.." value={this.state.text} onChange={this.handleInput} />
      <button type="button" onClick={this.handleOnClick}>Search!</button>
      </div>
      {this.state.loading ? 'Loading..' : (this.state.data && this.state.data.length ? this.state.data.map(res => (<Weather woeid={res.woeid} city={res.title} />)) : 'No results found!')}
      </div>
    );
  }
}

export default SearchPage;

Solution based on Redirect (which will re-load / re-mount ponent) (Tholle's answer)

change App to following:

class App extends Component {
  render() {
    return (
    <HashRouter basename="/" hashType="slash">
    <Switch>
        <Route exact path={'/'} ponent={HomePage}></Route>
        <Route path={'/weather/:woeid'} ponent={WeatherPage}></Route>
        <Route path={'/search/:term'} render={props => <SearchPage key={props.match.params.term.toLowerCase()} {...props} />}></Route>
    </Switch>
    </HashRouter>
    );
  }
}

change SearchPage to following:

class SearchPage extends Component {
  constructor(props){
      super(props);
      this.state = {
          loading: true,
          text: this.props.match.params.term,
          redirect: false,
          data: null
      };
  }

  ponentDidMount() {
    fetchData(this.props.match.params.term).then(jsonResponse => {
        this.setState({
            loading: false,
            data: jsonResponse
        });
    });
  }

  handleOnClick = () => {
    this.setState({redirect: true});
  }

  handleInput = (evt) => {
    this.setState({text: evt.target.value});
  }

  render() {
    if (this.state.redirect) {
        // redirect and update url
        return <Redirect to={"/search/"+this.state.text.toLowerCase()} />;
  }
    return (
      <div>
      <h1>Search Page</h1>
      <div>
      <input type="text" placeholder="search.." value={this.state.text} onChange={this.handleInput} />
      <button type="button" onClick={this.handleOnClick}>Search!</button>
      </div>
      {this.state.loading ? 'Loading..' : (this.state.data && this.state.data.length ? this.state.data.map(res => (<Weather woeid={res.woeid} city={res.title} />)) : 'No results found!')}
      </div>
    );
  }
}

I have a demo application that uses React (v.16.8.6) and HashRouter (a requirement to use hash router). My app simply fetches some weather data and displays the relevant ponents for weather in some cities.

The problem is that there is a search feature in index page and also similar feature on search page. When search term is given app redirects to #/search/{term} route. Everything is working as expected however when I am already on search page and trigger new search I redirect to same search page with different {term} slug (needed in order to update browser current url including hash).

As a result page is NOT reloaded and ponent is NOT remounted so lifecycle hooks (especially ponentDidMount where the setup is made) are not triggered.

I made a workaround to simulate ponent re-load, but it is sort of a hack (eg manipulating state directly and settting timeout see below relevant code).

Is there a better way of achieving redirect to same page (and changing browser url including hash) while ponent is reloaded/remounted correctly?

App.js

import React, { Component } from 'react';
import { HashRouter, Route, Switch } from 'react-router-dom';

import HomePage from './home';
import SearchPage from './search';
import WeatherPage from './weather';

import './App.css';

class App extends Component {
  render() {
    return (
    <HashRouter basename="/" hashType="slash">
    <Switch>
        <Route exact path={'/'} ponent={HomePage}></Route>
        <Route path={'/weather/:woeid'} ponent={WeatherPage}></Route>
        <Route path={'/search/:term'} ponent={SearchPage}></Route>
    </Switch>
    </HashRouter>
    );
  }
}

export default App;

SearchPage.js ponent

import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import Weather from '../ponents/Weather';

function fetchData( term ) {
    return fetch('{api_url_here}?keyword='+term)
    .then(response => response.json());
}

class SearchPage extends Component {
  constructor(props){
      super(props);
      this.state = {
          loading: true,
          text: this.props.match.params.term,
          redirect: false,
          data: null
      };
  }

  ponentDidMount() {
    fetchData(this.props.match.params.term).then(jsonResponse => {
        this.setState({
            loading: false,
            data: jsonResponse
        });
    });
  }

  handleOnClick = () => {
    this.setState({redirect: true});
  }

  handleInput = (evt) => {
    this.setState({text: evt.target.value});
  }

  render() {
    // any way to avoid this workaround while redirecting to same page?
    // [from here]
     if (this.state.redirect) {
        this.state.redirect = false;
        this.state.text = this.state.text.toLowerCase();
        this.state.data = null;
        this.state.loading = true;
        // simulate ponentDidMount, since it os not re-called on same page
        setTimeout(()=>(fetchData(this.state.text).then(jsonResponse => {
            this.setState({
                loading: false,
                data: jsonResponse
            });
        })), 40);
        // redirect and update url
        return <Redirect to={"/search/"+this.state.text} />;
       // [to here]
  }
    return (
      <div>
      <h1>Search Page</h1>
      <div>
      <input type="text" placeholder="search.." value={this.state.text} onChange={this.handleInput} />
      <button type="button" onClick={this.handleOnClick}>Search!</button>
      </div>
      {this.state.loading ? 'Loading..' : (this.state.data && this.state.data.length ? this.state.data.map(res => (<Weather woeid={res.woeid} city={res.title} />)) : 'No results found!')}
      </div>
    );
  }
}

export default SearchPage;

Solution based on Redirect (which will re-load / re-mount ponent) (Tholle's answer)

change App to following:

class App extends Component {
  render() {
    return (
    <HashRouter basename="/" hashType="slash">
    <Switch>
        <Route exact path={'/'} ponent={HomePage}></Route>
        <Route path={'/weather/:woeid'} ponent={WeatherPage}></Route>
        <Route path={'/search/:term'} render={props => <SearchPage key={props.match.params.term.toLowerCase()} {...props} />}></Route>
    </Switch>
    </HashRouter>
    );
  }
}

change SearchPage to following:

class SearchPage extends Component {
  constructor(props){
      super(props);
      this.state = {
          loading: true,
          text: this.props.match.params.term,
          redirect: false,
          data: null
      };
  }

  ponentDidMount() {
    fetchData(this.props.match.params.term).then(jsonResponse => {
        this.setState({
            loading: false,
            data: jsonResponse
        });
    });
  }

  handleOnClick = () => {
    this.setState({redirect: true});
  }

  handleInput = (evt) => {
    this.setState({text: evt.target.value});
  }

  render() {
    if (this.state.redirect) {
        // redirect and update url
        return <Redirect to={"/search/"+this.state.text.toLowerCase()} />;
  }
    return (
      <div>
      <h1>Search Page</h1>
      <div>
      <input type="text" placeholder="search.." value={this.state.text} onChange={this.handleInput} />
      <button type="button" onClick={this.handleOnClick}>Search!</button>
      </div>
      {this.state.loading ? 'Loading..' : (this.state.data && this.state.data.length ? this.state.data.map(res => (<Weather woeid={res.woeid} city={res.title} />)) : 'No results found!')}
      </div>
    );
  }
}
Share Improve this question edited Apr 15, 2019 at 10:56 Nikos M. asked Apr 15, 2019 at 9:54 Nikos M.Nikos M. 8,3554 gold badges40 silver badges45 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 1

One way of going about it is to add a ponentDidUpdate lifecycle hook to the SearchPage ponent and check if the term parameter changes and do the search again.

If you want to remount the ponent when the term parameter changes instead, you can use the key prop to unmount the previous ponent and mount a new one when the key changes.

<Route
  path={"/search/:term"}
  render={props => <SearchPage key={props.match.params.term} {...props} />}
/>

Use the Link from react-router-dom instead of the <button> to change the route instead of just redirecting. Then in the onClick of the <Link> ponent call the fetchData and set the state accordingly.

<Link className="btn" to={"/search/"+this.state.text} onClick={this.handleOnClick}>Search!</Link>

Then in the handleOnClick function

handleOnClick() {
   this.setState({
      loading: true
   });
   fetchData(this.state.text).then(jsonResponse => {
     this.setState({
        loading: false,
        data: jsonResponse
     });
   });
}

本文标签: