Context

Context

Context is a means to provide values from a component to all components that are contained within it - without explicitly passing it through all intermediate levels.

The interface of context can pass both data and event handlers

Context

an application can have many different context types, e.g.:

  • ThemeContext
  • LanguageContext
  • UserContext
  • TodosContext
  • ...

Context

a component higher up in the component tree may provide a context

a component lower down may consume a context

(downwards dataflow, but with skipped intermediate levels)

Context

possible structure with "stateful providers":

  • LanguageProvider (manages / provides language state)
    • ThemeProvider (manages / provides theme state)
      • TodosProvider
        • App
          • TodoList
            • TodoItem
              • ...

Provider components

distinction of providers:

  • "pure provider" - receives data via props, does not have internal state
  • "stateful provider" - has internal state, uses a "pure provider" to make its state available
    • may be defined "manually"
    • may be defined via the constate library

Stateful context with constate

Constate

constate: library that creates a context with an associated state

Constate

defining the context:

custom hook that manages todo data:

function useTodos() {
  // ...
  return { todos, addTodo, deleteTodo, setTodoCompleted };
}

creating a provider component and a consumer hook:

const [TodosProvider, useTodosContext] = constate(useTodos);

Constate

providing the context:

function App() {
  return (
    <TodosProvider>
      <AddTodoForm />
      <TodoList />
      <Statistics />
    </TodosProvider>
  );
}

Constate

querying the context:

function TodoList() {
  const {
    todos,
    deleteTodo,
    setTodoCompleted,
  } = useTodosContext();
  // ...
}

Context with pure React

Context

steps to provide context:

  • define context (and maybe its type / default values)
  • optional: add state to the provider
  • include provider component in the component hierarchy

steps to query context:

  • call useContext in a component

Context definition

defining a context in JavaScript:

// ThemeContext.js

import { createContext } from 'react';

// default value: null
const ThemeContext = createContext(null);

export { ThemeContext };

Default value and TypeScript

A context always has a default / fallback value which will be used when no matching context provider is found

In TypeScript it can be cumbersome to define a default value to satisfy the type checker

Default value and TypeScript

defining a context type in TypeScript:

type ThemeContextType = {
  theme: string;
  onThemeChange: (theme: string) => void;
};

Default value and TypeScript

to satisfy the type checker when creating the context:

actually provide fallback values:

const ThemeContext = createContext<ThemeContextType>({
  theme: 'light',
  onThemeChange: () => {},
});

use null and cast to any:

const ThemeContext = createContext<ThemeContextType>(
  null as any
);

more information / solutions: React TypeScript cheatsheets

Context with "pure providers"

Example: using a "pure provider" (the state is in the App component)

function App() {
  const [theme, setTheme] = useState('light');
  return (
    <ThemeContext.Provider
      value={{ theme: theme, onThemeChange: setTheme }}
    >
      ... sub-components ...
    </ThemeContext.Provider>
  );
}

Context with "pure providers"

example: product page with a "pure" context provider

function ProductPage() {
  // ... (load product data from an API)

  return (
    <ProductContext.Provider value={productData}>
      <ProductDescription />
      <ProductPictures />
      <ProductReviews />
      <ProductQuestionsAndAnswers />
    </ProductContext.Provider>
  );
}

Context with "stateful providers"

Example: creating a stateful provider that manages and provides state:

// TodosContext.tsx
function TodosProvider(props: { children: ReactNode }) {
  const todosCtrl = useTodos();
  <TodosContext.Provider value={todosCtrl}>
    {props.children}
  </TodosContext.Provider>;
}
function TodoApp() {
  return <TodosProvider>...</TodosProvider>;
}

Context

querying context from within a component:

const todosContext = useContext(TodosContext);