1

I'm designing a simple application using create-react-app and I've recently delved into the power of react-router. However, I'm having an issue with pages getting stuck trying to load, and I'm not sure why.

I've got a Navbar component, for which I'll condense the gist of what it is.

import React from 'react';
import { BrowserRouter as Router, Link, Switch, Route } from 'react-router-dom';

import BlogPost from '../Pages/BlogPost';

// Condensed because a lot of the detail is unnecessary and no-one likes blocks of unneeded code
class Navbar extends React.Component {
  render() {
    return (
      <Router>
        <nav>
          <div id="navigationlinks">
            <ul id="navlinks-left">
                <li className="nav-item"><Link to='/'>Home</Link></li>
              <li className="nav-item"><Link to='/about'>About</Link></li>
            </ul>
          </div>
        </nav>

        <Switch>
          <Route path='/post' component={BlogPost} />
          <Route path='/about'>
            <About />
          </Route>
        </Switch>
      </Router>
    );
  }
}

function About() {
  return <h2>The about page.</h2>;
}

export default Navbar;

BlogPost represents a component that is a page in itself. The code looks like this. Again, I'll abstract away from the stuff that's not relevant.

import React from 'react';

import Navbar from '../Components/Navbar';
// Other components on this page. None of them reference the navbar or contain a router of their own.
import BlogBox from '../Sections/BlogBox';
import CommentForm from '../Components/CommentForm';
import CommentBox from '../Sections/CommentBox';

class BlogPost extends React.Component {
    
    getBlogPost(address) {
        // Gets blog data (like the title, description, content, image, etc.) using 'fetch' from 
        // an API at 'address'. Abstracted away because this isn't relevant.
    }

    componentDidMount() {
        console.log("Blog post mounted!");
        this.getBlogPost("http://localhost:8080/api/blog/samplepost");
    }

    render() {
        return (
            <div className="blog-post">
                <Navbar />
                <BlogBox post={this.state ? this.state.post : ""}/>
                <CommentForm />
                <CommentBox comments={this.state ? this.state.comments: ""} />
            </div>
        );
    }   

}

export default BlogPost;

The homepage of my application is located at '/'. It's rendered in my index.js file, the code of which is below.

import React from 'react';
import ReactDOM from 'react-dom';

import BlogHome from './Pages/BlogHome';
import BlogPost from './Pages/BlogPost';

ReactDOM.render(
    <BlogHome />,
    document.getElementById('root')
);

The homepage renders fine, but when I go to navigate to /post, the application attempts to load the page indefinitely, and eventually times out, being unable to do so.

Notice how the BlogPost page component renders the Navbar component, which in turn has a path /post which renders the BlogPost object? I'm not sure if I'm able to change this. Could this be the reason why the /post path can't load?

What concerns me the most here is that I eventually would like to add additional pages which will not only contain this same Navbar, but for which their page links also exist in this Navbar. For example, if I add an About page, this page will not only contain the Navbar, but its link will also be present in this Navbar!

Is there a way I can keep such self-page links in the Navbar, without the recursive render loop occurring?

5
  • 1
    That is probably exactly why it times out, seems you've created a recursive render loop. What and where is the "home" route rendered? I.E. what is rendering a <Route path="/" component={???} />? Why do you render your NavBar again? Typically that would be rendered outside the routes it navigates to. Commented Dec 7, 2020 at 6:28
  • @DrewReese Excellent questions. I've updated the question to include the code in my index.js file. The BlogHome component also contains the Navbar object, but it renders fine, probably due to the fact that there is no Route which currently serves the base path /. Commented Dec 7, 2020 at 6:34
  • As for why I've re-rendered the Navbar object, I usually render it at the top of every page in my application (as is usually typically for a navbar). Thus, I thought that I could incorporate react-router into this component, seeing as it will exist on every page, but this may be a rookie mistake, due to the recursive loop you mentioned. It would be nice, however, to be able to use my Navbar (somehow) incorporated with react-router, if possible.. Commented Dec 7, 2020 at 6:36
  • So you've not only rendered your Navbar in multiple places, but also BlogPost? A navbar at the top of each page is fine, but you're rendering more than just navigation links. You create a cycle. Commented Dec 7, 2020 at 6:37
  • BlogPost is currently only "rendered" in 1 place, and that is as a top-level page component which is intended to be routed to at the /post route. Or at least, I'm trying to render it in only 1 place... I'm not sure if that's what you mean. Commented Dec 7, 2020 at 6:40

2 Answers 2

1

Issue

You are creating a cycle between rendering Navbar and BlogPost when the current path matches "/post`.

In other words BlogHome renders a Navar which renders a BlogPost which then renders another Navbar and another BlogPost ad nauseam.

Solution

Restructure your navbar and routes a bit to split them out

index.js

Since BlogHome sounds like a page it should be placed on its own route. Move Router here to wrap entire app. Render BlogHome path last as that matches all path prefixes, so it will render if no other route is matched above it by the Switch.

ReactDOM.render(
    <Router>
      <Switch>
        <Route path='/post' component={BlogPost} />
        <Route path='/about'>
          <About />
        </Route>
        <Route path="/" component={BlogHome} />
      </Switch>
    </Router>,
    document.getElementById('root')
);

Navbar

Remove the router and routes, leave links only.

class Navbar extends React.Component {
  render() {
    return (
      <nav>
        <div id="navigationlinks">
          <ul id="navlinks-left">
              <li className="nav-item"><Link to='/'>Home</Link></li>
            <li className="nav-item"><Link to='/about'>About</Link></li>
          </ul>
        </div>
      </nav>
    );
  }
}

Now each page should be free to render a Navbar or not.

Sign up to request clarification or add additional context in comments.

4 Comments

I think your answer helped me to grasp some of the core concepts behind react-router that I didn't understand before - maybe you can help me clarify one of these concepts. By "rendering" a Router in the index.js file, are we simply setting up a route "fork" which will read the current path and render the corresponding component based on the switch? I previously thought that you could only pass rendered components into the ReactDOM.render() function.
@AnagramDatagram You pass JSX to ReactDOM.render. Previously you rendered <BlogHome /> into it, in my example a <Router>...</Router> is rendered into it. The Router renders its children, one of which is a Switch that renders Route components. If by "fork" you mean some logic branching then I supposed you could think about the Switch component as such since it renders routes exclusively (as opposed to inclusively with Router). Anything that is valid renderable JSX can be rendered into ReactDOM.render.
Wonderful. Clear and detailed explanations, and the solution you provided works perfectly and as I wanted. This will definitely pave the way for the next steps I have this application. Thank you!
Coming to this question from the future to help people who may be having the same problem; I was able to keep all router components together by rendering the Navbar at the top level, keeping the Link and Switch components together. This keeps the Navbar at the top of every page (which isn't a problem for me), and renders each page component (not containing the navbar) below it.
0

try to remove Navbar in your BlogPost component, because when the location changes it will switch component here

   <Switch>
      <Route path='/post' component={BlogPost} />
      <Route path='/about'>
        <About />
      </Route>
   </Switch>

and

   <nav>
      <div id="navigationlinks">
          <ul id="navlinks-left">
                <li className="nav-item"><Link to='/'>Home</Link></li>
            <li className="nav-item"><Link to='/about'>About</Link></li>
         </ul>
      </div>
   </nav>

will persisit.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.