What I'm trying to do is a search flow in my project using the Reddit API.
Link
that directs the user to the dynamic route: 'r/:subreddit'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};
}
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.
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.