Guido Goluke
Guido Goluke

Reputation: 139

React router does not render route when URL accessed directly

Consider this the main App component (imports left out for brevity):

const App = () => {
    const [orderRoutes, setOrderRoutes] = useState([])

    const updateOrderRoutes = (newRoute) => {
        orderRoutes.push(newRoute)
        setOrderRoutes(orderRoutes)
    }

    const renderedOrderRoutes = orderRoutes.map(route => {
        return (
            <Route
                path={`/${route.class}/${route.order}`}
                exact
                key={`/${route.class}/${route.order}`}
            >
                <CatalogPage />
            </Route>
        )
    })

    return (
        <BrowserRouter>
            <Header
                updateOrderRoutes={updateOrderRoutes}
            />
            <Route path="/" exact component={Home} />
            <Route path="/aboutus" exact component={AboutUs} />
            <Route path="/faq" exact component={Faq} />
            <Route path="/register" exact component={Register} />
            {renderedOrderRoutes}
            <Footer />
        </BrowserRouter>
    )
}

export default App

The challenge is that some of the routes are not known when rendering the initial App component. They will be known when an AJAX request in the <Header> component is responded to. The header will then update the new route to the orderRoutes state property, re-rendering the App component every time. The routes that are the result of the AJAX call (that is made in the <Header>) are then rendered to the <BrowserRouter> (in {renderedOrderRoutes}). In the <Header>, there is a <Link> for each route being rendered as a result of the same AJAX call, so that every menu entry (The <Link>s) will have a corresponding route.

This works fine, but when I access one of the URL's that this mechanism generates directly (e.g.: refresh the page), the <CatalogPage> component is not rendered.

So, for instance let's say that the AJAX call results in a bunch of routes and one of those is /t-shirts/tanktops. I will get a menu entry with a link to that path. When I click that menu entry the <CatalogPage> component is rendered. But when I access /t-shirts/tanktops directly, the <CatalogPage> component is not rendered.

How can I alter this code to make the URL's that are a result of the AJAX call directly accessible?

EDIT

OK, I 'solved' this (don't like it) by forcing the <App> component to re-render when one of the <Link>s was clicked by creating an unused piece of state on the App component called activeOrderRoute. I passed the setter down to the Header as a prop and connected it as a callback to the onClick handler for each Link that was created in response to the AJAX request. This essentially forces the App to re-render and render the routes, which solved my problems.

Still, that does not seem like the correct way to do it so any help would be appreciated.

Upvotes: 5

Views: 5455

Answers (3)

Drew Reese
Drew Reese

Reputation: 202686

Instead of trying to explicitly render a route for each asynchronously fetched route, leverage the power of react-router-dom and render a dynamic route path string that can handle any catalog page.

Instead of this:

const renderedOrderRoutes = orderRoutes.map(route => {
  return (
    <Route
      path={`/${route.class}/${route.order}`}
      exact
      key={`/${route.class}/${route.order}`}
    >
      <CatalogPage />
    </Route>
  )
})

return (
  <BrowserRouter>
    <Header
      updateOrderRoutes={updateOrderRoutes}
    />
    <Route path="/" exact component={Home} />
    <Route path="/aboutus" exact component={AboutUs} />
    <Route path="/faq" exact component={Faq} />
    <Route path="/register" exact component={Register} />
    {renderedOrderRoutes}
    <Footer />
  </BrowserRouter>
)

Render a single dynamic route in your Router. Use a Switch so only a single route component is matched and rendered. Reorder the routes so the more specific paths can be matched before less specific paths. Now, when a URL has a path that is of the shape "/someClass/someOrder" it can be matched before you try matching any of the more general paths. You will see that the home path ("/") is matched last and the reordering allows us to remove the exact prop on all routes.

return (
  <BrowserRouter>
    <Header updateOrderRoutes={updateOrderRoutes} />
    <Switch>
      <Route
        path="/:class/:order"
        exact
        component={CatalogPage}
      />
      <Route path="/aboutus" component={AboutUs} />
      <Route path="/faq" component={Faq} />
      <Route path="/register" component={Register} />
      <Route path="/" component={Home} />
    </Switch>
    <Footer />
  </BrowserRouter>
)

You may need to adjust some logic in CatalogPage to handle possible undefined catalog data, whatever it is using from the route props/etc... to render catalog stuff.

In your Header component make the asynchronous call there to fetch the routes that can be navigated to so you can dynamically render render links to them (if that is even why you are passing the routes to Header).

Upvotes: 0

Muhammad Taseen
Muhammad Taseen

Reputation: 579

you need to modify your webpack.config.js and add the following lines.

module.exports = {
  devServer: {
    historyApiFallback: true,
  },
  ...

Upvotes: 3

Aditya Rastogi
Aditya Rastogi

Reputation: 386

React router does not directly have routing support for all URLs. It catches the default domain only the remaining routing is done on client side and requests are not served. If your domain is www.mydomain.com, you can not access the URL www.mydomain.com/info directly in the react router. Solutions:

  1. You can use a hash router but that makes the URLs unfriendly for SEO
  2. You can set up a catch-all routes and route it yourself

This link would help you with the same https://ui.dev/react-router-cannot-get-url-refresh/

Upvotes: 1

Related Questions