How Custom Hooks Isolate Business Logic from UI Components in React

Custom hooks are JavaScript functions prefixed with use that encapsulate state, side effects, and data manipulation, allowing UI components to remain pure presentation layers that only render markup and delegate all business logic to reusable, testable functions.

The Stitch Skills codebase (google-labs-code/stitch-skills) demonstrates this architectural pattern extensively. By extracting API calls, validation logic, and state management into custom hooks, the repository keeps UI components thin and declarative while centralizing complex business rules in portable, unit-testable functions.

The Custom Hook Pattern in Stitch Skills

A custom hook is a plain JavaScript or TypeScript function whose name starts with use. It can call other React hooks like useState, useEffect, and useContext, and return any values the component needs. In plugins/stitch-build/skills/shadcn-ui/examples/form-pattern.tsx, the component imports useForm from react-hook-form, delegating all validation and submission logic to the hook while the component focuses solely on rendering inputs.

Architectural Flow: From Hook to JSX

The typical data flow in this repository follows three distinct layers:

  1. Import: The component imports a specialized hook (either from a library or a local file).
  2. Encapsulation: The hook creates local state, performs side effects, and exposes handlers and derived data.
  3. Rendering: The component receives these values and wires them to JSX without conditional logic, HTTP calls, or data transformation.

This separation ensures that when a component renders in different contexts, the same hook can be reused without copying UI code, maintaining a single source of truth for business rules.

Implementation Examples from the Source Code

Form Handling with useForm

In plugins/stitch-build/skills/shadcn-ui/examples/form-pattern.tsx, the form validation and submission logic lives entirely within the useForm hook. The component only registers fields and renders markup:

// plugins/stitch-build/skills/shadcn-ui/examples/form-pattern.tsx
import { useForm } from "react-hook-form";

const Form = () => {
  const { register, handleSubmit, formState } = useForm<FormValues>();
  const onSubmit = (data: FormValues) => {
    // Business rule: send data to API
    toast({ title: "Submitted", description: JSON.stringify(data) });
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("username")} />
      <select {...register("role")}>{/* … */}</select>
      <button type="submit">Save</button>
    </form>
  );
};

Toast Notifications with useToast

The components/ui/use-toast.ts file demonstrates a small custom hook that abstracts the toast library implementation. Components consume a clean API without knowing the underlying toast system:

// components/ui/use-toast.ts
import { toast as toastLib } from "@/components/ui/toast";

export const useToast = () => ({
  success: (msg: string) => toastLib({ variant: "success", description: msg }),
  error: (msg: string) => toastLib({ variant: "destructive", description: msg }),
});

Usage in a component remains purely declarative:

const { success } = useToast();
success("Profile updated");

Table State with useReactTable

In plugins/stitch-build/skills/shadcn-ui/examples/data-table.tsx, sorting, filtering, and pagination logic are handled by useReactTable. The UI component simply maps over the rows returned by the hook:

// plugins/stitch-build/skills/shadcn-ui/examples/data-table.tsx
const [sorting, setSorting] = React.useState<SortingState>([]);
const table = useReactTable({ 
  data, 
  columns, 
  state: { sorting }, 
  onSortingChange: setSorting 
});

// Component only handles rendering
table.getRowModel().rows.map(row => /* JSX */);

Key Benefits Demonstrated in the Repository

Benefit Implementation Example Location
Separation of concerns UI renders markup; hook handles data and side effects useForm in form-pattern.tsx isolates validation
Reusability Same hook imported by multiple components useToast in /components/ui/use-toast.ts shared across UI
Testability Hook logic exercised without DOM rendering useForm tested with Jest without JSX
Simpler components No async code or conditionals in JSX auth-layout.tsx only toggles loading via useState

Summary

  • Custom hooks in the Stitch Skills codebase are functions prefixed with use that encapsulate business logic, state management, and side effects.
  • Components in files like form-pattern.tsx and data-table.tsx remain pure presentation layers, delegating complex operations to hooks.
  • This pattern enables unit testing of business logic without rendering components, promotes code reuse across the application, and maintains separation of concerns between data operations and UI markup.
  • The repository demonstrates this architecture through implementations of useForm, useToast, and useReactTable, keeping UI components thin and declarative.

Frequently Asked Questions

What makes a function a "custom hook" in React?

A custom hook is any JavaScript or TypeScript function whose name starts with use and that may call other React hooks like useState or useEffect. According to the Stitch Skills source code, these functions return values that components need while hiding implementation details of state management and side effects.

How do custom hooks improve testability compared to logic inside components?

When business logic lives inside a hook rather than a component, you can unit test the logic by calling the hook directly in a test environment without mounting a DOM or rendering JSX. For example, the validation logic in useForm from form-pattern.tsx can be tested with Jest independently of the React component tree.

Can custom hooks be reused across different UI libraries?

Yes. Because custom hooks are pure JavaScript functions that return data and callbacks, they are not tied to specific UI implementations. The useToast hook in components/ui/use-toast.ts can be imported by any component regardless of its specific styling or layout, ensuring consistent notification behavior across the application.

Where should business logic live if it's not in the component?

In the Stitch Skills architecture, business logic belongs in custom hooks for stateful operations or in standalone utility functions for pure data transformations. Files like plugins/stitch-build/skills/shadcn-ui/examples/auth-layout.tsx demonstrate keeping even simple state logic in hooks, ensuring components only handle rendering concerns.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →