본문 바로가기
Dev/React

A Practical Guide to useContext in React 19

by ArcticBear 2025. 3. 5.
반응형

 

 

 

 1. Introduction

When building applications in React, you often need to share data or functions across different components. However, passing these values down multiple levels through props can become cumbersome and lead to what’s commonly referred to as “prop drilling.” The Context API offers a more streamlined approach, letting you create a context and consume its data in nested components without manually passing props at every level.

In this post, we’ll explore:

  • What useContext is and why it’s so useful
  • How to set up a custom context for sharing data
  • Practical examples of using useContext in real projects
  • Best practices to keep your context usage clear and efficient

 

 2. What Is useContext?

useContext is a React hook that allows you to consume a Context value directly in a functional component. Normally, we create a Context object with React.createContext and then we can provide its value at a top-level component. Any descendant can subscribe to the same context value by calling useContext(SomeContext), retrieving the latest value without needing intermediate props.

Core Concepts

  • Context Provider: Holds the data or functions you want to share.
  • Context Consumer: Any component that reads the context value using useContext.

Signature:

const value = useContext(SomeContext)

Here, value is whatever the SomeContext provider is currently supplying. If the context changes, React will re-render any components that consume it.

 

 

 

 3. Setting Up a Context

 

3.1 Creating a Context

// ThemeContext.js
import React from "react";

// Initialize with a default value (e.g., 'light')
const ThemeContext = React.createContext("light");

export default ThemeContext;

By convention, we store context objects in a separate file. The createContext function can accept a default value that will be used if no provider supplies a value down the tree.

 

3.2 Providing a Value

// App.js
import React, { useState } from "react";
import ThemeContext from "./ThemeContext";
import ThemedButton from "./ThemedButton";

function App() {
  const [theme, setTheme] = useState("light");

  return (
    <ThemeContext.Provider value={theme}>
      <div>
        <h1>Current theme: {theme}</h1>
        <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
          Toggle Theme
        </button>

        {/* This button will consume the context value */}
        <ThemedButton />
      </div>
    </ThemeContext.Provider>
  );
}

export default App;

 

  • ThemeContext.Provider: Wraps any component that should have access to the theme value.
  • value={theme}: The value prop determines what the children see.

 

 

 4. Consuming Context with useContext

 

4.1 Basic Usage

// ThemedButton.js
import React, { useContext } from "react";
import ThemeContext from "./ThemeContext";

export default function ThemedButton() {
  const theme = useContext(ThemeContext);

  const styles = {
    backgroundColor: theme === "light" ? "#fff" : "#333",
    color: theme === "light" ? "#333" : "#fff",
    padding: "10px 20px",
    border: "none",
    cursor: "pointer",
  };

  return <button style={styles}>Themed Button</button>;
}

 

  • useContext(ThemeContext) directly retrieves the current theme value from the nearest <ThemeContext.Provider>.
  • Every time theme changes in the provider, ThemedButton re-renders with the updated value.

 

4.2 Multiple Contexts

You can consume multiple contexts in the same component by calling useContext for each one:

import React, { useContext } from "react";
import ThemeContext from "./ThemeContext";
import UserContext from "./UserContext";

function Profile() {
  const theme = useContext(ThemeContext);
  const user = useContext(UserContext);

  return (
    <div style={{ color: theme === "light" ? "#000" : "#fff" }}>
      <p>Welcome, {user.name}!</p>
    </div>
  );
}

 

 

 5. Practical Examples

 

5.1 Authentication Context

A typical use case involves authentication—storing the current user object and a function to log in/log out.

// AuthContext.js
import React from "react";

const AuthContext = React.createContext({
  user: null,
  login: () => {},
  logout: () => {},
});

export default AuthContext;
// AuthProvider.js
import React, { useState } from "react";
import AuthContext from "./AuthContext";

export default function AuthProvider({ children }) {
  const [user, setUser] = useState(null);

  function login(username) {
    // call an API, authenticate
    setUser({ name: username });
  }

  function logout() {
    setUser(null);
  }

  const value = { user, login, logout };

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

 

Now any component wrapped by AuthProvider can call useContext(AuthContext) to get user, login, and logout:

// LoginButton.js
import React, { useContext } from "react";
import AuthContext from "./AuthContext";

function LoginButton() {
  const { user, login } = useContext(AuthContext);
  if (user) {
    return <p>Welcome back, {user.name}!</p>;
  }
  return <button onClick={() => login("Alice")}>Log In</button>;
}

export default LoginButton;
 

5.2 Global Settings Context

For example, you may have application-wide settings (language preference, date formats, etc.) that multiple components need:

// SettingsContext.js
import React from "react";

const SettingsContext = React.createContext({
  locale: "en-US",
  timezone: "GMT",
});

export default SettingsContext;

 

 

 6. Best Practices

  • Avoid Overuse of Context
    • Context can simplify prop-passing, but if you overuse it for every piece of data, it might become harder to trace data flow.
    • Keep context usage reserved for truly global data (e.g., theme, authentication, user preferences).
  • Separate Contexts for Unrelated Data
    • Having a single “mega-context” with multiple, unrelated values can force unnecessary re-renders. Break them into smaller contexts if they change at different frequencies.
  • Memoize Provider Values
    • If your context value is an object or array that changes on each render, it can cause excessive re-renders. Use useMemo inside the provider if necessary.
    • Example:
const value = useMemo(() => ({ user, login, logout }), [user]);

 

  • Combine with Custom Hooks
    • You can wrap context logic in a custom hook for reusability. For instance, create a useAuth hook that calls useContext(AuthContext) and returns { user, login, logout }.
  • Testing and Debugging
    • Context usage can sometimes obscure the data flow, so keep track of where your providers and consumers are placed. Tools like the React DevTools can help you inspect context values.

 

 7. When Not to Use Context

  • Local Component State: If data is only needed in a single component, context is overkill—stick with useState.
  • App-Wide State Libraries: If you have a robust state management library like Redux, MobX, or Zustand, you might not need context for global data. However, React context can still complement these solutions in simpler cases.

 

 

 8. Conclusion

useContext is a powerful, straightforward way to handle cross-component data sharing in React. By eliminating the need for prop drilling, it can make your code cleaner and easier to maintain. However, use it wisely—too many contexts or using context for every piece of data can complicate your app’s architecture.

Key Takeaways

  • Context helps avoid prop drilling by allowing data to be accessed deeply in the component tree.
  • useContext is how functional components consume context values.
  • Consider performance: separate contexts for unrelated data, and memoize provider values when necessary.
  • Keep it simple: context is best for truly global concerns like theme, authentication, and user settings.

If you have more questions or want to share your own experiences with the Context API, feel free to leave a comment below. Happy coding!

 

반응형