A cursed way to have vue-like slots api in SolidJS.
This library does not use Portal
s, and provides a ssr-friendly slots api.
When designing compound components our api is sometimes at odds with underlying layout. Consider a layout component, where we want to put the header and the content into different elements. E.g. we want html like this:
<div>
<h1>Title</h1>
<main>
<p>Info</p>
<p>Extra info</p>
</main>
</div>
This library allows us to provide an api like this:
<Layout>
<Header>Title</Header>
<p>Info</p>
<p>Extra info</p>
</Layout>
Here, we extract our title from our children, and put it outside of <main>
tag.
This is done via core primitive resolveSlots
:
const Header = createSlot();
function Layout(props) {
const [header, children] = resolveSlots(() => props.children, Header);
return <div>
<h1>{header()}</h1>
<main>{children()}</main>
</div>;
}
And that's it.
This library abuses some implementation details of SolidJS rendering to implement this nice api. These implementation details are unlikely to change, but beware.
The api of the library has settled general direction, but is expected to change in minor details. There are some non-trivial uses that are possible, but not yet supported.
The core idea of the library is slots - it is the component that can be introspected.
They are created via createSlot
function:
const Header = createSlot();
const Footer = createSlot();
const Aside = createSlot();
The returned slots are solid components, that just render their children:
<Header>
<b>Hello</b> World
</Header>
Renders as just <b>Hello</b> World
.
But they have a superpower to be introspected.
In your consuming component, you call resolveSlots
, to extract slotted components from your slots.
function Layout(props) {
const [header, footer, children] = resolveSlots(() => props.children, Header, Footer);
// ...
}
Here resolveSlots
traverses the props.children
.
It collects all children that are Header
into header()
accessor,
and all children that are Footer
into footer()
accessor.
All other children are put into the children()
accessor.
Note, how we ignore <Aside>
slot.
If the user passes an unknown slot, resolveSlots
puts it into children
.
The question is, when will you see a slot in you children. It is a non-trivial question, and I will not give a definitive answer in all cases.
Here is a common list of examples:
const MyHeader = (props) => <Header>My {props.children}</Header>;
<Layout>
<Header>Yes</Header>
<MyHeader>Yes</MyHeader>
<><Header>Yes</Header></>
<div><Header>No</Header></div>
<Show when={cond}>
<Header>Probably</Header>
</Show>
<For each={[1, 2, 3]}>{() => <>
<Header>Probably</Header>
</>}</For>
</Layout>
On top of that, any call to children
helper from solid-js
, or to resolveSlots
expands all slots.
E.g.
import {children} from "solid-js";
const resolved = children(() => props.children);
// Slot will never be seen, as `children` has removed all slots machinery
const [slot, rest] = resolveSlots(resolved, Slot);