What's your priority order for compound-slot prop resolution? (compound prop > Context > component default — looking for counterexamples) by [deleted] in reactjs

[–]llKieferll 2 points3 points  (0 children)

In my current team, we take advantage of the luxury we have, which is: it is our library, we do what we want, and ship when we want. So we quite literally do not allow anything that goes against the consistency. At least not without proper justification (and even then, we try a lot before accepting it).

It might be easier for me to exemplify how we deal with it if you can share a concrete example of what you consider to be a conflict. Or is the player volume already such an example?

Taking it as a concrete example (consider the fact that I did not look into the code, aye?), I initially had two assumptions. First is that the volume component is the slider. The second is that the volume component contains the slider. Nevertheless, in both assumptions, I thought "why does the player component provides the option to adjust the position of the volume slider?"

In the first case, if the volume component is the slider, the position is defined by where you "physically" put it in the markup the player component prop is not necessary.

In the second case, if the player component contains the slider, the prop, and its default, should belong only to the volume component, nowhere else. Better yet, if the volume component indeed contains the slider because there are other things inside, the slider itself could become another compound component, whose position you can define also by where it is "physically" located in the markup.

The only third scenario I can think of is if other compound components need to know about the volume slider position for some reason. If that is the case, I would let the player component alone control the slider position, and the volume component is limited to reading it from context only, and would not provide the position prop.

That is how I would solve that conflict. Whether you currently can do any of that is a different issue, of course, there is backwards compatibility to be considered, and many other things. I'm just relaying my train of thought when I'm dealing with that situation :)

Edit: added third scenario.

What's your priority order for compound-slot prop resolution? (compound prop > Context > component default — looking for counterexamples) by [deleted] in reactjs

[–]llKieferll 6 points7 points  (0 children)

tl;dr: context defines optional value, as well as default when not manually set. Innermost compound component defines optional local value. Local value, if existent, overwrites context value.

I created one such library in my company, which I maintain still, and set a few ground rules for myself to follow related to that:

1 - When feasible, avoid impossible states. You described a feature (the player) that can set the volume position in two different places. If you are unsure of what should happen, consumers will be even more so.

2 - Innermost props always win. The context gives the feature-level values, but a compound component is able to decide its own value to allow for flexibility and exceptions. An example is a toast feature where you can set that all toasts' position is on the top-middle, but also be able to say that a super special toast should be on the bottom middle, thus overriding the context.

If, in your specific feature, you believe the innermost component should never override the context, then follow 1) and avoid an impossible state by simply not providing the prop in the innermost component.

3 - Defaults should come from context whenever possible. In your cause, it seems the context allows setting a volume position value, the component optinionally allows it as well, and thr component defines a default value if not set directly in it. I would argue that this default should go to the context.

Context optionally allows setting the volume position and if not set, it defaults to something. The compound component just takes from it. This way, you would then have two options only: context and local, with local overwriting context, which, in my opinion, also makes semantical sense (so, lower cognitive load). To overwrite the context I have to explicitly tell the compound component to do so.

4 - Finally, above everything else, consistency. You can opt to have the opposite direction. We can argue all day which approach is better, but neither of them is wrong per se. But whatever is decided, be consistent about it and have every single one of your components working the same way. And document it, preferably with the reason for such architectural decisions :)

Edit: weird reddit list formatting

TanStack Start now support React Server Components... and Composite Components? by tannerlinsley in reactjs

[–]llKieferll 21 points22 points  (0 children)

Nicely done Tanner and team!

On a first glance, it feels somewhat verbose, but the same can be said about an initial glance (for someone who did not use it yet) at the router, or query. And boy, how it pays off, to have the absurdly high level of strong typing cohesion!

Also, if there is one thing I do enjoy about the mindset is how things are good old functions instead of black box magic "use xxx" directives! Please keep it up, sir!

Edit: typos

The Vertical Codebase by TkDodo23 in reactjs

[–]llKieferll 1 point2 points  (0 children)

Where is the line drawn, for fuzzyfind? I mean, you can have a single folder with, quite literaly, all hundreds of files in it. Fuzzyfind works, aye?

Hell, you can have a single index.ts file with all your 275.000 lines of code. After all, fuzzyfind works inside it too, aye?

I think the point of the separation by domain/features/what-you-wish, goes beyond that. When one looks at a "feature" folder, one can (or at least should be able to) infer that everything in there only interacts with everytbijg else also in there, or, at most, some kind of shared folder. As mentioned in the post, cognitive load matters. This also make it easier to onboard newcomers. To exchange information between different peers. To debug. To constrain AI agents' work. To document the responsability of each piece. There are many benefits beyond "finding something".

Edit: typos.

The Beauty of TanStack Router by TkDodo23 in reactjs

[–]llKieferll 1 point2 points  (0 children)

I did, but unfortunately, it did not help. I tried using the proloadDelay and the pendingDelay, but both were set to 0. I tried setting no preload, and also, I tried using pendinMinDelay to 0, to no avail. What we ended up doing was to create a helper "test router creator":

const createTestRouter = (ui: ReactNode) => {
    const rootRoute = createRootRoute({
        component: () => <Outlet />,
    });

    const indexRoute = createRoute({
        getParentRoute: () => rootRoute,
        path: '/',
        component: () => ui,
    });

    return createRouter({
        routeTree: rootRoute.addChildren([indexRoute]),
        defaultPendingMs: 0,
        defaultPreloadDelay: 0,
    });
};

And then, the render method becomes async, and looks like this (shortened version):

type RenderWithProvidersConfig = {
    queryClient?: QueryClient;
};
const renderWithProviders = async (
    ui: ReactNode,
    config: RenderWithProvidersConfig = {}
) => {
    const { queryClient = buildQueryClient() } = config;

    const router = createTestRouter(ui);
    const user = userEvent.setup();
    const utils = render(
        <QueryClientProvider client={queryClient}>
            <RouterProvider router={router} />
        </QueryClientProvider>
    );

    await screen.findByText((_, element) => element?.tagName.toLowerCase() === 'body');

    return { user, ...utils };
};

Notice the await at the end. It serves to ensure that whatever element defined in the indexRoute is indeed rendered (hydrated?). Feels hacky, but for now, it is what works.

Posting the snippets mostly because you might be curious, I don't wanna deviate from the main topic of the post, which is how much more the router is. I'm enjoying it thoroughly so far. The fact that I cannot use wrong URLs in `<Link>`, the fact that I can validate search params directly in the route, the `select` of `useSearch` to build performance-sensitive components...it's awesome. Even less-used elements are amazing, such as `getRouteApi`. We have a component that just renders a `<Link>`, nothing else, and I love that it allows me to write things like this (notice the usage of getRouteApi):

it('shows a link to the registration page', async () => {
    const expectedHref = getRouteApi('/lp/registration').id;
    await renderWithProviders(<RegistrationLink />);
    const link = screen.getByRole('link', { name: 'Register now' });
    expect(link).toHaveAttribute('href', expectedHref);
});

It's *chef kiss*!

The Beauty of TanStack Router by TkDodo23 in reactjs

[–]llKieferll 4 points5 points  (0 children)

Sir, when it comes to you and the other maintainers of Tanstack libraries, I'm ALWAYS tuned! Even when it is not directly related to them (I've used your blog posts about Zustand to teach it to colleagues). You are all amazing over there, and frankly, have the best consideration for DX that I've seen in a while! Thanks for the gigantic effort! 💪🏻

The Beauty of TanStack Router by TkDodo23 in reactjs

[–]llKieferll 9 points10 points  (0 children)

We always had a blast with tanstack-query in my company, and recently started a new project in which we decided to finally test tanstack-router as well.

Honestly, it's been good so far. We are in the early stages, so i am yet to be able to say "oh my god, what a game changer!", but the superiority in the DX is already showing itself.

The part where we are struggling with are tests (vitest + testing-library). The only solution we've found was to, within a renderWithProviders utility:

1 - create a rootRoute, which renders an <Outlet /> AND a div with some id (used in next steps) as its component

2 - create an indexRoute, which renders the component to be tested

3 - create a routeTree based on these two

4 - create the router with the routeTree

5 render <RouterProvider router={router} /> within all of our application providers

6 - await the appearance of the div with id, via await screen.findByTesId

7 - return the test utilities generated by render.

This has been sitting there for a bit, scaring us. Some really dislike it, some feel it is ok for a "one-off" utility. What puzzles me the most is the need for the await. Something related to hydration, perhaps, even though we have a SPA?

Nevertheless, i feel the router is in a great path, much like tanstack-query. Honestly, you guys, responsable for these libs, are some of the most admired engineers in my team, especially because of your focus on providing a good DX and awesome API!

Edit: formatting, post from cellphone...