'Make react-router-dom v6 pass path as key to rendered element
I think I may need a paradigm shift in my thinking here, so I'm open to those kinds of answers as well here.
Consider the following simplified example:
export const App = (p: { apiClient: SomeApiClient }) => {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/posts/:postId" element={<Post apiClient={p.apiClient} />} />
</Routes>
);
}
export const Home = () => {
return <h1>Home!</h1>
}
export const Post = (p: { apiClient: SomeApiClient }) => {
const { postId } = useParams();
const [ state, setState ] = useState<PostState>({ status: "loading" });
// When the component mounts, get the specified post from the API
useEffect(() => {
if (state.status === "loading") {
(async () => {
const post = await p.apiClient.getPost(postId);
setState({ status: "ready", post });
})();
}
})
return (
<h2>Posts</h2>
{
state.status === "loading"
? <p>Loading....</p>
: <div className="post">
<h3>{state.post.title}</h3>
<div className="content">{state.post.content}</div>
</div>
}
)
}
export type PostState =
| { status: "loading" }
| { status: "ready"; post: BlogPost };
export type BlogPost = { title: string; content: string };
This works fine the first time, but pretend there's a <Link /> on the page that goes to the next post. When I click that link, the URL changes, but the page content doesn't, because React Router is not actually re-mounting the <Post .../> component. That component correctly receives the updated postId and is re-rendered, but since it doesn't get re-mounted, the useEffect logic doesn't run again and the content stays the same.
I've been solving this very awkwardly by creating intermediary components like so:
export const App = (p: { apiClient: SomeApiClient }) => {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/posts/:postId" element={<PostRenderer apiClient={p.apiClient} />} />
</Routes>
);
}
export const PostRenderer = (p: { apiClient: SomeApiClient }) => {
const { postId } = useParams();
return <Post key={postId} postId={postId} apiClient={p.apiClient} />
}
export const Post = (p: { postId: string; apiClient: SomeApiClient }) => {
// ....
}
But I'm starting to get a lot of those, and literally all they do is take the param from the URL and use it as a key on the actual target component. I've read through the react-router-dom docs and am not finding anything that indicates there's a way to automate this. I must be thinking about this wrong.... Any suggestions are appreciated.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
