DevNotes
React.js Intermediate 15 min read

React Hooks Guide 2026: Complete Patterns + Best Practices πŸš€

Master React Hooks with useState, useEffect, useCallback, useMemo, useRef, useTransition, custom hooks, and production patterns for Server Components, concurrent features, and performance optimization.

#react hooks #useState #useEffect #useCallback #useMemo #useTransition #custom hooks
Guide React.js

React Hooks Guide 2026: Production Patterns + Concurrent Features πŸš€

React Hooks (React 19+) power functional components with state, side effects, performance optimization, and concurrent rendering. Modern hooks like useTransition, useOptimistic, useDeferredValue, and custom hook patterns enable 60fps animations, instant server state, and Server Components integration without class component complexity.

🎯 Essential Hooks Hierarchy

HookPurposeWhen to Use
useStateLocal stateUI state, form inputs
useEffectSide effectsData fetching, subscriptions
useCallbackMemoized callbacksPrevent child re-renders
useMemoExpensive calculationsFiltering, mapping large arrays
useRefMutable refsDOM access, previous values
useTransitionConcurrent updatesNon-urgent state changes
useOptimisticInstant UIServer mutations (React 19)

πŸ—οΈ Production-Ready Hook Patterns

1. useState + useReducer (Complex State)

// Simple counter
const [count, setCount] = useState(0);

// Complex form state β†’ useReducer
interface FormState {
  name: string;
  email: string;
  preferences: string[];
}

type FormAction = 
  | { type: 'SET_FIELD'; field: keyof FormState; value: string }
  | { type: 'TOGGLE_PREFERENCE'; preference: string };

const formReducer = (state: FormState, action: FormAction): FormState => {
  switch (action.type) {
    case 'SET_FIELD':
      return { ...state, [action.field]: action.value };
    case 'TOGGLE_PREFERENCE':
      return {
        ...state,
        preferences: state.preferences.includes(action.preference)
          ? state.preferences.filter(p => p !== action.preference)
          : [...state.preferences, action.preference]
      };
  }
};

const [formState, dispatch] = useReducer(formReducer, {
  name: '',
  email: '',
  preferences: []
});

2. useEffect + Cleanup (Data Fetching)

const [data, setData] = useState<Data | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
  let isCancelled = false;
  
  const fetchData = async () => {
    try {
      setLoading(true);
      const response = await fetch('/api/users');
      const result = await response.json();
      
      if (!isCancelled) {
        setData(result);
        setError(null);
      }
    } catch (err) {
      if (!isCancelled) {
        setError(err instanceof Error ? err.message : 'Failed to fetch');
      }
    } finally {
      if (!isCancelled) {
        setLoading(false);
      }
    }
  };

  fetchData();

  return () => {
    isCancelled = true;
  };
}, []);

3. useCallback + useMemo (Performance)

// ❌ Re-creates on every render
const handleSubmit = () => { /* ... */ };

// βœ… Memoized callback
const handleSubmit = useCallback((formData: FormData) => {
  submitForm(formData);
}, []);

// βœ… Expensive computation
const filteredUsers = useMemo(() => {
  return users.filter(user => 
    user.name.toLowerCase().includes(searchTerm.toLowerCase())
  );
}, [users, searchTerm]);

// βœ… Previous value ref
const prevCountRef = useRef<number>();
useEffect(() => {
  console.log('Count changed:', prevCountRef.current, 'β†’', count);
  prevCountRef.current = count;
}, [count]);

πŸš€ Concurrent Features (React 19)

useTransition - Non-Urgent Updates

const [isPending, startTransition] = useTransition();

const handleTabChange = (tab: string) => {
  // βœ… Urgent: Immediate tab switch
  setActiveTab(tab);
  
  // ❌ Non-urgent: Filter heavy list
  startTransition(() => {
    setFilteredUsers(filterUsers(tab));
  });
};

return (
  <>
    <div>Tabs: {activeTab}</div>
    {isPending && <Spinner />} {/* Shows during transition */}
    <UserList users={filteredUsers} />
  </>
);

useOptimistic - Instant Server State

const [optimisticTodos, addOptimisticTodo] = useOptimistic(
  todos,
  (state, newTodo: Todo) => [...state, { ...newTodo, id: 'temp', pending: true }]
);

const handleSubmit = (formData: FormData) => {
  const newTodo = { title: formData.get('title') as string, done: false };
  
  // βœ… Instant UI feedback
  addOptimisticTodo(newTodo);
  
  // ❌ Network delay
  createTodo(newTodo).then((result) => {
    // Server result replaces optimistic state
    setTodos(result);
  });
};

πŸ› οΈ Custom Hooks (Production Patterns)

useAsync (Data Fetching)

type AsyncState<T> = {
  data: T | null;
  loading: boolean;
  error: string | null;
};

function useAsync<T>(asyncFn: () => Promise<T>): AsyncState<T> {
  const [state, setState] = useState<AsyncState<T>>({
    data: null,
    loading: true,
    error: null
  });

  useEffect(() => {
    let cancelled = false;
    
    asyncFn()
      .then(data => {
        if (!cancelled) {
          setState({ data, loading: false, error: null });
        }
      })
      .catch(err => {
        if (!cancelled) {
          setState({ data: null, loading: false, error: err.message });
        }
      });

    return () => {
      cancelled = true;
    };
  }, [asyncFn]);

  return state;
}

// Usage
const { data: users, loading, error } = useAsync(() => 
  fetch('/api/users').then(res => res.json())
);

useLocalStorage (Persistent State)

function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T) => void] {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      return initialValue;
    }
  });

  const setValue = useCallback((value: T) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error('localStorage error:', error);
    }
  }, [key]);

  return [storedValue, setValue];
}

⚑ Rules of Hooks (Never Break)

βœ… Call at TOP LEVEL (not in loops/conditions) βœ… Only in React Functions or Custom Hooks βœ… Custom Hook names START WITH β€œuse”

❌ Inside if/else ❌ Inside loops ❌ Nested functions ❌ Regular JS functions

🎯 Hook Order Matters (Linter Enforced)

// βœ… Correct order (stable)
function MyComponent() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  useEffect(() => {}, []);
  const memoizedValue = useMemo(() => {}, []);
  
  return <div>{count}</div>;
}

// ❌ Wrong order (breaks React)
function BadComponent({ shouldLoad }) {
  if (shouldLoad) {
    const [data, setData] = useState(null); // ❌ Different order each render
  }
  useEffect(() => {}, []); // ❌
}

πŸ“Š Performance Patterns

PatternHookBundle Impact
Form StateuseReducerMinimal
Data FetchinguseEffect + AbortControllerMinimal
CallbacksuseCallback+2KB
ComputationsuseMemo+1KB
OptimisticuseOptimisticBuilt-in

πŸš€ Server Components + Hooks

// app/dashboard/page.tsx (Server Component)
async function Dashboard() {
  const data = await fetchUsers();
  
  return <ClientDashboard initialData={data} />;
}

// Client Component with hooks
'use client';
function ClientDashboard({ initialData }: { initialData: User[] }) {
  const [users, setUsers] = useState(initialData);
  
  const refresh = useCallback(async () => {
    const freshData = await fetch('/api/users');
    setUsers(await freshData.json());
  }, []);
  
  return (
    <div>
      <button onClick={refresh}>Refresh</button>
      <UserList users={users} />
    </div>
  );
}

🎯 Production Checklist

βœ… [] useCallback for expensive callbacks βœ… [] useMemo for expensive calculations βœ… [] useEffect cleanup prevents memory leaks βœ… [] useTransition for non-urgent updates βœ… [] Custom hooks for reusable logic βœ… [] ESLint plugin-react-hooks enabled βœ… [] Rules: Top level only, stable order βœ… [] Server Components pass initial data βœ… [] useOptimistic for instant feedback

πŸ”₯ Common Anti-Patterns

// ❌ Empty dependency array (stale closures)
useEffect(() => {
  fetchData(userId); // userId may be stale
}, []);

// βœ… Include all dependencies
useEffect(() => {
  fetchData(userId);
}, [userId]);

// βœ… OR useCallback for stable reference
const fetchData = useCallback((id: string) => {
  // fetch logic
}, []);

🎯 Final Thoughts

Hooks = React’s superpower. From simple useState to advanced concurrent patterns, hooks enable declarative data flow, performance optimization, and Server Components integration without class complexity.

2026 React workflow:

Server Components β†’ Initial data

useOptimistic β†’ Instant feedback

useTransition β†’ Concurrent updates

Custom hooks β†’ Reusable logic

60fps + instant server state

Master hooks β†’ Master React. Build production apps with concurrent features and optimal performance πŸš€.


React 19 Docs: react.dev | Concurrent Features: react.dev/concurrent

Chat with us