Search code examples
reactjsreact-router-dom

How do I avoid React Router Link going to the root route '/' before going to the dynamic route?


What I'm trying to do is a search flow in my project using the Reddit API.

  1. User does a search
  2. The Web App returns Subreddits that include the query searched
  3. Every Subreddit component has a Link that directs the user to the dynamic route: 'r/:subreddit'
  4. The loader specified in that route uses window.location.pathname to gather the name of the subreddit (example: r/autism) and do a fetch to return the recent posts from that subreddit.

However, the problem I'm encountering is that for some reason, React-Router first changes the pathname to '/' and then to '/r/:subreddit', so the Loader when executed, uses the pathname '/' while fetching, and returns the results.

Below is the code for the three different files as well as the Codepen link if you rather see the chunks of code there.

main.jsx

const router = createBrowserRouter(
  [
    {
      path: '/',
      element: <App />,
      errorElement: <ErrorPage />,
      children: [
        {
          errorElement: <ErrorPage />,
          children: [
            {
              index: true,
              element: <Feed />,
              loader: indexLoader,
            },
            {
              path: 'r/:subreddit/comments/:id/:title',
              element: <Post />,
              action: commentAction,
            },
            {
              path: 'news',
              element: <Feed />,
              loader: newsLoader,
            },
            {
              path: 'tech',
              element: <Feed />,
              loader: techLoader,
            },
            {
              path: 'sports',
              element: <Feed />,
              loader: sportsLoader,
            },
            {
              path: 'astronomy',
              element: <Feed />,
              loader: spaceLoader,
            },
            {
              path: 'science',
              element: <Feed />,
              loader: scienceLoader,
            },
            {
              path: 'gaming',
              element: <Feed />,
              loader: gamingLoader,
            },
            {
              path: 'r/:subreddit',
              element: <Feed />,
              loader: subredditLoader
            } 
          ]
        },
      ],
    },
  ]

Subreddit.tsx

export default function Subreddit ({ payload }) {
    useHTMLText(payload.public_description_html, payload.id);
    //const url = `https://www.reddit.com/${payload.display_name_prefixed}.json?raw.json=1`;

    return (
        <section className="subreddit">
                <img src={payload.banner_img} className="subreddit-banner" style={{
                    backgroundColor: payload.banner_background_color
                }}/>
                
                <div className="subreddit-identity">
                    <img src={payload.icon_img} className="subreddit-icon" style={{
                        backgroundColor: payload.key_color,
                        borderRadius: "50%",
                        width: 64,
                        height: 64
                    }}/>
                    <h3>{payload.display_name_prefixed}</h3>
                    <a href={`https://www.reddit.com${payload.url}`} target="_blank" className="subreddit-button"><img src={open_tab_white as unknown as string} alt="Open subreddit in another tab" aria-label="Open subreddit in another tab"/></a>
                </div>
                
                <Link to={payload.display_name_prefixed}>
                    <p id={payload.id}></p>
                    <p style={{color: "#656869", textAlign: "center"}}>{formatAmount(payload.subscribers)} members</p>
                </Link>
        </section>
    )
}

loaders.ts

export async function subreddit() {
    const pathname = window.location.pathname;
    const url = `https://www.reddit.com${pathname}.json?raw.json=1`;
    const feed = await fetchHandler(url);
    const elements = redditFilter(feed);
    
    
    return {elements, url, pathname};
}

Codepen Collection

So far, what I've tried is using setTimeout inside the loader to wait a second so the route changes from '/' to '/r/:subreddit' but it doesn't do anything, it says you either have to return something or return null. Also, I don't think that would be a good practice.

The component that renders the posts is called Feed. It's a pure function using useLoader() to render the articles/posts returned by the loaders using fetch. This would be the same for this particular case, User clicks a Subreddit -> Subreddit Link sends user to dynamic route -> Dynamic Route Loader uses URL pathname inside fetch and returns articles/posts -> Feed uses useLoader() and renders the data returned.

That being said, I also tried using the useEffect hook in the Feed component but there must be two cases, whether Feed has something using useLoader() or it doesn't and makes a fetch with a retrieved window.location.pathname. However, I think this is adding a lot of complexity to something that should be simple. (also it didn't work)

The only thing I need is for the Loader to receive the pathname at once instead of first getting '/' and then the expected pathname.

Solution: Thanks to Alexander, I just implemented what React offers (instead of trying to use the window API directly).

    export async function subreddit({ request }) {
    
    const currentURL = new URL(request.url);
    const pathname = currentURL.pathname;
    const url = `https://www.reddit.com${pathname}.json?raw.json=1`;
    const feed = await fetchHandler(url);
    const elements = redditFilter(feed);
    
    
    return {elements, url, pathname};
}

I just took the request object passed into the Loader, made it a new URL, and then used the pathname property to access the string I wanted.


Solution

  • This example is taken from the documentation. This shows how you should access dynamic path parameters in your loader using the params argument.

    createBrowserRouter([
      {
        path: "/teams/:teamId",
        loader: ({ params }) => {
          return fakeGetTeam(params.teamId);
        },
      },
    ]);
    

    As a side note, you may want to look into using the request argument as described here:

    function loader({ request }) {
      const url = new URL(request.url);
      const searchTerm = url.searchParams.get("q");
      return searchProducts(searchTerm);
    }
    

    When using this pattern, you can for example use a form submission to navigate the user to the correct route with some query parameters, like r/frontend?title=react, if that's something you want to do.