# How Custom Hooks Isolate Business Logic from UI Components in React

> Learn how React custom hooks isolate business logic from UI components. Keep your presentation layers clean and reusable for better code organization.

- Repository: [Google Labs Code/stitch-skills](https://github.com/google-labs-code/stitch-skills)
- Tags: how-to-guide
- Published: 2026-07-16

---

**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`](https://github.com/google-labs-code/stitch-skills/blob/main/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`](https://github.com/google-labs-code/stitch-skills/blob/main/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:

```tsx
// 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`](https://github.com/google-labs-code/stitch-skills/blob/main/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:

```tsx
// 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:

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

```

### Table State with useReactTable

In [`plugins/stitch-build/skills/shadcn-ui/examples/data-table.tsx`](https://github.com/google-labs-code/stitch-skills/blob/main/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:

```tsx
// 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`](https://github.com/google-labs-code/stitch-skills/blob/main/form-pattern.tsx) isolates validation |
| **Reusability** | Same hook imported by multiple components | `useToast` in [`/components/ui/use-toast.ts`](https://github.com/google-labs-code/stitch-skills/blob/main//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`](https://github.com/google-labs-code/stitch-skills/blob/main/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`](https://github.com/google-labs-code/stitch-skills/blob/main/form-pattern.tsx) and [`data-table.tsx`](https://github.com/google-labs-code/stitch-skills/blob/main/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`](https://github.com/google-labs-code/stitch-skills/blob/main/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`](https://github.com/google-labs-code/stitch-skills/blob/main/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`](https://github.com/google-labs-code/stitch-skills/blob/main/plugins/stitch-build/skills/shadcn-ui/examples/auth-layout.tsx) demonstrate keeping even simple state logic in hooks, ensuring components only handle rendering concerns.