2

So I have a NavBar component and the links created work fine if I am going from "/sites/1" to "/categories/1" or anything with "/sites" to "/categories" or even "/sites/1" to "/sites" but doesn't work when I am on a category show page aka "/categories/1" or "/categories/3" and trying to go to another category show page with the path "/categories/5" for example. I think the issue has to do with the fact that the exact path is dynamic and the router sees both paths as the same thing till there is a hard reload. Below is my code. Is there any way to use Link and get that component to re-render or do I have to use an anchor tag and reload each time?

import React, { useState, useEffect } from "react"
import { Switch, Route, Link, Redirect } from "react-router-dom"

import SitesIndex from "./SitesIndex";
import SiteShow from "./SiteShow";
import AddNewSiteForm from "./AddNewSiteForm"
import CategoryShow from "./CategoryShow"

const NavBar = props => {
  const [categories, setCategories] = useState([])

  const fetchCategories = async() =>{
    try {
      const response = await fetch("/api/v1/categories")
      if(!response.ok){
        const errorMessage = `${response.status} (${response.statusText})`
        const error = new Error(errorMessage)
        throw(error);
      }
      const categoryData = await response.json();
      setCategories(categoryData.categories)
    } catch (error) {
      console.error(`Error in fetch: ${error.message}`)
    }
  }

  useEffect(() =>{
    fetchCategories()
  },[])

  const categoryLinks = categories.map(category =>{
    return(
      <li key={category.id} >
        <Link to={`/categories/${category.id}`}>{category.name}</Link>
      </li>
    )
  })

  categoryLinks.unshift(
    <li key={0}>
      <Link to={`/sites`} >Home</Link>
    </li>
  )

  return(
    <div>
      <div>
        <ul className="nav-link">
          {categoryLinks}
        </ul>
      </div>

      <Switch>
        <Route exact path="/" >
          <Redirect to="/sites" />
        </Route>
        <Route exact path="/sites" component={SitesIndex} />
        <Route exact path ="/sites/new" component={AddNewSiteForm} />
        <Route exact path="/sites/:id" component={SiteShow} />
        <Route exact path="/categories/:id" component={CategoryShow} />
      </Switch>
    </div>
  )

}

export default NavBar
Katame
  • 61
  • 6

3 Answers3

2

You should include the code of the component which you are facing issues.

Do you know you can listen the changes made from special react hook called useEffect. In your categories component you can use props.match.params.id as useEffect dependency and listen the change like

const Categories = (props)=>{
 const id = props.match.params.id;
useEffect(()=>{
  //do something 
},[id])                     // This is where the react listens the changes

}
Saral Karki
  • 4,530
  • 2
  • 7
  • 10
1

Issue

You've not included the code that has the issue, i.e. the SiteShow and CategoryShow but I suspect that neither of them is reacting to changes of the id route match params value.

Solution

In both SiteShow and CategoryShow use an useEffect hook with a dependency on the id route match param to update any state, fetch any new data, etc.

import { useParams } from 'react-router-dom';

...

const { id } = useParams();

useEffect(() => {
  console.log('Route id updated', id);
  // logic to handle the id updating
}, [id]);

If you are still using class-based components then let me know and I can update answer for this use case.

Minor point about using the Switch, you should order your routes from more specific paths to less specific paths, this is so you won't need to specify the exact prop on every route. For example, "/sites/new" is more specific than "/sites/:id" is more specific than "/sites". The Redirect also doesn't, and shouldn't, be wrapped in a Route. If you need to redirect from a specific path then use the from prop.

<Switch>
  <Route path ="/sites/new" component={AddNewSiteForm} />
  <Route path="/sites/:id" component={SiteShow} />
  <Route path="/sites" component={SitesIndex} />
  <Route path="/categories/:id" component={CategoryShow} />
  <Redirect to="/sites" />
</Switch>
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
0

I ended up using useLocation to fix this issue on the categories component

const CategoryShow = (props) =>{
  let location = useLocation();
  const [category, setCategory] = useState( {sites: []})

  const fetchCategory = async() => {
    try {
      const categoryId = props.match.params.id
      const response = await fetch(`/api/v1/categories/${categoryId}`)
      if(!response.ok) {
        const errorMessage = `${response.status} (${response.statusText})`
        const error = new Error(errorMessage)
        throw(err)
      }
      const categoryData = await response.json()
      setCategory(categoryData.category)
    } catch (err) {
      console.error(`Error in fetch: ${err.message}`)
      setCategory(null)
    }
  }

  useEffect(() =>{
    fetchCategory()
  },[location.pathname])



  if(!category) {
    return(
    <div>
      <h1>That category could not be found!</h1>
      <img src={"https://error404.fun/img/full-preview/1x/9.png"} height="100%" alt="Page Not Found" />
    </div>
    )
  }

  const siteTiles = category.sites.map(site =>{
    return(
      <SiteTile
      key = {site.id}
      site = {site}
      />
    )
  })

  return(
    <div>
      <h1>{category.name}</h1>
      {siteTiles}
    </div>
  )
}

export default CategoryShow
Katame
  • 61
  • 6