I’m having a few issue with React composition. For isntance:
const FormActions: React.FC<any> = () => {
return (
<Fab position="right-bottom" slot="fixed" color="pink">
<Icon f7="question"></Icon>
<FabButtons position="top">
<FabButton label="Show Errors">
<Icon f7="rays"></Icon>
</FabButton>
</FabButtons>
</Fab>
);
}
<Page name="multiStepDomainFormPage">
<FormActions />
</Page>
The FAB is placed in page content area.
But if I change it to this:
<Page name="multiStepDomainFormPage">
<div slot="fixed"><FormActions /></div>
</Page>
The the FAB is inserted inside a div which is a direct child of “page” right before “page-content” which is correct.
This happens to other elements as well when using composition. If I extract ListItem to a composable component then I must manually wrap it with ul element.
Is there a explanation for this, and what are the best practices for React composition.
When you use custom component F7 can’t detect what is inside. It is by design, just use slots:
<Page name="multiStepDomainFormPage">
<FormActions slot="fixed" />
</Page>
Thanks, that works.
Some components do not have slots.
const InputController: React.FC<any> = () => {
return (
<ListInput
... />
);
}
<List>
<InputController />
</List>
This render without ul element. Currently I am doing this to compensate:
<List>
<ul>
<InputController />
</ul>
</List>
Would you do it similarly or is there another way?
I’ve just come across this behaviour. The problem with this is that you can’t then control the order of the items in a list if you are mixing custom components with standard ListItem components.
This really breaks composability.
I just worked out how to get around this somewhat, in case anyone else finds this:
I changed my custom component to be an Input component, then wrapped that in a ListInput component with input={false} in my main List component.
I’ve found that this works for me:
export const SlotItem: React.FC<{ slot: string, renderFn: () => any }> = (props) => props.renderFn();
Then just use slot ‘list’.
I had the need to render a component that wrapped a ListItem and to pass the slot items as components themselves.
Passing it as children did not work as children is an array. So I ended up with the following pattern that did the job:
interface IProps extends ListItemProps {
slotRenderers?: { [slot: string]: () => JSX.Element } // MUST be object literal
}
export const SomeListItemWrapper: FC<IProps> = (props) => {
return <ListItem
{...props}
>
{
props.slotRenderers?.['subtitle'] &&
<span slot="subtitle">{props.slotRenderers?.['subtitle']()}</span>
}
// repeat for all the slots on ListItem
</ListItem>;
}
Note that I passed slotRenderers as an object literal and not an array. Passing it as an array does not work.