Aditya Narayan Jha
Aditya Narayan Jha

Reputation: 33

Why is React router not unmounting previous content when route is changed?

I'm trying to build a React app which fetches news from the news API and displays it based on the category. It uses Infinite scrolling and displays 6 news items at a time.

export default class App extends Component {
  render() {
    return (<>
      <BrowserRouter>

        <Navbar />
        <Routes>

          <Route exact path="/general" key="general" element={<News category={'general'} />} />
          <Route exact path="/sports" key="sports" element={<News category={'sports'} />} />
          <Route exact path="/entertainment" key="entertainment" element={<News category={'entertainment'} />} />
          <Route exact path="/health" key="health" element={<News category={'health'} />} />
          <Route exact path="/science" key="science" element={<News category={'science'} />} />
          <Route exact path="/" key="everything" element={<News/>} />

        </Routes>

      </BrowserRouter>
    </>
    )
  }
}

here is the code of News component

export default class News extends Component {

  static defaultProps = {
    pageLimit: 6,
    category:'',
    country: 'in'
  }

  static propTypes = {
    pageLimit: PropTypes.number.isRequired,
    country: PropTypes.string.isRequired
  }

  constructor() {
    super();
    this.state = {
      articles: [],
      loading: true,
      page: 1,
      totalResults: 1,
      pageSize: 1
    }
  }

  async componentDidMount() {
    let url = `https://newsapi.org/v2/top-headlines?country=${this.props.country}&apiKey=445f58e1d17c4229b23e3965d19197c7&category=${this.props.category}&page=1&pagesize=${this.props.pageLimit}`;
    let res = await fetch(url);
    let data = await res.json();
    this.setState({
      loading: false,
      totalResults: data.totalResults,
      articles: data.articles,
      pageSize: Math.ceil(data.totalResults / this.props.pageLimit)
    });
  }

  fetchData = async () => {
    let url = `https://newsapi.org/v2/top-headlines?country=${this.props.country}&apiKey=445f58e1d17c4229b23e3965d19197c7&category=${this.props.category}&page=${this.state.page + 1}&pagesize=${this.props.pageLimit}`;
    
    this.setState({
      loading: true,
      page: this.state.page + 1,
    })

    let res = await fetch(url);
    let data = await res.json();
    this.setState({
      loading: false,
      articles: this.state.articles.concat(data.articles),
    });
  }

  render() {
    return (<>

      <InfiniteScroll
        dataLength={this.state.articles.length}
        next={this.fetchData}
        hasMore={this.state.articles.length !== this.state.totalResults}
        loader={<Spinner />}
      >
        <div className='container'>
          <div className='row'>
            {
              this.state.articles.map((elem) => {
                return <div className='col-md-4' key={elem.url}>
                  <Newscomp title={elem.title ? elem.title.slice(0, 30) : ""} url={elem.url} imgUrl={elem.urlToImage} description={elem.content ? elem.content.slice(0, 40) : ""} />
                </div>
              })
            }
          </div>
        </div>
      </InfiniteScroll>
    </>
    )
  }
}

when I try to change the category lets say from general to sports the previous 6 news items from general remain but on scrolling down I get the news from sports category.

I want it to completely remove the previous content and show the newly fetched content when I switch categories. How can I do that?

Upvotes: 3

Views: 3853

Answers (1)

Drew Reese
Drew Reese

Reputation: 202608

This is an optimization when 2 or more routes render the same routed component. In this case, the News component. The News component will remain mounted and only the category prop is changing, so the News component can respond to that change. You have a couple options:

  1. Move the key to the correct component. It is rather irrelevant on the Route component. Move the key prop to the routed component to "force remount" it.

    <BrowserRouter>
      <Navbar />
      <Routes>
        <Route path="/general" element={<News key="general" category={'general'} />} />
        <Route path="/sports" element={<News key="sports" category={'sports'} />} />
        <Route
          path="/entertainment"
          element={<News key="entertainment" category={'entertainment'} />}
        />
        <Route path="/health" element={<News key="health" category={'health'} />} />
        <Route path="/science" element={<News key="science" category={'science'} />} />
        <Route path="/" element={<News key="everything" />} />
      </Routes>
    </BrowserRouter>
    
  2. Implement the componentDidUpdate lifecycle method to handle responding to the category prop updating.

    export default class News extends Component {
      ...
    
      fetchInitialData = async () => {
        const { category, country, pageLimit } = this.props;
        this.setState({ loading: true }); // set loading state
    
        const url = `https://newsapi.org/v2/top-headlines?country=${country}&apiKey=445f58e1d17c4229b23e3965d19197c7&category=${category}&page=1&pagesize=${pageLimit}`;
    
        try {
          const res = await fetch(url);
          const data = await res.json();
    
          this.setState({
            totalResults: data.totalResults,
            articles: data.articles,
            pageSize: Math.ceil(data.totalResults / pageLimit)
          });
        } catch(error) {
          // handle any rejected Promises or thrown errors
        } finally {
          this.setState({ loading: false }); // clear loading state
        }
      }
    
      componentDidMount() {
        fetchInitialData();
      }
    
      componentDidUpdate(prevProps) {
        if (prevProps.category !== this.props.category) {
          fetchInitialData();
        }
      }
    
      ...
    }
    

Upvotes: 3

Related Questions