React Hooks Explorer
An interactive guide to React hooks and patterns used in visualization components
useState Hook
The useState hook lets you add React state to function components. It returns a stateful value and a function to update it.
const [count, setCount] = useState<number>(0); const [isVisible, setIsVisible] = useState<boolean>(true);
Simple Counter Example
Toggling Visibility
Key Takeaways:
- Use useState when you need to track values that change over time
- The setter function can accept a new value or a function that provides the previous value
- Each state update causes a re-render of the component
- Use multiple useState calls to manage different pieces of state
useRef Hook
The useRef hook provides a way to access DOM nodes directly, and can also be used to persist values across renders without causing re-renders.
const inputRef = useRef<HTMLInputElement>(null); const renderCount = useRef<number>(0); // Access the DOM element inputRef.current?.focus(); // Track values between renders without causing re-renders renderCount.current += 1;
DOM Access Example
Persist Value Across Renders
Official render count: 0This is tracked using useRef and automatically increases on every render
Manual ref updates: 0This is displayed using state but tracks manual ref changes
What happens: (1) We update renderCount.current
which doesn't trigger a re-render. (2) We update internalRefValue
state which does trigger a re-render. (3) During this re-render, renderCount.current
is displayed with its new value.
Behind The Scenes: This Component's Code
Here's the key code from this demo that shows how each interaction works:
// 1. State and ref declarations const [inputValue, setInputValue] = useState<string>(''); const [count, setCount] = useState<number>(0); const [internalRefValue, setInternalRefValue] = useState<number>(0); const [isMounted, setIsMounted] = useState<boolean>(false); // Create refs - with proper initialization const inputRef = useRef<HTMLInputElement>(null); // For DOM access const renderCount = useRef<number>(0); // For tracking renders const sectionRef = useRef<HTMLElement>(null); // Section reference // Safe effect with proper cleanup useEffect(() => { setIsMounted(true); if (isMounted) { renderCount.current += 1; } return () => { setIsMounted(false); }; }, [isMounted, count, internalRefValue]); // Safe DOM operations with null checks const handleFocusClick = () => { if (inputRef.current instanceof HTMLInputElement) { inputRef.current.focus(); } };
What Happens When You Click Each Button:
- "Focus Input" button: Calls
inputRef.current?.focus()
which directly manipulates the DOM without re-rendering the component. - "Force Re-render" button: Updates the
count
state, triggering a re-render. The useEffect hook runs, incrementingrenderCount.current
. - "Update ref manually" button: First updates
renderCount.current
(no re-render), then updatesinternalRefValue
state (causes re-render). After this re-render, useEffect runs again and incrementsrenderCount.current
one more time. This is why after clicking this button, the official render count increases by 2.
Key understanding: When you click "Update ref manually", the render count increases by 2 because:
1. You manually increment renderCount.current
2. The state update (setInternalRefValue
) triggers a re-render
3. The useEffect runs after this re-render, incrementing renderCount.current
again
Key Takeaways:
- useRef creates a mutable reference that persists for the full lifetime of the component
- Changes to .current don't trigger re-renders
- It's perfect for storing DOM elements or values that need to persist between renders
- Common uses include: focus management, imperative animations, previous value tracking
useCallback Hook
The useCallback hook returns a memoized callback that only changes if one of its dependencies has changed, preventing unnecessary rerenders.
const addItem = useCallback(() => { if (inputValue.trim()) { setItems(prevItems => [...prevItems, inputValue.trim()]); setInputValue(''); } }, [inputValue]); // Dependency array - recreates only when inputValue changes
List Management Example
Items:
- Item 1
- Item 2
- Item 3
useCallback Performance Demo
With useCallback (stable reference)
Renders: 0Click to trigger parent re-render. The component using the non-memoized callback will re-render even though its props didn't actually change.
What's happening?
React.memo makes a component only re-render if its props have changed. Without useCallback, a new function is created on every render, causing React.memo to detect a prop change even though the function does the same thing. With useCallback, the function reference stays the same between renders, so React.memo works as expected.
Key Takeaways:
- useCallback is essential for performance optimization
- It prevents recreation of function references on each render
- Particularly useful when passing callbacks to optimized child components
- Include all values from the component scope in the dependency array
- React.memo + useCallback prevents unnecessary child re-renders
useMemo Hook
The useMemo hook memoizes expensive calculations so they are only recomputed when dependencies change, improving performance by avoiding unnecessary recalculations.
// Memoize expensive calculation const memoizedValue = useMemo(() => { return expensiveCalculation(inputValue); }, [inputValue]); // Only recalculate when inputValue changes // Memoize derived state const filteredItems = useMemo(() => { return items.filter(item => item.toLowerCase().includes(searchQuery.toLowerCase()) ); }, [items, searchQuery]);
Performance Example
Derived State Example
useMemo is also useful for derived state, like filtering lists based on a search query.
Filtered Items
- Item 1
- Item 2
- Item 3
Key Takeaways:
- Use useMemo for expensive calculations that should be skipped when inputs haven't changed
- Memoization caches the result of a calculation between renders
- Perfect for derived state (filtering, sorting, transforming data)
- Don't overuse - simple calculations may be faster without memoization
- Remember that useMemo is for values, useCallback is for functions
useEffect Hook
The useEffect hook lets you perform side effects in your components after render. Side effects include data fetching, subscriptions, manual DOM manipulations, and more.
// Basic useEffect syntax useEffect(() => { // This code runs after render console.log('Component rendered'); // Optional cleanup function return () => { // This code runs before the next effect or when unmounting console.log('Cleaning up'); }; }, [dependency1, dependency2]); // Dependency array controls when effect runs
Effect Dependencies Demo
Change the value to trigger effects with dependencies. Watch the logs to see which effects run.
Subscription Example
Toggle to start/stop a subscription that updates a counter every 2 seconds.
Subscription inactive - click Subscribe to start
Effect Logs
No effect logs yet. Interact with the examples above.
Common useEffect Patterns
Run on every render (no dependency array)
useEffect(() => { console.log('This runs after every render'); }); // No dependency array
Run once on mount (empty dependency array)
useEffect(() => { console.log('This runs once after the initial render'); return () => { console.log('This runs once before the component unmounts'); }; }, []); // Empty dependency array
Run when specific values change
useEffect(() => { console.log('This runs when userId or query changes'); fetchData(userId, query); return () => { console.log('This runs before the next effect or unmount'); cancelFetchData(); }; }, [userId, query]); // Specific dependencies
Key Takeaways:
- useEffect runs after render and is perfect for side effects
- The dependency array controls when your effect runs:
- No array: runs on every render
- Empty array []: runs once on mount
- With dependencies [a, b]: runs when dependencies change
- Return a cleanup function to prevent memory leaks and handle unsubscriptions
- Common use cases: data fetching, DOM manipulation, subscriptions, and timers
- Always include all values from the component scope that your effect uses in the dependency array
Open your browser console to see additional logs from the various effects.