What React Hooks do you know?

👨‍💻 Frontend Developer 🟢 Almost Certain 🎚️ Medium
#React #Hooks

Brief Answer

React provides several built-in hooks for working with state, effects, and other features of functional components:

Main hooks:

  • useState — for managing local state
  • useEffect — for working with side effects
  • useContext — for using React context

Additional hooks:

  • useReducer — alternative to useState for complex state
  • useCallback — for memoizing functions
  • useMemo — for memoizing values
  • useRef — for accessing DOM elements and storing mutable values
  • useLayoutEffect — synchronous alternative to useEffect
  • useTransition — for managing update priorities
  • useId — for generating unique IDs

Creating custom hooks — allows extracting reusable logic into separate functions.


Full Answer

React Hooks are functions that allow you to use state and other React features without writing classes. They were introduced in React 16.8 and became a revolution in functional component development. 🚀

Main Built-in Hooks

1. useState — State Management

The most commonly used hook for managing component local state:

// Simple counter example
function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

2. useEffect — Side Effects

Used for performing side effects such as API requests, subscriptions, timers:

// Example with subscription and unsubscription
function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);
  
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();
    
    return () => {
      connection.disconnect();
    };
  }, [roomId]);
  
  return <div>{/* display messages */}</div>;
}

3. useContext — Working with Context

Allows accessing React context values without prop drilling:

// Example using theme
function Button() {
  const theme = useContext(ThemeContext);
  
  return (
    <button style={{ background: theme.background }}>
      Button
    </button>
  );
}

Additional Built-in Hooks

1. useReducer — Complex State

Alternative to useState for complex state update logic:

// Counter example with reducer
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}
 
function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  
  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>
        Increment
      </button>
    </div>
  );
}

2. useCallback — Function Memoization

Prevents unnecessary function creation on every render:

// Example optimizing callbacks
function Parent() {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []);
  
  return <Child onClick={handleClick} />;
}

3. useMemo — Value Memoization

Caches computation results between renders:

// Example of heavy computations
function ExpensiveComponent({ items }) {
  const expensiveValue = useMemo(() => {
    return items.reduce((acc, item) => acc + item.value, 0);
  }, [items]);
  
  return <div>Result: {expensiveValue}</div>;
}

4. useRef — DOM Access and Mutable Values

Allows accessing DOM elements or storing mutable values:

// Example focusing on element
function Form() {
  const inputRef = useRef(null);
  
  const focusInput = () => {
    inputRef.current.focus();
  };
  
  return (
    <div>
      <input ref={inputRef} />
      <button onClick={focusInput}>Focus</button>
    </div>
  );
}

5. useLayoutEffect — Synchronous Effects

Analog of useEffect but runs synchronously after DOM changes:

// Example measuring element
function Tooltip() {
  const [tooltipHeight, setTooltipHeight] = useState(0);
  const tooltipRef = useRef();
  
  useLayoutEffect(() => {
    setTooltipHeight(tooltipRef.current.offsetHeight);
  });
  
  return (
    <div ref={tooltipRef}>
      Tooltip height {tooltipHeight}px
    </div>
  );
}

6. useTransition — Managing Update Priorities

Allows marking state updates as non-urgent to prevent blocking the interface:

// Example with delayed search
function SearchComponent() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();
  
  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value);
    
    // Mark results update as non-urgent
    startTransition(() => {
      // Heavy search operation
      const filteredResults = heavySearchOperation(value);
      setResults(filteredResults);
    });
  };
  
  return (
    <div>
      <input value={query} onChange={handleChange} />
      {isPending && <div>Loading...</div>}
      <ResultsList results={results} />
    </div>
  );
}

7. useId — Generating Unique IDs

Generates stable unique IDs, useful for accessibility attributes:

// Example creating accessible forms
function FormField({ label, value, onChange }) {
  const id = useId();
  
  return (
    <div>
      <label htmlFor={id}>{label}</label>
      <input 
        id={id} 
        value={value} 
        onChange={onChange} 
        aria-describedby={`${id}-description`}
      />
      <p id={`${id}-description`}>
        This field is required
      </p>
    </div>
  );
}

Custom Hooks

Allow extracting reusable logic into reusable functions:

// Example custom hook for data fetching
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetch(url)
      .then(response => response.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, [url]);
  
  return { data, loading };
}
 
// Using custom hook
function UserProfile({ userId }) {
  const { data, loading } = useFetch(`/api/users/${userId}`);
  
  if (loading) return <div>Loading...</div>;
  return <div>Hello, {data.name}!</div>;
}

When to Use Each Hook

useState vs useReducer

  • useState — for simple state (string, number, boolean)
  • useReducer — for complex state with multiple actions

useEffect vs useLayoutEffect

  • useEffect — for asynchronous operations (subscriptions, timers)
  • useLayoutEffect — when you need to run code before browser painting

useCallback vs useMemo

  • useCallback — for memoizing functions
  • useMemo — for memoizing values

Common Mistakes

1. Overusing Memoization

// ❌ No need to memoize simple values
const value = useMemo(() => 5, []); // Unnecessary!
 
// ✅ Just use the value directly
const value = 5;

2. Missing Dependencies in useEffect

// ❌ Missing dependency
useEffect(() => {
  console.log(userId); // userId is used but not in dependencies
}, []);
 
// ✅ All dependencies specified
useEffect(() => {
  console.log(userId);
}, [userId]);

3. Direct State Mutation

// ❌ Mutating array directly
const [items, setItems] = useState([1, 2, 3]);
items.push(4); // Won't work!
 
// ✅ Creating new array
const [items, setItems] = useState([1, 2, 3]);
setItems([...items, 4]); // Correct!

Best Practices

1. Use the Right Hooks for Tasks

// Good: using the right hooks
function Timer() {
  const [seconds, setSeconds] = useState(0);
  
  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);
    
    return () => clearInterval(interval);
  }, []);
  
  return <div>Elapsed: {seconds} seconds</div>;
}

2. Create Custom Hooks for Repeating Logic

// Custom hook for working with local storage
function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      return initialValue;
    }
  });
  
  const setValue = (value) => {
    setStoredValue(value);
    window.localStorage.setItem(key, JSON.stringify(value));
  };
  
  return [storedValue, setValue];
}

Summary

React Hooks are a powerful tool for working with state and other features in functional components:

Main hooks:

  • useState — for simple state
  • useEffect — for side effects
  • useContext — for working with context

Additional hooks:

  • useReducer — for complex state
  • useCallback/useMemo — for optimization
  • useRef — for DOM access and storing values
  • useTransition — for managing update priorities
  • useId — for generating unique IDs

Custom hooks:

  • Allow extracting repeating logic
  • Make code more readable and reusable

Understanding all hooks and knowing how to apply them correctly is key to effective React development! 🎯


Want more articles to prepare for interviews? Subscribe to EasyAdvice, bookmark the site, and improve yourself every day 💪