0

I have this case where, in the nav bar I have different categories eg Science, business etc. When clicked on ay of these we are supposed to render News component with the appropriate category.

But in my case on clicking on any of the link, although the address bar did change but news component was not being reloaded with the new category that I provided.

Here are my files :

App.js

import './App.css';

import React, { Component } from 'react'
import NavBar from './components/NavBar';
import News from './components/News';
import {
  BrowserRouter as Router,
  Switch,
  Route
} from "react-router-dom";

export default class App extends Component {
  constructor() {
    super();
    this.category = 'about';
    this.uniqueKeyForRemountingNewsComponent = '';
  }
  render() {
    return (
      <Router>
        <div>
          <NavBar/>
          <Switch>
            <Route exact path="/about">
            </Route>
            <Route exact path="/business">
              {this.uniqueKeyForRemountingNewsComponent = this.category = 'business'}
            </Route>
            <Route exact path="/entertainment">
              {this.uniqueKeyForRemountingNewsComponent = this.category = 'entertainment'}
            </Route>
            <Route exact path="/general">
              {this.uniqueKeyForRemountingNewsComponent = this.category = 'general'}
            </Route>
            <Route exact exactpath="/health">
              {this.uniqueKeyForRemountingNewsComponent = this.category = 'health'}
            </Route>
            <Route exact path="/science">
              {this.uniqueKeyForRemountingNewsComponent = this.category = 'science'}
            </Route>
            <Route exact path="/sports">
              {this.uniqueKeyForRemountingNewsComponent = this.category = 'sports'}
            </Route>
            <Route exact path="/technology">
              {this.uniqueKeyForRemountingNewsComponent = this.category = 'technology'}
            </Route>
          </Switch>
          <News key={this.uniqueKeyForRemountingNewsComponent} pageSize={5} country="in" category={this.category}/>
        </div>
      </Router>
    )
  }
}

NavBar.js

import React, { Component } from 'react'
import {
    Link
  } from "react-router-dom";

export class NavBar extends Component {

    render() {
        return (
            <div>
                <nav className="navbar navbar-expand-lg navbar-dark bg-dark">
                    <div className="container-fluid">
                        <Link className="navbar-brand" to="/">NewsMonkey</Link>
                        <button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                        <span className="navbar-toggler-icon"></span>
                        </button>
                        <div className="collapse navbar-collapse" id="navbarSupportedContent">
                        <ul className="navbar-nav me-auto mb-2 mb-lg-0">
                            <li className="nav-item">
                            <Link className="nav-link active" aria-current="page" to="/">Home</Link>
                            </li>
                            <li className="nav-item">
                            <Link className="nav-link" to="/business">Business</Link>
                            </li>
                            <li className="nav-item">
                            <Link className="nav-link" to="/entertainment">Entertainment</Link>
                            </li>
                            <li className="nav-item">
                            <Link className="nav-link" to="/general">General</Link>
                            </li>
                            <li className="nav-item">
                            <Link className="nav-link" to="/health">Health</Link>
                            </li>
                            <li className="nav-item">
                            <Link className="nav-link" to="/science">Science</Link>
                            </li>
                            <li className="nav-item">
                            <Link className="nav-link" to="/sports">Sports</Link>
                            </li>
                            <li className="nav-item">
                            <Link className="nav-link" to="/technology">Technology</Link>
                            </li>

                        </ul>
                        </div>
                    </div>
                </nav>
            </div>
        )
    }
}

export default NavBar

News.js

import React, { Component } from 'react'
import NewsItem from './NewsItem'
import Spinner from './Spinner'
import PropTypes from 'prop-types'


export class News extends Component {

    static defaultProps = {
        country: 'in',
        pageSize: 5,
        category: 'general'
    }

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

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

    async componentDidMount() {
        let url = `https://newsapi.org/v2/top-headlines?country=${this.props.country}&category=${this.props.category}&apiKey=APIKEY&page=1&pageSize=${this.props.pageSize}`;
        this.setState({loading: true});
        let data = await fetch(url);
        console.log(data);
        let parsedData = await data.json();
        this.setState({articles: parsedData.articles, totalResults: parsedData.totalResults, loading: false})
        console.log(this.state);
    }

    handlePrevClick = async ()=>{
        let currentPage = this.state.page;
        let url = `https://newsapi.org/v2/top-headlines?country=${this.props.country}&category=${this.props.category}&apiKey=APIKEY&page=${currentPage-1}&pageSize=${this.props.pageSize}`;
        this.setState({loading: true});
        let data = await fetch(url);
        console.log(data);
        let parsedData = await data.json();
        console.log(parsedData);
        if(parsedData)
            this.setState({articles: parsedData.articles, page : currentPage - 1, loading: false});
            console.log(this.state);
    }

    handleNextClick = async ()=>{
        let currentPage = this.state.page;
        let url = `https://newsapi.org/v2/top-headlines?country=${this.props.country}&category=${this.props.category}&apiKey=APIKEY&page=${currentPage+1}&pageSize=${this.props.pageSize}`;
        this.setState({loading: true});
        let data = await fetch(url);
        console.log(data);
        let parsedData = await data.json();
        console.log(parsedData);
        if(parsedData)
            this.setState({articles: parsedData.articles, page : currentPage + 1, loading: false});
            console.log(this.state);
    }

    render() {
        return (
            <div className="container my-3">
                <h3 className="text-center">NewsMonkey - Top headlines</h3>
                {this.state.loading && <Spinner/>}
                
                <div className="row">
                {!this.state.loading && this.state.articles.map((element)=>{
                    return (
                    <div className="col-md-4" key={element.url}>
                        <NewsItem title={element && element.title?element.title.slice(0, 45): ""} description={element && element.description?element.description.slice(0, 50):""}
                        imageUrl={element.urlToImage}
                        newsUrl ={element.url}/>
                    </div>
                )})}
                </div>
                <div className="container d-flex justify-content-between">
                    
                    <button type="button" disabled={this.state.page<=1} className="btn btn-dark" onClick={this.handlePrevClick} >&larr; Prev</button>
                    <button type="button" disabled={this.props.pageSize*this.state.page>=this.state.totalResults} className="btn btn-dark" onClick={this.handleNextClick}>Next &rarr;</button>
                </div>
                
            </div>

        )
    }
}

export default News
Himanshu Poddar
  • 7,112
  • 10
  • 47
  • 93

1 Answers1

1

you shouldn't try to set a variable but just directly put the component

export default class App extends Component {
  render() {
    return (
      <Router>
        <div>
          <NavBar/>
          <Switch>
            <Route exact path="/business">
              <News pageSize={5} country="in" category={'business'}/>
            </Route>
            <Route exact path="/entertainment">
              <News pageSize={5} country="in" category={'entertainment'}/>
            </Route>
          </Switch>
          
        </div>
      </Router>
    )
  }
}

every time you hit a /business it will render the component that is a child of <Route exact path="/business">

if you call /entertainment, it renders the child component you put under <Route exact path="/entertainment">

and so on

If you want to make that a bit more "clean", you could create a component that returns a Route


const RouteCategory = ({category}) => (
  <>
            <Route exact path={`${'/' + category `}>
              <News pageSize={5} country="in" category={category}/>
            </Route>
  </>
)

export default class App extends Component {
  render() {
    return (
      <Router>
        <div>
          <NavBar/>
          <Switch>
            {['business', 'entertainment'].map(category => <RouteCategory category={category}>)}
          </Switch>

        </div>
      </Router>
    )
  }
}

I haven't tested this code but you get the idea

fredericrous
  • 2,833
  • 1
  • 25
  • 26
  • how about I use a state to do the same `{this.setState({uniqueKeyForRemountingNewsComponent: 'business', category : 'business'})}` – Himanshu Poddar Oct 14 '21 at 18:23
  • you'll get into a rerender loop :/ – fredericrous Oct 14 '21 at 18:24
  • Writing news so many times would be so repeatative, I just wanted a one liner clean solution – Himanshu Poddar Oct 14 '21 at 18:24
  • you could create a component that returns a Route – fredericrous Oct 14 '21 at 18:25
  • is there no one liner elegant way? I mean writing news component just once – Himanshu Poddar Oct 14 '21 at 18:28
  • I updated my answer, you won't get something simpler than the last suggestion I made – fredericrous Oct 14 '21 at 18:29
  • Compressing the router switch will make it a little complex and non easily rwadable, I ll wait for few answers if I don't get a simple one, I ll accept this! – Himanshu Poddar Oct 14 '21 at 18:31
  • thanks, Himanshu. I'll be interested if someone finds a better solution. Yeah my first suggestion of copy-pasting the News component under each route is also imo the most readable solution of the 2 I suggested. Also, you don't have to create the RouteCategory component, you could directly return its content inside the map function – fredericrous Oct 14 '21 at 18:42
  • @HimanshuPoddar have you found a better solution to your issue? – fredericrous Nov 02 '21 at 13:30
  • when you navigate to a route like /business, the router renders what's inside ``...however inside your component there are no children. Therefore nothing is rendered – fredericrous Nov 03 '21 at 11:36
  • so does that mean it does not even executes the JSX statements inside it? it does not act like a switch case?\ – Himanshu Poddar Nov 03 '21 at 11:39
  • No it's not a switch case. It executes the js inside but it might be scoped to the component – fredericrous Nov 03 '21 at 11:44
  • scoped to the route component meaning? because anyway I am using global variables or global function to update the variables – Himanshu Poddar Nov 03 '21 at 11:49
  • I didn't write the thing man. IMO it should just display the thing inside route, I don't even understand why you try to go off the rails – fredericrous Nov 03 '21 at 12:36
  • you could open a discussion on the github repo to get advice from the people who wrote the stuff https://github.com/remix-run/react-router/discussions – fredericrous Nov 03 '21 at 12:46
  • its not about finding answers from people who wrote the stuffs, its about how that things is supposed to work, and how its working internally. Its okay if you don't know, I ll try finding answers from other sources – Himanshu Poddar Nov 03 '21 at 13:07
  • the thing is supposed to work by rendering what's inside the component when you access a /route... – fredericrous Nov 03 '21 at 14:51