Should Functions Be Passed Down via Context in React?

In React, you can pass any value via context, including functions:

function MyApp() {
  const [currentUser, setCurrentUser] = useState(null);

  function login(response) {
    storeCredentials(response.credentials);
    setCurrentUser(response.user);
  }

  return (
    <AuthContext.Provider value={{ currentUser, login }}>
      <Page />
    </AuthContext.Provider>
  );
}

Passing functions via context can be useful, for example, when multiple components need access to the same function without the need for prop drilling (i.e., passing the function through multiple intermediary components). This can help reduce the complexity of passing props through multiple layers of nested components.

However, you should consider the following points before deciding to pass functions down via context:

Often times, you could benefit from alternative solutions such as directly importing functions in components that need them, using a global state management library, or using an event-driven architecture.

Can Add Layer of Complexity

In a complex application where multiple components rely on shared functions through context, it can be difficult to trace the path of data and understand how information flows between components. This is especially true when there are many intermediate components involved, making it harder to track where the functions are used and how they affect the overall application state.

Can be Hard to Scale and Maintain

When a function passed via context is used by multiple components, modifying the function's behavior or signature can have far-reaching consequences. It may require updating all the components that use the function, potentially introducing unintended side effects or breaking existing functionality. The more components depend on the function, the more effort is needed to ensure the changes are propagated correctly throughout the application.

Can Have a Performance Impact

Passing a function via context can trigger re-renders of all components consuming that context when the function or its context changes. This can have a performance impact when updates are frequent or if many components rely on it.

However, in that case, you can wrap the function with the useCallback() hook to avoid unnecessary re-renders, for example, like so:

import { useCallback, useMemo } from 'react';

function MyApp() {
  const [currentUser, setCurrentUser] = useState(null);

  const login = useCallback((response) => {
    storeCredentials(response.credentials);
    setCurrentUser(response.user);
  }, []);

  const contextValue = useMemo(() => ({
    currentUser,
    login
  }), [currentUser, login]);

  return (
    <AuthContext.Provider value={contextValue}>
      <Page />
    </AuthContext.Provider>
  );
}

This post was published by Daniyal Hamid. Daniyal currently works as the Head of Engineering in Germany and has 20+ years of experience in software engineering, design and marketing. Please show your love and support by sharing this post.