react-use-callback

📁 flpbalada/my-opencode-config 📅 14 days ago
1
总安装量
1
周安装量
#55168
全站排名
安装命令
npx skills add https://github.com/flpbalada/my-opencode-config --skill react-use-callback

Agent 安装分布

opencode 1
claude-code 1

Skill 文档

React: useCallback Best Practices

Core Principle

useCallback caches a function definition between re-renders until its dependencies change.

Only use useCallback for specific performance optimizations – not by default.

When to Use useCallback

1. Passing Callbacks to Memoized Children

When passing a function to a component wrapped in memo():

import { useCallback, memo } from 'react';

const ExpensiveChild = memo(function ExpensiveChild({ onClick }) {
  // Expensive rendering logic
  return <button onClick={onClick}>Click me</button>;
});

function Parent({ productId }) {
  // Without useCallback, handleClick would be a new function every render
  // causing ExpensiveChild to re-render unnecessarily
  const handleClick = useCallback(() => {
    console.log('Clicked:', productId);
  }, [productId]);

  return <ExpensiveChild onClick={handleClick} />;
}

2. Function as Effect Dependency

When a function is used inside useEffect:

function ChatRoom({ roomId }) {
  const createOptions = useCallback(() => {
    return { serverUrl: 'https://localhost:1234', roomId };
  }, [roomId]);

  useEffect(() => {
    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [createOptions]);
}

Better alternative: Move the function inside the effect:

function ChatRoom({ roomId }) {
  useEffect(() => {
    // Function defined inside effect - no useCallback needed
    function createOptions() {
      return { serverUrl: 'https://localhost:1234', roomId };
    }
    
    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);
}

3. Custom Hook Return Values

Always wrap functions returned from custom hooks:

function useRouter() {
  const { dispatch } = useContext(RouterStateContext);

  const navigate = useCallback((url) => {
    dispatch({ type: 'navigate', url });
  }, [dispatch]);

  const goBack = useCallback(() => {
    dispatch({ type: 'back' });
  }, [dispatch]);

  return { navigate, goBack };
}

4. Reducing State Dependencies

Use updater functions to eliminate state dependencies:

// Before: todos is a dependency
const handleAddTodo = useCallback((text) => {
  setTodos([...todos, { id: nextId++, text }]);
}, [todos]);

// After: No todos dependency needed
const handleAddTodo = useCallback((text) => {
  setTodos(todos => [...todos, { id: nextId++, text }]);
}, []);

When NOT to Use useCallback

1. Child Is Not Memoized

Without memo(), useCallback provides no benefit:

// useCallback is pointless here
function Parent() {
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);

  // Child will re-render anyway when Parent re-renders
  return <Child onClick={handleClick} />;
}

2. Coarse Interactions

Apps with page-level navigation don’t benefit from memoization:

// Overkill for simple navigation
function App() {
  const [page, setPage] = useState('home');
  
  // Not needed - page transitions are inherently expensive anyway
  const navigate = useCallback((page) => setPage(page), []);
  
  return <Navigation onNavigate={navigate} />;
}

3. When Better Alternatives Exist

Accept JSX as children:

// Instead of memoizing onClick
function Panel({ children }) {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <div>
      <button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
      {isOpen && children}
    </div>
  );
}

// Children don't re-render when Panel's state changes
<Panel>
  <ExpensiveComponent />
</Panel>

Keep state local:

// Don't lift state higher than necessary
function SearchForm() {
  // Local state doesn't trigger parent re-renders
  const [query, setQuery] = useState('');
  return <input value={query} onChange={e => setQuery(e.target.value)} />;
}

Anti-Patterns to Avoid

Missing Dependency Array

// Returns a new function every render
const handleClick = useCallback(() => {
  doSomething();
}); // Missing dependency array!

// Correct
const handleClick = useCallback(() => {
  doSomething();
}, []);

useCallback in Loops

// Can't call hooks in loops
function List({ items }) {
  return items.map(item => {
    // WRONG
    const handleClick = useCallback(() => sendReport(item), [item]);
    return <Chart key={item.id} onClick={handleClick} />;
  });
}

// Correct: Extract to component
function List({ items }) {
  return items.map(item => (
    <Report key={item.id} item={item} />
  ));
}

function Report({ item }) {
  const handleClick = useCallback(() => sendReport(item), [item]);
  return <Chart onClick={handleClick} />;
}

// Alternative: Wrap Report in memo instead
const Report = memo(function Report({ item }) {
  function handleClick() {
    sendReport(item);
  }
  return <Chart onClick={handleClick} />;
});

useCallback vs useMemo

Hook Caches Use Case
useCallback(fn, deps) The function itself Callback props
useMemo(() => fn, deps) Result of calling function Computed values
// Equivalent
const memoizedFn = useCallback(fn, deps);
const memoizedFn = useMemo(() => fn, deps);

Quick Reference

DO

  • Use with memo() wrapped children
  • Use when function is an effect dependency
  • Wrap custom hook return functions
  • Use updater functions to reduce dependencies

DON’T

  • Add everywhere “just in case”
  • Use without memo() on child component
  • Use when you can restructure code instead
  • Forget the dependency array

Performance Debugging

When memoization isn’t working, debug dependencies:

const handleSubmit = useCallback((orderDetails) => {
  // ...
}, [productId, referrer]);

console.log([productId, referrer]);

Check in browser console:

Object.is(temp1[0], temp2[0]); // First dependency same?
Object.is(temp1[1], temp2[1]); // Second dependency same?

Future: React Compiler

React Compiler automatically memoizes values and functions, reducing the need for manual useCallback calls. Consider using the compiler to handle memoization automatically.

References