MAL's Logo

UseContext

đź’ˇ Example on how to use useContext to pass props globally instead of constantly depending on state management libraries.

Introduction

It is recommended to utilise useContext when a certain value needs to be re-used across the application, or when components don’t communicate directly with one another and passing props alone will be difficult or outright impossible.

Under in the Code section, I opted to share two examples. Option I, will throw an error and make an application unusable if not properly used - can be great to catch on errors, but not always the right fit. At times, due to an application’s complexity, Option II may be a better approach.

Breakdown

To properly use useContext, I prefer to divide it in three parts:

  • createContext - offer start values or default to null
  • provider - the component to wrap around the application
  • useContext - the function that can be reused where it’s needed
  • It is best to do these steps in its own file, to have all the logic in one place and thereby it should be easier to maintain.

    Code

    Option I

    In this example, our context begins with a nullable version, where, if the context remains null, the application will throw out an error. Error handling can save up on a lot of development time, as this approach will complain if the hooks that belong to this provider are not used within said provider.

    type UploadContextValue = {
      isUpload: boolean;
      setIsUpload: React.Dispatch<React.SetStateAction<boolean>>;
    };
    
    const UploadContext = React.createContext<UploadContextValue | null>(null);
    
    export const UploadProvider = React.memo(
    	({ children }: { children: React.ReactNode; }) => {
      const [isUpload, setIsUpload] = React.useState(false);
    
      const value = React.useMemo(() => (
          {
            isUpload, 
            setIsUpload,
          }
      ), [isUpload, setIsUpload]);
    
      return (
          <UploadContext.Provider value={value}>
              {children}
          </UploadContext.Provider>
      );
    });
    
    const useNullableUploadContext = () => React.useContext(UploadContext);
    
    const useUploadContext = () => {
      const context = useNullableUploadContext();
      if (context === null) {
          throw new Error('No UploadContext defined.');
      }
      return context;
    };
    
    export const useUploadState = () => {
       const context = useUploadContext();
       return React.useMemo(() =>
         [context.isUpload, context.setIsUpload] as const,
         [context.isUpload, context.setIsUpload]);
     };

    Option II

    In contrast to the prior example, in this example, useContext starts with pre-defined values and will not throw an error even if it is being used incorrectly.

    type DownloadContextValues = {
      isOpen: boolean;
      setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
    };
    
    const defaultValues = {
      isOpen: false,
      setIsOpen: () => {}
    };
    
    const DownloadContext = React.createContext<DownloadContextValues>(defaultValues);
    
    export const DownloadProvider = React.memo(
    	({ children }: { children: React.ReactNode; }) => {
      const [isOpen, setIsOpen] = React.useState<boolean>(false);
    
      const value = React.useMemo(() => (
          {
              isOpen,
              setIsOpen,
          }
      ), [isOpen]);
    
      return (
          <DownloadContext.Provider value={value}>
              {children}
          </DownloadContext.Provider>
      );
    });
    
    export const useIsDownloadOpen = () => {
      const { isOpen, setIsOpen } = React.useContext(DownloadContext);
      return React.useMemo(() => [isOpen, setIsOpen] as const, [isOpen, setIsOpen]);
    };

    Resources

  • https://react.dev/reference/react/useContext
  • MAL's Logo© 2021 – 2026 MAL. All rights reserved.