skip to Main Content

So in React, the rules of hooks says that hooks cannot be called outside of functional components.

My predicament: I need to initialize state in my createContext using a hook for the SchedulesProvider. The state is a dynamic date, like two days from the current date. Hooks can’t be called outside of functional components. createContext cannot be called within the provider component because it needs to be exported and live outside of the SchedulesProvider.

What I’ve tried

const utils = useUtils(); //useUtils is a hook from a date library

export const ScheduleContext = createContext({
  scheduleStartDate: utils.date().add(2, "days"), //using the hook here
  ...  
});

My provider is trying to pass the state down like so…

  const { scheduleStartDate } = useContext(ScheduleContext);
    <ScheduleContext.Provider
      value={{
        scheduleEndDate: scheduleEndDate,
      }}
    >
      {children}
    </ScheduleContext.Provider>

Error I get: Invalid hook call. Hooks can only be called inside of the body of a function component.

2

Answers


  1. You probably don’t need a default value for your context. The default value is only used if there is no ScheduledContext.Provider higher up the component tree, and you can arrange your components so that never happens.

    If you don’t need a default value, you can wait until you are rendering the provider to call useUtils. For example:

    export const ScheduleContext = createContext(null);
    // ...
    const MyProvider = ({ children }) => {
      const utils = useUtils();
      return (
        <ScheduleContext.Provider value={{ 
          scheduledEndDate: utils.date().add(2, "days")
        }}>
          {children}
        </ScheduledContext.Provider>
      )
    }
    
    Login or Signup to reply.
  2. The value you pass to createContext() is more like a fallback for when it’s used outside of your context provider hierarchy.

    From createContext()

    Parameters

    • defaultValue: The value that you want the context to have when there is no matching context provider in the tree above the component that reads context. If you don’t have any meaningful default value, specify null. The default value is meant as a “last resort” fallback. It is static and never changes over time.

    Set the real value with your own provider component instead

    const ScheduleContext = createContext({
      scheduleStartDate: new Date(), // or literally anything really
      // ...
    });
    
    // define a context provider component
    const ScheduleContextProvider = ({ children }) => {
      const { date } = useUtils();
      const scheduleStartDate = date().add(2, 'days');
    
      return (
        <ScheduleContext.Provider value={{ scheduleStartDate }}>
          {children}
        </ScheduleContext.Provider>
      );
    };
    
    // this is also a very handy custom hook for your context
    const useSchedule = () => useContext(ScheduleContext);
    
    export { ScheduleContextProvider as default, useSchedule };
    

    I prefer to set a real-ish value for two reasons…

    1. It prevents possible runtime errors like "Cannot read properties of null" or "Cannot destructure property ‘x’ of ‘null’"
    2. If using TypeScript, it allows you to avoid checking for potential nulls every time you want to use your context
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search