SPFx React Components: Container vs Presentational
How to apply container/presentational separation in SPFx for testable, maintainable code.
Why separate?
- Presentational components: receive data via props, render UI, stateless.
- Container components: orchestrate data fetching, state, and actions.
This separation reduces coupling, improves testability, and simplifies reuse.
Presentational example
type UserCardProps = { name: string; email?: string; avatarUrl?: string };
export function UserCard({ name, email, avatarUrl }: UserCardProps) {
return (
<div className="user-card">
{avatarUrl && <img src={avatarUrl} alt="" aria-hidden="true" />}
<div>
<strong>{name}</strong>
{email && <div className="muted">{email}</div>}
</div>
</div>
);
}
Container example (SPFx)
import { useCancelableFetch } from '../hooks/useCancelableFetch';
type Props = { userId: string };
export function UserCardContainer({ userId }: Props) {
const { data, loading, error } = useCancelableFetch(`/api/users/${userId}`);
if (loading) return <div>Loading…</div>;
if (error) return <div role="alert">Failed to load user</div>;
return <UserCard name={data.name} email={data.mail} avatarUrl={data.photo} />;
}
Anti‑Patterns
- Presentational components performing async calls or reading global state directly.
- Containers leaking implementation details via overly specific props.
- Tight coupling to SPFx context (
this.context) inside presentational components.
Testing strategy
- Test
UserCardwith snapshots and accessibility checks. - Test
UserCardContainerwith mocked fetch/store.
SPFx note
Keep SPFx Context usage at the container layer; pass only data and callbacks down.