Skip to main content
In modern React applications, managing state at the feature level has become a popular and efficient approach. This method allows each major feature to maintain its own state, which can be passed deeply into sub-components using React’s Context API. Let’s explore this concept using the Tracking feature as an example.

Creating and Providing Context

Each feature typically defines its own context and provider. For the Tracking feature, we have a TrackingProvider component that creates and provides the context:
import React, { createContext, useState } from "react";

interface TrackingContextProps {
  // ... context properties ...
}

export const TrackingContext =
  createContext<TrackingContextProps>(defaultContext);

const TrackingProvider: React.FC<React.PropsWithChildren<{}>> = ({
  children,
}) => {
  const [sidebarOpen, setSidebarOpen] = useState<boolean>(false);
  const [selectedUniqueIdentifier, setSelectedUniqueIdentifier] =
    useState<UniqueIdentifier | null>(null);
  // ... other state variables ...

  return (
    <TrackingContext.Provider
      value={{
        sidebarOpen,
        setSidebarOpen,
        selectedUniqueIdentifier,
        setSelectedUniqueIdentifier,
        // ... other values and functions ...
      }}
    >
      {children}
    </TrackingContext.Provider>
  );
};

export default TrackingProvider;
This provider component wraps its children with the context provider, making the state and functions available to all nested components.

Consuming Context

Components within the Tracking feature can consume this context to access shared state and functions. While you could use the useContext hook directly, it’s recommended to create a custom hook for consuming the context. This approach provides better encapsulation and makes it easier to manage potential changes to the context structure. For the Tracking feature, a custom useTracking hook is provided:
export const useTracking = () => {
  const context = useContext(TrackingContext);
  if (!context) {
    throw new Error("useTracking must be used within a TrackingProvider");
  }
  return context;
};
This custom hook not only provides the context but also includes an error check to ensure it’s used within the correct provider.

Using the Context in Components

Now, components can easily access the shared state and functions using the custom hook:
import React from "react";
import { useTracking } from "./TrackingProvider";

const TrackingComponent: React.FC = () => {
  const { sidebarOpen, setSidebarOpen, selectedUniqueIdentifier } =
    useTracking();

  return (
    <div>
      <button onClick={() => setSidebarOpen(!sidebarOpen)}>
        Toggle Sidebar
      </button>
      {selectedUniqueIdentifier && (
        <p>Selected ID: {selectedUniqueIdentifier.id}</p>
      )}
    </div>
  );
};
This approach allows components to access only the state and functions they need, promoting a clean and efficient design.

Benefits of Feature-Level State Management

  1. Modularity: Each feature manages its own state, making it easier to develop and maintain large applications.
  2. Reduced Prop Drilling: Context allows state to be accessed by deeply nested components without passing props through intermediate components.
  3. Separation of Concerns: Feature-specific state is kept separate from global application state, improving code organization.
  4. Reusability: Features can be more easily reused or moved between projects when their state management is self-contained.
By leveraging React’s Context API and custom hooks, we can create a robust and scalable state management solution at the feature level. This approach provides a clean and efficient way to share state among components while maintaining a clear separation of concerns in your React application.