I have a <Background />
element upstream of the child pages.
const myRouter = createBrowserRouter([
{
path: '/',
element: <ParentLayout />,
children: [
{
path: '/red-page',
element: <RedPage />,
},
{
path: '/green-page',
element: <GreenPage />,
},
],
},
]);
// ParentLayout.tsx
export const ParentLayout = ({ bgColor }) => {
return (
<>
<Background color={bgColor} />
<Outlet />
</>
);
};
Each child pages should be able to set the background color. What is the best way to achieve that?
(Please keep in mind that it is a toy example, the goal is not really to set the background color, which could be obviously done with a quick DOM/css hack)
Ideal solution
Any child could somehow declare that they need a bgColor="red"
. React would then proceed to render ParentLayout
and the child page.
Issue: Children are rendered after ParentLayout
, so it is probably not possible.
Working solution: Using loaders
This works ok, but it has two cons:
- We are hacking the loader’s primary raison d’être (async data loading).
- The declaration of background color is taken out of the child component (violates a bit the SoC principle)
const myRouter = createBrowserRouter([
{
path: '/',
element: <ParentLayout />,
children: [
{
path: '/red-page',
element: <RedPage />,
loader: () => ({ bgColor: 'red' }),
},
{
path: '/green-page',
element: <GreenPage />,
loader: () => ({ bgColor: 'green' }),
},
],
},
]);
// ParentLayout.tsx
export const ParentLayout = () => {
const { bgColor } = useLoadedData()
return (
<>
<Background color={bgColor} />
<Outlet />
</>
);
};
Working solution: Use useEffect / useLayoutEffect
// ParentLayout.tsx
export const ParentLayout = () => {
const {bgColor} = useSomeStorageOrAContext();
return (
<>
<Background color={bgColor} />
<Outlet />
</>
);
};
// RedPage.tsx
export const RedPage = () => {
const {setBgColor} = useSomeStorageOrAContext();
useLayoutEffect(() => {
setBgColor('red')
}, [])
return (
<div>My Red Page</div>
);
};
This approach has the benefit of keeping the route declaration pure, and leaving to the children the choice to modify the background themselves.
Cons:
- The
ParentLayout
will be rendered twice. This can possibly be alleviated by usinguseLayoutEffect
to prevent the first unnecessary painting. But it still seems unnecessarily heavy. - Such imperative approach is meh. I would love a declarative approach.
Is there a cannonical approach for that?