What is the most concise way to provide and consume context values in React? [closed]

I am looking for the most concise way, in React, to provide context to a component (say a "page") and all its subcomponents. For now, the most concise way I have found includes 6 files (!), plus one file per subcomponent that needs the context. Important notice: I am separating a lot of stuff instead of putting everything in the same file because I want to avoid the following warning: Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components. Here is my MWE in the case where our main component FooPage has 2 subcomponents FooButton and FooText, so, in this case, we have 8 files in total: File #1: define the type of the context value. // types.ts // define the type of the value provided by the context, // it depends on which stuff we want to provide to the subcomponents export type FooContextType = { foo: number; setFoo: React.Dispatch; }; File #2: the actual outer component that we will use in our web page. // FooPageWrapper.tsx // This component is the one we actually use, it wraps // the FooPage inside the context provider. import FooProvider from "./context/FooProvider"; import FooPage from "./FooPage"; export default function FooPageWrapper() { return ( ); } File #3: the actual content (which needs to be wrapped in the context provider). // FooPage.tsx // This component is the one with the actual content. // It must be used inside the context provider FooProvider. import FooButton from "./components/FooButton"; import FooText from "./components/FooText"; export default function FooPage() { return ( Foo Page ); } File #4: the definition of the context itself. // context/FooContext.tsx // defines the context. import { createContext } from "react"; import { FooContextType } from "../types"; export const FooContext = createContext(undefined); File #5: the context provider, in which we will put all our logic (this is the most important one). // context/FooProvider.tsx // This is the actual context provider component. // Here we provide the context value using useState, // but we could provide a more complex value if needed, // using useReducer or other state management solutions. import { useState } from "react"; import { FooContext } from "./FooContext"; export default function FooProvider({ children, }: { children: React.ReactNode; }) { const [foo, setFoo] = useState(0); return ( {children} ); } File #6: a custom hook to easily access the context value in our subcomponents. // context/useFoo.tsx // Custom hook to use the context FooContext // when we are in a component that is wrapped by the FooProvider. import { useContext } from "react"; import { FooContext } from "./FooContext"; export default function useFoo() { const c = useContext(FooContext); if (c === undefined) { throw new Error("FooContext is undefined"); } return c; } File #7: a first subcomponent that uses the context. // components/FooButton.tsx // Example of a component that uses the context FooContext // (via the custom hook useFoo). import useFoo from "../context/useFoo"; export default function FooButton() { const { foo, setFoo } = useFoo(); return ( setFoo(foo + 1)}> Foo is {foo}, click to increment it! ); } File #8: a second subcomponent that uses the context. // components/FooText.tsx // Example of a component that uses the context FooContext // (via the custom hook useFoo). import useFoo from "../context/useFoo"; export default function FooText() { const { foo } = useFoo(); return ( Foo is {foo}, this is a text component that uses the Foo context! ); } The example works as expected: we obtain a page with a button and a text, which both can read and write the value of foo in the context. However, this is very verbose. Is there any way to make it more concise?

May 29, 2025 - 07:10
 0

I am looking for the most concise way, in React, to provide context to a component (say a "page") and all its subcomponents. For now, the most concise way I have found includes 6 files (!), plus one file per subcomponent that needs the context.

Important notice: I am separating a lot of stuff instead of putting everything in the same file because I want to avoid the following warning:

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components.

Here is my MWE in the case where our main component FooPage has 2 subcomponents FooButton and FooText, so, in this case, we have 8 files in total:

File #1: define the type of the context value.

// types.ts

// define the type of the value provided by the context,
// it depends on which stuff we want to provide to the subcomponents
export type FooContextType = {
  foo: number;
  setFoo: React.Dispatch>;
};

File #2: the actual outer component that we will use in our web page.

// FooPageWrapper.tsx
// This component is the one we actually use, it wraps
// the FooPage inside the context provider.
import FooProvider from "./context/FooProvider";
import FooPage from "./FooPage";

export default function FooPageWrapper() {
  return (
    
      
    
  );
}

File #3: the actual content (which needs to be wrapped in the context provider).

// FooPage.tsx
// This component is the one with the actual content.
// It must be used inside the context provider FooProvider.
import FooButton from "./components/FooButton";
import FooText from "./components/FooText";

export default function FooPage() {
  return (
    

Foo Page

); }

File #4: the definition of the context itself.

// context/FooContext.tsx
// defines the context.
import { createContext } from "react";

import { FooContextType } from "../types";

export const FooContext = createContext(undefined);

File #5: the context provider, in which we will put all our logic (this is the most important one).

// context/FooProvider.tsx
// This is the actual context provider component.
// Here we provide the context value using useState,
// but we could provide a more complex value if needed,
// using useReducer or other state management solutions.
import { useState } from "react";

import { FooContext } from "./FooContext";
export default function FooProvider({
  children,
}: {
  children: React.ReactNode;
}) {
  const [foo, setFoo] = useState(0);
  return (
    
      {children}
    
  );
}

File #6: a custom hook to easily access the context value in our subcomponents.

// context/useFoo.tsx
// Custom hook to use the context FooContext
// when we are in a component that is wrapped by the FooProvider.
import { useContext } from "react";

import { FooContext } from "./FooContext";
export default function useFoo() {
  const c = useContext(FooContext);
  if (c === undefined) {
    throw new Error("FooContext is undefined");
  }
  return c;
}

File #7: a first subcomponent that uses the context.

// components/FooButton.tsx
// Example of a component that uses the context FooContext
// (via the custom hook useFoo).
import useFoo from "../context/useFoo";

export default function FooButton() {
  const { foo, setFoo } = useFoo();
  return (
    
  );
}

File #8: a second subcomponent that uses the context.

// components/FooText.tsx
// Example of a component that uses the context FooContext
// (via the custom hook useFoo).
import useFoo from "../context/useFoo";
export default function FooText() {
  const { foo } = useFoo();
  return (
    
      Foo is {foo}, this is a text component that uses the Foo context!
    
  );
}

The example works as expected: we obtain a page with a button and a text, which both can read and write the value of foo in the context. However, this is very verbose.

Is there any way to make it more concise?