Reacter Router Dom - Topics > Topic - How to show content based on URL?

Hello.

I’m trying (and almost succeeding) in getting React Router DOM working - I’m using React Create App to build my App.

I’ve got top level pages working (Home, About, Topics) but am having a problem with individual Topic listed on the main Topics page.

Here’s the code:

Topics.js

import React from 'react'
import { BrowserRouter as Router,Route, Link } from "react-router-dom"
import Topic from "./Topic"
function Topics({ match }){
	return(
	<Router>
	  <div>
		<h2>Topics</h2>
		<ul>
		  <li>
			<Link to={`${match.url}/rendering`}>Rendering with React</Link>
		  </li>
		  <li>
			<Link to={`${match.url}/components`}>Components</Link>
		  </li>
		  <li>
			<Link to={`${match.url}/props-v-state`}>Props v. State</Link>
		  </li>
		</ul>

		<Route path={`${match.url}/:topicId`} component={Topic} />
		<Route path={match.url} render={() => <h3>Please select a topic.</h3>} />
	  </div>
	 </Router>
	)
}

export default Topics

Topic.js

import React from 'react'

function Topic({match}){
	return(
		<div>
			<h3>{match.params.topicId}</h3>
		</div>	
	)
}

export default Topic

When I land on the Topics page there are links to each individual topic:

and when I click on a topic link, e.g. ‘Rendering with React’ I get the ‘rendering’ page:

The word ‘rendering’ in bold is output via <h3>{match.params.topicId}</h3> in Topic.js and is just the url fragment of the targeted page.

What I’ve been struggling with for many hours is how to get any specific content to display on each of the three pages using some kind of conditional statement, something like (pseudo-code):

function Topic({match}){

    if(path === topics/rendering) {
        return (
            <div>
                <h3>{match.params.topicId}</h3>
                <p>Rendering content...</p>
            </div>
        )
    } else if(path === topics/components) {
        return (
            <div>
                <h3>{match.params.topicId}</h3>
                <p>Components content...</p>
            </div>            
        )
    }  else if(path === topics/props-v-state) {
        return (
            <div>
                <h3>{match.params.topicId}</h3>
                <p>Props V State content...</p>
            </div>            
        )
    }

}

export default Topic

If I’m not being clear, please say and I will try and supply more details.

Hope someone can point me in the right direction!

Chris

I would go with a Switch.

You can build something like this:

<Switch>
  <Route exact path="/topics/components" render={() => <h3>your html/jsx</h3>}/>
  <Route exact path="/topics/props-v-state" render={() => <h3>your html/jsx</h3>}/>
</Switch>

Instead of the inline render function you can also use a Component like component={Story}

Hi - thanks for that - I’ll give it a go :slight_smile:

Hi - that worked - Many thanks!

Awesome.

You can help other people by marking your thread as “solved”,
so they can easily navigate to the solution.

I’m actually not sure if it is solved now :frowning:

I implemented the <Switch>....</Switch> and it worked. Later on I took it away again and it also worked!?

Main problem now (probably was earlier but I hadn’t noticed it) is that I can’t navigate back from a topic to the main Topics screen (although the URL displays correctly and I can reach it via the back button or by refreshing the page). Will post code later!

I implemented the <Switch>....</Switch> and it worked. Later on I took it away again and it also worked!?

<Switch> is unique in that it renders a route exclusively . In contrast, every <Route> that matches the location renders inclusively .

In short:
If you have multiple Routes, that match the path, all of them will be displayed.
If you have a Switch around the Routes, only the first matching Route will be displayed

Hi miku86coding - I am a real beginner at this so your quotes from the manual don’t make a lot of sense to me, sorry!

As mentioned in my last comment - whether I use <Switch> (in Topics.js) or not doesn’t seem to matter. I can now display individual content per Topic. However, I now see that there is another problem:

  • The app loads and I click on Topics. All good - I see the Topic items and the Topic message:

  • Next I click on ‘Rendering with React’. Again, all good, my specific Topic content is now displaying:

  • However, when I now click on the Topics link in the main menu the following happens:

The Rendering with React screen displays, but the URL has gone to localhost:3000/topics!??

(If I either reload the page, or click on Home or About then click again on Topics the correct screen appears.)


This is my folder structure:

folder-structure

The important files are:

App.js

import React from "react"

// Components
import Dashboard from "./components/Dashboard"

function App() {
	return (
		<div>
			<Dashboard />
		</div>
	)
}

export default App

Dashboard.js

import React from 'react'
import { BrowserRouter as Router, Route, NavLink } from "react-router-dom"
import Home from "../pages/Home"
import About from "../pages/About"
import Topics from "../pages/Topics"

function Dashboard() {
    return (
		<Router>
			<div>
				<ul>
					<li><NavLink exact={ true } to="/">Home</NavLink></li>
					<li><NavLink to="/about">About</NavLink></li>
					<li><NavLink  to="/topics">Topics</NavLink></li>
				</ul>

				<hr />

				<Route exact path="/" render={()=> <Home />} />
				<Route path="/about" render={()=> <About />} />
				<Route exact path="/topics" render={()=> <Topics />} />
			</div>
		</Router>
	)
}

export default Dashboard

Topics.js

import React from 'react'
import { BrowserRouter as Router, Route, Switch, NavLink } from "react-router-dom"
import TopicMessage from "./topics/TopicMessage"
import TopicRendering from "./topics/TopicRendering"
import TopicComponents from "./topics/TopicComponents"
import TopicPropsvstate from "./topics/TopicPropsvstate"
function Topics(){
	return(
		<Router>
			<div>
				<h2>Topics</h2>
				<ul className="sub-nav">
					<li><NavLink to={`/topics/rendering`}>Rendering with React</NavLink></li>
					<li><NavLink to={`/topics/components`}>Components</NavLink></li>
					<li><NavLink to={`/topics/props-v-state`}>Props v. State</NavLink></li>
				</ul>

				<Switch>
					<Route exact path="/topics/rendering" component={TopicRendering} />
					<Route exact path="/topics/components" component={TopicComponents} />
					<Route exact path="/topics/props-v-state" component={TopicPropsvstate} />
					<Route exact path="/topics" component={TopicMessage} />
				</Switch>
			</div>
		</Router>
	)
}

export default Topics

TopicRendering.js - I’ve only included one of the Topic files as they are all structurally the same.

import React from 'react'

function TopicRendering(){
	return(
		<div className="topic-box topic-rendering">
			<h3>Rendering with React</h3>
			<p>Rendering with React content...</p>
		</div>	
	)
}

export default TopicRendering

TopicMessage.js - this should display only on the top level ‘Topics’ screen

import React from 'react'

function TopicMessage(){
	return(
		<div>
			<h3>Please select a topic.</h3>
		</div>	
	)
}

export default TopicMessage

Any idea why this is happening?

Cheers Chris :slight_smile:

In Topics.js try to remove exact from routes.

Will try that, thanks :slight_smile:

No, that had no effect whatsoever…

Post your code on codesandbox.io so we can play with it.

OK jenovs, will do that first thing tomorrow and get back to you, thanks :slight_smile:

Hi Jenovs,

I’ve put the code here:https://codesandbox.io/s/2x7qn4pq10.

Thanks!

The problem is with Topics component not being dependent on anything - it’s a functional component that doesn’t get any props from its parent, therefore when you click Topics link React doesn’t see any changes and doesn’t re-render.

You may think that having Route with path “/topics” in your Topics component will do the routing, but in reality the “Topics” link is in a Dashboard component which, as I already said, just renders Topics without passing anything to it (parent doesn’t know that the child has changed).

I see (I think).

Does this mean that I have to create a prop in Dashboard and pass this down to Topics for it to work?

If so, what do you think that prop might be?

First, you should only have one <Router/> element. Remove it from your Topics.js component. (I somehow missed it yesterday).
Second, in Dashboard.js remove exact from “/topics” Route.
And third, remove Route “/topics” from Topics.js (just a cleanup).

And third, remove Route “/topics” from Topics.js (just a cleanup).

Do you mean just delete:

<Route exact path="/topics" component={TopicMessage} />

?

Yes. It doesn’t do anything anyway.

Actually, it does. It renders ’ Please select a topic’ on the Topics landing page.

I tried all your suggestions but after I’d done so clicking on the Topics links didn’t render anything (https://codesandbox.io/s/3yo6qyn3xq).

I’m following a tutorial on the subject at the moment. Will post an update if/when I figure how this all works.

Thanks for your help :slight_smile: