S
SolidJS•5mo ago
flippyflops

Is there a way to enforce children of a particular component type?

I'm wanting to make a component that only accepts children that are a specific component so for example:
// Valid
<CodeGroup>
<Code label="Ctrl" />
<Code label="M" />
</CodeGroup>

// Invalid
<CodeGroup>
<div>hi</div>
</CodeGroup>
// Valid
<CodeGroup>
<Code label="Ctrl" />
<Code label="M" />
</CodeGroup>

// Invalid
<CodeGroup>
<div>hi</div>
</CodeGroup>
I tried playing around with FlowProps and FlowComponent but couldn't quite get it to work. I think I'm pretty close but I'm not sure how I can allow passing the result of calling <Code /> while also enforcing the children are of type Code. So as a prop, items: (typeof Code)[] for example allows me pass in Code but not <Code />.
6 Replies
lxsmnsyc
lxsmnsyc•5mo ago
There isn't really. TypeScript's JSX inference is very limited, and if you plan for runtime checks, it's impossible. What you could do is, instead of JSX children, you could just accept an array of objects
flippyflops
flippyflopsOP•5mo ago
I was starting to think this wasn't possible in SolidJS, kind of bummed about it but as long as there is a way to enforce using props that would work too
export type CodeGroupProps = {
codes: string[];
};

export function CodeGroup(props: CodeGroupProps) {
return (
<div class={css.codeGroup}>
<For each={props.codes}>{(code) => <Code label={code} />}</For>
</div>
);
}
export type CodeGroupProps = {
codes: string[];
};

export function CodeGroup(props: CodeGroupProps) {
return (
<div class={css.codeGroup}>
<For each={props.codes}>{(code) => <Code label={code} />}</For>
</div>
);
}
This works as well but I guess its just a matter of preferred ergonomics. I saw Material UI implementation in solid uses children as the API for things like this, I just haven't checked if they do any enforcing of children type (I'm guessing not?) 🤔 Do you mind giving me an example of when one might use FlowProps? I thought it was for the case I mentioned.
lxsmnsyc
lxsmnsyc•5mo ago
Maybe
export type CodeGroupProps = {
children: string[];
};

export function CodeGroup(props: CodeGroupProps) {
return (
<div class={css.codeGroup}>
<For each={props.codes}>{(code) => <Code label={code} />}</For>
</div>
);
}

<CodeGroup>
{['Ctrl', 'M']}
</CodeGroup>
export type CodeGroupProps = {
children: string[];
};

export function CodeGroup(props: CodeGroupProps) {
return (
<div class={css.codeGroup}>
<For each={props.codes}>{(code) => <Code label={code} />}</For>
</div>
);
}

<CodeGroup>
{['Ctrl', 'M']}
</CodeGroup>
bigmistqke
bigmistqke•5mo ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
Total TypeScript
You Can't Make Children "Type Safe" in React & TypeScript
Learn why it's not possible to restrict the type of children in React components with TypeScript.
Paul Armstrong
Paul Armstrong•5mo ago
You could look at the jsx-tokenizer from solid-primitives. It doesn't exactly do what you're asking, but it might help enforce the pattern https://primitives.solidjs.community/package/jsx-tokenizer
Solid Primitives
A library of high-quality primitives that extend SolidJS reactivity
Paul Armstrong
Paul Armstrong•5mo ago
From their examples:
import { createToken, resolveTokens } from "@solid-primitives/jsx-tokenizer";

function Tabs<T>(props: { children: (Tab: Component<{ value: T }>) => JSX.Element; active: T }) {
const Tab = createToken((props: { value: T }) => props.value);
// resolveTokens will look for tokens created by Tab component
const tokens = resolveTokens(Tab, () => props.children(Tab));
return (
<ul>
<For each={tokens()}>
{token => <li classList={{ active: token.data === props.active }}>{token.data}</li>}
</For>
</ul>
);
}

// usage
<Tabs active="tab1">
{Tab => (
<>
<Tab value="tab1" />
<Tab value="tab2" />
</>
)}
</Tabs>;
import { createToken, resolveTokens } from "@solid-primitives/jsx-tokenizer";

function Tabs<T>(props: { children: (Tab: Component<{ value: T }>) => JSX.Element; active: T }) {
const Tab = createToken((props: { value: T }) => props.value);
// resolveTokens will look for tokens created by Tab component
const tokens = resolveTokens(Tab, () => props.children(Tab));
return (
<ul>
<For each={tokens()}>
{token => <li classList={{ active: token.data === props.active }}>{token.data}</li>}
</For>
</ul>
);
}

// usage
<Tabs active="tab1">
{Tab => (
<>
<Tab value="tab1" />
<Tab value="tab2" />
</>
)}
</Tabs>;
Want results from more Discord servers?
Add your server