Implementing the useTrackedEffect Custom Hook
Here's the implementation of a useTrackedEffect hook for React:
jsx
1 import { useEffect, useRef } from 'react';23 /**4 * A custom React hook that tracks which dependencies changed between renders5 *6 * @param {Function} effect - Effect callback that receives information about changed dependencies7 * @param {Array} dependencies - Array of dependencies to track8 */9 const useTrackedEffect = (effect, dependencies) => {10 const previousDependencies = useRef(dependencies);1112 useEffect(() => {13 // Skip first render14 if (previousDependencies.current) {15 // Create an object to track which dependencies changed16 const changedDeps = dependencies.reduce((acc, dep, index) => {17 // Compare current dependency with previous one18 if (dep !== previousDependencies.current[index]) {19 acc[index] = {20 from: previousDependencies.current[index],21 to: dep22 };23 }24 return acc;25 }, {});2627 // Only run effect if at least one dependency changed28 if (Object.keys(changedDeps).length > 0) {29 // Call the effect with changed dependencies info30 return effect(changedDeps);31 }32 }3334 // Update previous dependencies for next render35 previousDependencies.current = dependencies;3637 // Return cleanup function from effect (if any)38 return () => {39 if (effect.cleanup && typeof effect.cleanup === 'function') {40 effect.cleanup();41 }42 };43 }, dependencies);44 };4546 export default useTrackedEffect;
The useTrackedEffect
hook is a powerful enhancement to React's standard useEffect
. Here's how it works:
Core Functionality
The hook tracks which specific dependencies changed between renders, giving you precise information about what triggered your effect. This is incredibly useful for debugging and performance optimization.
Key Components
1. Dependency Tracking:
- Uses
useRef
to store the previous render's dependencies - Compares current dependencies with previous ones on each render
2. Change Detection Algorithm:
The reduce
function creates an object that maps:
- The index of each changed dependency
- Both the previous (
from
) and current (to
) values
3. Conditional Execution:
- Only executes the effect if at least one dependency changed
- Passes the change information to your effect callback
4. Cleanup Handling:
- Supports cleanup functions just like standard
useEffect
- Checks if the effect has a
cleanup
property that's a function
Usage Example
jsx
1 import useTrackedEffect from './useTrackedEffect';23 function ProfileComponent({ userId, settings }) {4 useTrackedEffect((changes) => {5 // Log which specific dependencies changed6 console.log('Changed dependencies:', changes);78 // You can see exactly what triggered this effect9 if (changes[0]) {10 console.log(`User ID changed from ${changes[0].from} to ${changes[0].to}`);11 // Fetch new user data...12 }1314 if (changes[1]) {15 console.log(`Settings changed from`, changes[1].from, `to`, changes[1].to);16 // Apply new settings...17 }1819 // Optional cleanup function20 return () => {21 console.log('Cleaning up previous effect');22 };23 }, [userId, settings]);2425 return <div>Profile Content</div>;26 }
Benefits
- Precise debugging: Immediately see which dependency caused a re-render
- Performance optimization: Target your optimizations at the exact dependencies causing unnecessary re-renders
- Better control flow: Make decisions in your effect based on which specific values changed
This hook is especially valuable in complex components with multiple dependencies where standard useEffect
doesn't provide enough context about what triggered the effect.