Search code examples
remixremix.run

Remix how to reuse routes


Just starting with Remix and followed the blog tutorial

In it, we setup a route for posts.$slug to display a post

export const loader = async ({ params }: LoaderArgs) => {
    invariant(params.slug, `params.slug is required`);

    const post = await getPost(params.slug);
    invariant(post, `Post not found: ${params.slug}`);

    const html = marked(post.markdown);
    return json({ post, html });
};

export default function PostSlug() {
    const { post, html } = useLoaderData<typeof loader>();
    return (
        <main className="mx-auto max-w-4xl">
            <h1 className="my-6 border-b-2 text-center text-3xl">
                {post.title}
            </h1>
            <div dangerouslySetInnerHTML={{ __html: html }}></div>
        </main>
    );
}

With it's own loader and display

Then we have an admin route with an <Outlet />

t default function PostAdmin() {
    const { posts } = useLoaderData<typeof loader>();

    return (
        <div className="mx-auto max-w-4xl">
            <h1 className="my-6 mb-2 border-b-2 text-center text-3xl">
                Blog Admin
            </h1>
            <div className="grid grid-cols-4 gap-6">
                <nav className="col-span-4 md:col-span-1">
                    <ul>
                        {posts.map((post) => (
                            <li key={post.slug}>
                                <Link
                                    to={post.slug}
                                    className="text-blue-600 underline"
                                >
                                    {post.title}
                                </Link>
                            </li>
                        ))}
                    </ul>
                </nav>
                <main className="col-span-4 md:col-span-3">
                    <Outlet />
                </main>
            </div>
        </div>
    )
}

But if I click on a post, it shows 404 (route is not handled)

Now I can duplicate posts.$slug.tsx into posts.admin.$slug.ts but then I have 2 places to maintain

I know I can create a component

export default PostView({post, html}) {
    const { post, html } = useLoaderData<typeof loader>();
    return (
        <main className="mx-auto max-w-4xl">
            <h1 className="my-6 border-b-2 text-center text-3xl">
                {post.title}
            </h1>
            <div dangerouslySetInnerHTML={{ __html: html }}></div>
        </main>
    );
}

But that what about the route loader ?

I feel I'm missing a concept, any pointers would be appreciated :)


Solution

  • This is common in software development. You're developing a new feature and it looks similar to an existing feature. The temptation to share the code is strong because we've been told many times: DRY (Don't Repeat Yourself).

    Well, try to avoid that templation as long as possible.

    With Remix, you should consider loaders and actions as well as the UI component as very specific parts of your app. They should be hyper-focused on that part of your route tree. No more, no less.

    That's not to say you shouldn't extract common parts to functions that you call, like getPost, updatePost, etc. But you most likely will never need to reuse loaders.

    Same with UI components. You can create reusable components for common functionality. But your route component is specific to your route since it is generally dependent on your loader data as well. You can pass that loader data as props to your shared UI as needed.

    Here's a great article by Kent C. Dodds https://kentcdodds.com/blog/aha-programming

    AHA: Avoid Hasty Abstractions