React.createContext point of defaultValue?

JavascriptReactjs

Javascript Problem Overview


On the React 16 Context doc page, they have examples that look similar to this one:

const defaultValue = 'light'
const SomeContext = React.createContext(defaultValue)

const startingValue = 'light'
const App = () => (
  <SomeContext.Provider theme={startingValue}>
    Content
  </SomeContext.Provider>
)

It seems that the defaultValue is useless because if you instead set the startingValue to anything else or don't set it (which is undefined), it overrides it. That's fine, it should do that.

But then what's the point of the defaultValue?

If I want to have a static context that doesn't change, it would be nice to be able to do something like below, and just have the Provider been passed through the defaultValue

const App = () => (
  <SomeContext.Provider>
    Content
  </SomeContext.Provider>
)

Javascript Solutions


Solution 1 - Javascript

When there's no Provider, the defaultValue argument is used for the function createContext. This is helpful for testing components in isolation without wrapping them, or testing it with different values from the Provider.


Code sample:

import { createContext, useContext } from "react";

const Context = createContext( "Default Value" );

function Child() {
  const context = useContext(Context);
  return <h2>Child1: {context}</h2>;
}

function Child2() {
  const context = useContext(Context);
  return <h2>Child2: {context}</h2>;
}

function App() {

  return (
    <>
      <Context.Provider value={ "Initial Value" }>
        <Child /> {/* Child inside Provider will get "Initial Value" */}
      </Context.Provider>
        <Child2 /> {/* Child outside Provider will get "Default Value" */}
    </>
  );
}

Codesandbox Demo

Solution 2 - Javascript

Just sharing my typical setup when using TypeScript, to complete answer from @tiomno above, because I think many googlers that ends up here are actually looking for this:

interface GridItemContextType {
    /** Unique id of the item */
    i: string;
}
const GridItemContext = React.createContext<GridItemContextType | undefined>(
    undefined
);

export const useGridItemContext = () => {
    const gridItemContext = useContext(GridItemContext);
    if (!gridItemContext)
        throw new Error(
            'No GridItemContext.Provider found when calling useGridItemContext.'
        );
    return gridItemContext;
};

The hook provides a safer typing in this scenario. The undefined defaultValue protects you from forgetting to setup the provider.

Solution 3 - Javascript

My two cents:

After reading this instructive article by Kent C. Dodds as usual :), I learnt that the defaultValue is useful when you destructure the value returned by useContext:

Define the context in one corner of the codebase without defaultValue:

const CountStateContext = React.createContext() // <-- define the context in one corner of the codebase without defaultValue

and use it like so in a component:

const { count } = React.useContext(CountStateContext)

JS will obviously say TypeError: Cannot read property 'count' of undefined

But you can simply not do that and avoid the defaultValue altogether.

About tests, my teacher Kent has a good point when he says:

> The React docs suggest that providing a default value "can be helpful > in testing components in isolation without wrapping them." While it's > true that it allows you to do this, I disagree that it's better than > wrapping your components with the necessary context. Remember that > every time you do something in your test that you don't do in your > application, you reduce the amount of confidence that test can give > you.

Extra for TypeScript; if you don't want to use a defaultValue, it's easy to please the lint by doing the following:

const MyFancyContext = React.createContext<MyFancyType | undefined>(undefined)

You only need to be sure to add the extra validations later on to be sure you have covered the cases when MyFancyContext === undefined

  • MyFancyContext ?? 'default'
  • MyFancyContext?.notThatFancyProperty

etc

Solution 4 - Javascript

You can set the default values using useReducer hook, then the 2nd argument will be the default value:

import React, { createContext, useReducer } from "react";
import { yourReducer } from "./yourReducer";

export const WidgetContext = createContext();

const ContextProvider = (props) => {

  const { children , defaultValues } = props;
 
  const [state, dispatch] = useReducer(yourReducer, defaultValues);

  return (
    <WidgetContext.Provider value={{ state, dispatch }}>
      {children}
    </WidgetContext.Provider>
  );
};

export default ContextProvider;

// implementation

   <ContextProvider
                  defaultValues={{
                    disabled: false,
                    icon: undefined,
                    text: "Hello",
                    badge: "100k",
                    styletype: "primary",
                    dir: "ltr",
                    }}
                >
    </ContextProvider>
    

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMerlin -they-them-View Question on Stackoverflow
Solution 1 - JavascriptArielle NguyenView Answer on Stackoverflow
Solution 2 - JavascriptEric BurelView Answer on Stackoverflow
Solution 3 - JavascripttiomnoView Answer on Stackoverflow
Solution 4 - JavascriptLucas MatosView Answer on Stackoverflow