SPFx React Components: Composition Patterns
Composition patterns for SPFx components: children-as-API, render props, and light HOCs.
Best practices for composing SPFx components using children, slots, and render props.
Children as API
Use children to let consumers place arbitrary content.
type PanelProps = { title: string; children?: React.ReactNode };
export function Panel({ title, children }: PanelProps) {
return (
<section className="panel">
<h3>{title}</h3>
<div className="panel-body">{children}</div>
</section>
);
}
Named slots via props
Expose specific regions as optional props to avoid prop bloat.
type CardSlotsProps = {
header?: React.ReactNode;
footer?: React.ReactNode;
children?: React.ReactNode;
};
export function CardSlots({ header, footer, children }: CardSlotsProps) {
return (
<article className="card">
{header}
<div className="card-body">{children}</div>
{footer}
</article>
);
}
Render props
Pass a function to render based on internal state or data.
type DataProviderProps<T> = { url: string; children: (data: T | null) => React.ReactNode };
export function DataProvider<T>({ url, children }: DataProviderProps<T>) {
const { data, loading } = useCancelableFetch(url);
if (loading) return <div>Loading…</div>;
return <>{children(data)}</>;
}
Light HOCs
Prefer small wrappers that add a single capability rather than deep inheritance.
export function withThemeClass<T extends object>(Comp: React.ComponentType<T>) {
return function Wrapped(props: T) {
const themeClass = 'theme-default'; // could read from context
return <div className={themeClass}><Comp {...props} /></div>;
};
}
Anti‑Patterns
- Deep component trees with unclear data flow.
- Overusing HOCs leading to wrapper hell.
- Passing functions/JSX via props when a simpler
childrensolution suffices.
SPFx composition examples
- Wrap web part content with
WebPartHeader+Panel. - Provide data via
DataProviderand render presentational component. - Keep SPFx context outside presentational components; pass minimal props only.