SPFx React Components: Container vs Presentational

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 UserCard with snapshots and accessibility checks.
  • Test UserCardContainer with mocked fetch/store.

SPFx note

Keep SPFx Context usage at the container layer; pass only data and callbacks down.