Mapping props to attributes

A common pattern that comes up for me is creating components that expose the html attributes of their underlying element, while merging in their own values and defaults. For example something like this (this is not expected to work - just an example):
// MyButton.tsx
type Props = {foo:string} & JSX.HTMLAttributes<HTMLButtonElement>
const defaults = { foo:"bar", class:"my-button" };
const MyButton:ParentComponent<Props>(incoming) {
// I want to merge the classes/style of the incoming props
// rather than overwrite the defaults, but how to do that safely?
// Effectively I'm asking how to write my own mergeProps function but
// But I'm not sure A) that's a good idea, B) I understand enough about
// props in SolidJS to do that without making a mess...
const merged = mergeProps(defaults, incoming);
const [local, attrs] = splitProps(merged, ["children","foo"])
return <button {...attrs}>
<div class={`button-inner ${local.foo}}`>
{props.children}
</div>
</button>
}

// App.tsx
const App() {
return <MyButton class="a-class-to-be-merged" foo="bar">Hey!</MyButton>
}
// MyButton.tsx
type Props = {foo:string} & JSX.HTMLAttributes<HTMLButtonElement>
const defaults = { foo:"bar", class:"my-button" };
const MyButton:ParentComponent<Props>(incoming) {
// I want to merge the classes/style of the incoming props
// rather than overwrite the defaults, but how to do that safely?
// Effectively I'm asking how to write my own mergeProps function but
// But I'm not sure A) that's a good idea, B) I understand enough about
// props in SolidJS to do that without making a mess...
const merged = mergeProps(defaults, incoming);
const [local, attrs] = splitProps(merged, ["children","foo"])
return <button {...attrs}>
<div class={`button-inner ${local.foo}}`>
{props.children}
</div>
</button>
}

// App.tsx
const App() {
return <MyButton class="a-class-to-be-merged" foo="bar">Hey!</MyButton>
}
So my questions are: 1. Is this is a solved problem in Solid? I'm pretty new to the framework so I don't want to reinvent the wheel if there's an established idiom/library for this. 2. I'm not exactly clear on how the reactivity of props works. They don't seem to be a signal, so are they a store or do they have their own logic? When, where and how can they be modified? Should I wrap the guts of the component in a createEffect and expose the modified props through a signal?
3 Replies
Ghirigoro
GhirigoroOP8mo ago
P.S. - This is what I'm using currently:
export function createProps<
P extends Record<string, any>,
K extends keyof P,
A extends Exclude<P, K>
>(opts: { props: P; defaults?: Partial<P>; propKeys?: K[] }) {
const { props, defaults = {} as DefaultProps<P>, propKeys = [] } = opts;
const [_props, _setProps] = createSignal<P>({} as P);
const [_attrs, _setAttrs] = createSignal<A>({} as A);
createEffect(() => {
const defaultStyle: any = (defaults as any)["style"]
? (defaults as any)["style"]
: {};
const defaultClass: any = (defaults as any)["class"]
? (defaults as any)["class"]
: "";

const incomingStyle: any = props["style"] ? props["style"] : {};
const incomingClass: any = props["class"] ? props["class"] : "";

const classAttr = classes(defaultClass, incomingClass);
const styleAttr = Object.assign({}, defaultStyle, incomingStyle);

const p = mergeProps(defaults, props, {
class: classAttr,
style: styleAttr
});
const s = splitProps(p, propKeys);

_setProps(s[0] as any);
_setAttrs(s[1] as any);
});
return { props: _props, attrs: _attrs };
}
export function createProps<
P extends Record<string, any>,
K extends keyof P,
A extends Exclude<P, K>
>(opts: { props: P; defaults?: Partial<P>; propKeys?: K[] }) {
const { props, defaults = {} as DefaultProps<P>, propKeys = [] } = opts;
const [_props, _setProps] = createSignal<P>({} as P);
const [_attrs, _setAttrs] = createSignal<A>({} as A);
createEffect(() => {
const defaultStyle: any = (defaults as any)["style"]
? (defaults as any)["style"]
: {};
const defaultClass: any = (defaults as any)["class"]
? (defaults as any)["class"]
: "";

const incomingStyle: any = props["style"] ? props["style"] : {};
const incomingClass: any = props["class"] ? props["class"] : "";

const classAttr = classes(defaultClass, incomingClass);
const styleAttr = Object.assign({}, defaultStyle, incomingStyle);

const p = mergeProps(defaults, props, {
class: classAttr,
style: styleAttr
});
const s = splitProps(p, propKeys);

_setProps(s[0] as any);
_setAttrs(s[1] as any);
});
return { props: _props, attrs: _attrs };
}
REEEEE
REEEEE8mo ago
For attributes you should make use of mergeProps if you want to supply defaults for possibly undefined attributes. Otherwise just spreading them should be fine. For classes, you'd generally use a utility library like clsx Also props in Solid are transformed by Solid's compiler step into object getters
Ghirigoro
GhirigoroOP8mo ago
🙏
Want results from more Discord servers?
Add your server