Chaye Novak
โ† Back to Blog
ReactJavaScriptPatterns

10 React Patterns Every Developer Should Know

ยท3 min read

After years of building React applications, I've noticed that a handful of patterns come up again and again. Mastering these will make you a significantly better React developer.

1. Container / Presentational Pattern

Separate data-fetching logic from rendering:

// Container: handles data
function UserContainer() {
  const [user, setUser] = useState<User | null>(null);
  useEffect(() => { fetchUser().then(setUser); }, []);
  if (!user) return <Spinner />;
  return <UserCard user={user} />;
}

// Presentational: pure rendering
function UserCard({ user }: { user: User }) {
  return <div>{user.name}</div>;
}

2. Custom Hooks

Extract stateful logic into reusable hooks:

function useWindowSize() {
  const [size, setSize] = useState({ width: 0, height: 0 });
  useEffect(() => {
    const update = () =>
      setSize({ width: window.innerWidth, height: window.innerHeight });
    window.addEventListener("resize", update);
    update();
    return () => window.removeEventListener("resize", update);
  }, []);
  return size;
}

3. Compound Components

Build flexible component APIs:

function Tabs({ children }: { children: React.ReactNode }) {
  const [active, setActive] = useState(0);
  return (
    <TabContext.Provider value={{ active, setActive }}>
      {children}
    </TabContext.Provider>
  );
}

Tabs.List = TabList;
Tabs.Panel = TabPanel;

Usage: <Tabs><Tabs.List /><Tabs.Panel /></Tabs>

4. Render Props

Pass rendering control to the parent:

function MouseTracker({ render }: { render: (pos: Position) => React.ReactNode }) {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  return (
    <div onMouseMove={(e) => setPos({ x: e.clientX, y: e.clientY })}>
      {render(pos)}
    </div>
  );
}

5. Error Boundaries

Gracefully handle component errors:

class ErrorBoundary extends React.Component {
  state = { hasError: false };
  static getDerivedStateFromError() { return { hasError: true }; }
  render() {
    if (this.state.hasError) return <div>Something went wrong.</div>;
    return this.props.children;
  }
}

6. Lazy Loading with Suspense

const HeavyChart = React.lazy(() => import("./HeavyChart"));

function Dashboard() {
  return (
    <Suspense fallback={<Spinner />}>
      <HeavyChart />
    </Suspense>
  );
}

7. Controlled vs Uncontrolled Inputs

Prefer controlled inputs for form validation; use uncontrolled only when needed for performance.

8. Memoization

Use React.memo, useMemo, and useCallback wisely โ€” but only when you have a measured performance problem.

9. Context + useReducer for State Management

For medium-complexity state, skip Redux and reach for useReducer with Context:

const [state, dispatch] = useReducer(reducer, initialState);

10. Portals

Render modals and tooltips outside the main DOM hierarchy:

ReactDOM.createPortal(<Modal />, document.body)

These patterns aren't rules โ€” they're tools. Use them when they solve a real problem, and don't over-engineer. Happy coding! ๐Ÿš€