React Design Patterns

Decouple stateful logic from UI rendering.

Custom hooks are React's primary way to share stateful behavior. However, calling them directly at the root of a large page component forces the entire page to re-render whenever the hook updates. The Hook Wrapper Pattern compiles hooks into reusable components to isolate re-render performance.

Standard Call Pattern

Not Wrapped (Direct Hook)

Invoke hooks directly at the root of your component. Easy to write and straightforward for simple components, but ties the entire component's lifecycle to the hook's state transitions.

Code Snippet

function MyComponent() {
  // Triggers full re-render on state change
  const { count, inc } = useCounter({ step: 1 });
  
  return (
    <div>
      <button onClick={inc}>Add {count}</button>
      <HeavyComponent />
    </div>
  );
 }
Explore Not Wrapped Demo →

Wrapper Pattern

Wrapped (Hook Wrapper)

Wrap your custom hook with wrap() or useHook() to create a container component. This isolates state updates to the sub-tree under the render prop, avoiding top-level parent re-renders.

Code Snippet

import { useHook } from '@bemedev/hook-wrapper';

const CounterWrapper = useHook(useCounter);
 
 function MyComponent() {
  // Parent doesn't re-render when counter changes!
  return (
    <CounterWrapper
      step={1}
      render={({ count, inc }) => (
        <button onClick={inc}>Add {count}</button>
      )}
    />
  );
 }
Explore Wrapped Demo →

Performance & Capabilities

Comparison Matrix

Feature / CapabilityStandard Hook (Not Wrapped)Wrapper Component (Wrapped)
Re-render ScopeParent Component & ChildrenIsolated Render-Prop Sub-tree only
Class Component SupportUnsupported (React constraint)Supported (Standard JSX Component)
Dynamic / Conditional InvocationUnsupported (Violates Rules of Hooks)Supported (Can be mounted conditionally or in loops)
State EncapsulationLeaked to page component scopeSelf-contained inside the wrapper instance

Get Started

Introduction & Installation

@bemedev/hook-wrapper is a lightweight, zero-dependency utility that lets you wrap any React hook into a dedicated component.

This isolates hook lifecycle changes to a localized render-prop sub-tree, avoiding top-level parent component re-renders and boosting UI responsiveness.

Install Package
pnpm add @bemedev/hook-wrapper

Implementation Guide

How-to Use & Examples

1Define a Custom Hook

Write your hook just like any normal React hook, returning states and actions.

import { useState } from 'react';

export const useCounter = ({ step = 1 }: { step?: number } = {}) => {
  const [count, setCount] = useState(0);
  return {
    count,
    inc: () => setCount(c => c + step),
  };
};
2Wrap and Render the Hook

Pass your custom hook to the wrap() or useHook() function. Mount the returned wrapper, passing hook parameters as props and rendering the UI.

import { useHook } from '@bemedev/hook-wrapper';
import { useCounter } from './useCounter';

// Wrap hook to get a React Component
const CounterWrapper = useHook(useCounter);

export const Demo = () => (
  <CounterWrapper
    step={5}
    render={({ count, inc }) => (
      <div>
        <p>Count: {count}</p>
        <button onClick={inc}>Increment by 5</button>
      </div>
    )}
  />
);

Signature & API

API Reference

The core helper exposes a single, straightforward signature to encapsulate your logic.

wrap(hook) / useHook(hook)Core API

Both exports are functionally identical. useHook is provided as a semantic alternative for developers preferring standard hook naming conventions.

Parameters

  • hook: The target React hook function (e.g. useMyHook) you wish to wrap.

Returns

A standard React component that is type-safe and takes the following props:

  • Hook Arguments as Props: Matching exactly the inputs of the wrapped hook.
  • render: A callback function prop. It receives the values returned by the hook (as its single parameter) and returns the JSX elements to render.