Is there an idiomatic way for creating dependency injecting components?

For context, I am developing a UI library (not necessarily headless), and I want to give users the ability to customize how certain inner workings of components (e.g. things that normally would be incapsulated, such as inner elements of components) render. And following are the examples. Context-based approach That's how Kobalte and Corvu do it. The biggest disadvantage for me is that it's not type safe and requires runtime assertions.
const App: Component = () => {
return (
<Button.Root as="button">
<Button.Icon as="div"><Icon name="whatever" /></Button.Icon>
<Button.Label as="span">Hello world!</Button.Label>
</Button.Root>
);
}
const App: Component = () => {
return (
<Button.Root as="button">
<Button.Icon as="div"><Icon name="whatever" /></Button.Icon>
<Button.Label as="span">Hello world!</Button.Label>
</Button.Root>
);
}
Injecting through properties
const App: Component = () => {
// Button1 / Button2 are examples of base / finished components,
// I'd prefer Button1 for finished, styled components
// and Button2 for unstyled ones

// So Button1 could be using Button2 under the hood
return (
<div>
<Button1
icon={<Icon name="whatever" />}
label="Hello world!" />
<Button2
renderIcon={(props) => <div class="icon" {...props} />}
renderLabel={(props) => <span class="label" {...props} />} />
</div>
);
}
const App: Component = () => {
// Button1 / Button2 are examples of base / finished components,
// I'd prefer Button1 for finished, styled components
// and Button2 for unstyled ones

// So Button1 could be using Button2 under the hood
return (
<div>
<Button1
icon={<Icon name="whatever" />}
label="Hello world!" />
<Button2
renderIcon={(props) => <div class="icon" {...props} />}
renderLabel={(props) => <span class="label" {...props} />} />
</div>
);
}
Hooks I can't remember any real world examples but that's what it roughly should look like I guess... The hook could return helper functions for some components as well.
const App: Component = () => {
// I have no idea what shape
// should the return object have
const { Button } = createButton({
icon: (props) => /* ... */,
label: (props) => /* ... */,
});
return (
<Button
icon={<>...</>}
label="..." />
);
}
const App: Component = () => {
// I have no idea what shape
// should the return object have
const { Button } = createButton({
icon: (props) => /* ... */,
label: (props) => /* ... */,
});
return (
<Button
icon={<>...</>}
label="..." />
);
}
For each of the following examples I want to ask a few questions: a) Does it actually work flawlessly with Solid's reactivity? b) How idiomatic is it? c) Is it reliable to be used in production?
1 Reply
bigmistqke
bigmistqke•2mo ago
a. Yes b. I would consider all options to be idiomatic c. 💯
the biggest disadvantage for me is that it's not typesafe
Ye... it's a bummer that you cannot type the children of jsx components. I feel these type of compound components are a very established pattern though and communicate the intent well imo.

Did you find this page helpful?