React router goes to proper URL but does not update the view

Viewed 2546

I'm using React Router in my React app for tracking recipes. The component here is generating links for each recipe card that will then show the detail page for the recipe selected. When I click on the link in the cards, I am taken to the correct URL, but the view does not show what is in the component , instead it just shows the Home screen component from the base URL. What do I need to change to make the URLs recipe/{id} actually show the component?

export default function RecipeReviewCard(props) {
  const classes = useStyles();
  return (
    // TODO: Make this link to the actual recipe detail screen.
    <div>
      <Link to={'recipe/' + props.recipe.id}>
        <Card className={classes.root}>
          <CardHeader
            title={props.recipe.title}
            subheader={props.recipe.prepTime}
          />
          <CardMedia
            className={classes.media}
            image={props.recipe.imageURL}
            title={props.recipe.title}
          />
          <CardContent>
            <Typography variant='body2' color='textSecondary' component='p'>
              {props.recipe.description}
            </Typography>
          </CardContent>
        </Card>
      </Link>
      <Switch>
        <Route exact path={'recipe/' + props.recipe.id}>
          <RecipeDetail />
        </Route>
      </Switch>
    </div>
  );

This second component is the parent class which is also using router to define some paths for the main navbar in the app. Perhaps there is a problem with the priority of my routes, it is taking the default '/' route for some reason over the others.

export default function PermanentDrawerLeft() {
  const classes = useStyles();

  // TODO: Fix font/style on the nav bar buttons.
  return (
    <div className={classes.root}>
      <AppBar position='fixed' className={classes.appBar}>
        <Toolbar>
          <Typography variant='h6' noWrap>
            <Switch>
              <Route path='/'>Kitchen Assistant</Route>
              <Route path='/cookbooks'>Cookbooks</Route>
              <Route path='/search'>Search</Route>
              <Route path='/settings'>Settings</Route>
            </Switch>
          </Typography>
        </Toolbar>
      </AppBar>
      <Drawer
        className={classes.drawer}
        variant='permanent'
        classes={{
          paper: classes.drawerPaper,
        }}
        anchor='left'
      >
        <div className={classes.toolbar} />
        <Divider />
        <Link to='/cookbooks'>
          <ListItem button key={'Cookbooks'}>
            <ListItemIcon>
              <MenuBookIcon />
            </ListItemIcon>
            <ListItemText primary={'Cookbooks'} />
          </ListItem>
        </Link>
        <Link to='/search'>
          <ListItem button key={'Search'}>
            <ListItemIcon>
              <SearchIcon />
            </ListItemIcon>
            <ListItemText primary={'Search'} />
          </ListItem>
        </Link>
        <Divider />
        <Link to='/settings'>
          <ListItem button key={'Settings'}>
            <ListItemIcon>
              <SettingsIcon />
            </ListItemIcon>
            <ListItemText primary={'Settings'} />
          </ListItem>
        </Link>
      </Drawer>
      <main className={classes.content}>
        <div className={classes.toolbar} />
        <Switch>
          <Route path='/cookbooks'>
            <Cookbooks />
          </Route>
          <Route path='/search'>
            <Search />
          </Route>
          <Route path='/settings'>
            <Settings />
          </Route>
          <Route path='/'>
            <Home />
          </Route>
        </Switch>
      </main>
    </div>
2 Answers

You are most likely running into a specificity issue with your route declarations. You will need to specify the exact boolean on the routes you don't want to cascade.

As an example:

<Route path='/' component={Home} />

Would always match, but:

<Route exact path='/' component={Home} />

Would only match if you were at http://www.yoursite.com and not for http://www.yoursite.com/recipes.

Take a look at the documentation for that here.

Switch

Renders the first child <Route> or <Redirect> that matches the location.

This means you can use either the exact prop on your Routes, or just specify more specific paths first, i.e. "/pathA/pathB" before "/pathA".

<Switch>
  <Route path='/cookbooks'>Cookbooks</Route>
  <Route path='/search'>Search</Route>
  <Route path='/settings'>Settings</Route>
  <Route path='/'>Kitchen Assistant</Route> // <-- move less specific "/" to end
</Switch>

This handles getting your root route components displaying correctly.

In order to get your RecipeDetail component to render it also needs to be in the base router in order for RecipeReviewCard, when mounted, to display the link to it's path.

<Switch>
  <Route path='/cookbooks'>
    <Cookbooks />
  </Route>
  <Route path='/search'>
    <Search />
  </Route>
  <Route path='/settings'>
    <Settings />
  </Route>
  <Route exact path='recipe/:recipeId'> // <-- specify path and match param
    <RecipeDetail />
  </Route>
  <Route path='/'>
    <Home />
  </Route>
</Switch>

In order for RecipeDetail to likely load the correct page it will need to extract the match param from route-props.

For class-based components or any component directly rendered by the `Route` or wrapped with the [`withRouter`][3] Higher Order Component

props.match.params.recipeId

if using the useParams hook in a functional component

const { recipeId } = useParams();
Related