How to Define Default Values in React createContext with TypeScript

When using React createContext with TypeScript, the default value passed to createContext<T>() must be assignable to the generic type T, which you can satisfy by providing a full object literal, using a union type with null, or supplying a no-op function for callback contexts.

Setting up React createContext in TypeScript requires understanding how the generic type parameter constrains the default value argument. In the facebook/react repository, the createContext implementation in packages/react/src/ReactContext.js stores this default in internal properties like _currentValue, and TypeScript enforces that this value matches your declared interface at compile time.

Understanding the Type Constraint in React createContext

The TypeScript declaration for createContext reflects the implementation found in packages/react/src/ReactContext.js. At line 14, the function signature is effectively:

export function createContext<T>(defaultValue: T): ReactContext<T>;

When you invoke createContext<YourType>(), TypeScript compares the argument you supply against the generic T. If the argument is not assignable to T, the compiler raises a type-mismatch error.

Internally, React stores this default value in the context object's _currentValue and _currentValue2 fields (lines 20–30 in packages/react/src/ReactContext.js). These fields are later read by useContext and the Provider component (lines 36–38), meaning the default value serves as the fallback when a component consumes the context outside of a matching <Provider>.

Pattern 1: Full Object Default Values

Provide a concrete object that satisfies your interface when you want safe access without null checks. This pattern ensures that consuming components always receive a valid object, even if no Provider is present.

// src/context/ThemeContext.tsx
import {createContext} from 'react';

interface Theme {
  primaryColor: string;
  backgroundColor: string;
  spacing: number;
}

const defaultTheme: Theme = {
  primaryColor: '#0070f3',
  backgroundColor: '#ffffff',
  spacing: 8,
};

export const ThemeContext = createContext<Theme>(defaultTheme);

The default object adheres to the Theme type, so any component calling useContext(ThemeContext) receives a fully-typed Theme without needing null checks.

Pattern 2: Null Placeholder with Union Types

Use a union type T | null when the real value will always be supplied by a <Provider> and you prefer explicit null checks in consuming components.

// src/context/AuthContext.tsx
import {createContext} from 'react';

interface User {
  id: string;
  name: string;
  email: string;
}

export const AuthContext = createContext<User | null>(null);
// src/components/Profile.tsx
import {useContext} from 'react';
import {AuthContext} from '../context/AuthContext';

export function Profile() {
  const user = useContext(AuthContext);
  if (!user) {
    return <p>Loading…</p>;
  }
  return <div>{user.name}</div>;
}

Here the union User | null lets TypeScript enforce a runtime null check before accessing user properties.

Pattern 3: Function Defaults for Callback Contexts

Provide a no-op or fallback function when the context holds a callback signature. This prevents "undefined is not a function" errors when components call the context function outside of a Provider.

// src/context/ToastContext.tsx
import {createContext} from 'react';

type ShowToast = (msg: string, type?: 'info' | 'error') => void;

export const ToastContext = createContext<ShowToast>(() => {});
// src/components/Notifier.tsx
import {useContext} from 'react';
import {ToastContext} from '../context/ToastContext';

export function Notifier() {
  const showToast = useContext(ToastContext);
  const handleClick = () => showToast('Saved!', 'info');
  return <button onClick={handleClick}>Save</button>;
}

The default () => {} satisfies the ShowToast signature, allowing components to call the function safely even when a real provider is absent.

Common Pitfalls When Using React createContext with TypeScript

Pitfall Symptom Fix
Supplying {} for a complex interface "Property X is missing in type {}" Provide a real object, or widen the type to Partial<T> and handle missing fields.
Using null without union type "Argument of type null is not assignable to parameter of type T" Change the generic to T | null.
Forgetting to export the context Provider/Consumer see undefined Ensure the context is exported from its module and imported correctly.
Mixing default values between server and client bundles Runtime mismatch warnings Keep the default value identical across environments; the core creates the same object for both client and server.

Complete Implementation Example

Below is a complete minimal setup that demonstrates the three patterns discussed:

// src/contexts/AppContext.tsx
import {createContext, ReactNode, useState} from 'react';

// 1️⃣ Full object default
interface Settings {
  theme: 'light' | 'dark';
  language: string;
}
const defaultSettings: Settings = {theme: 'light', language: 'en'};
export const SettingsContext = createContext<Settings>(defaultSettings);

// 2️⃣ Null placeholder for auth
interface Auth {
  token: string;
  userId: string;
}
export const AuthContext = createContext<Auth | null>(null);

// 3️⃣ Function default for notifications
type Notify = (msg: string) => void;
export const NotifyContext = createContext<Notify>(() => {});

export function AppProvider({children}: {children: ReactNode}) {
  const [settings, setSettings] = useState(defaultSettings);
  const [auth, setAuth] = useState<Auth | null>(null);
  const notify: Notify = (msg) => alert(msg);

  return (
    <SettingsContext.Provider value={settings}>
      <AuthContext.Provider value={auth}>
        <NotifyContext.Provider value={notify}>{children}</NotifyContext.Provider>
      </AuthContext.Provider>
    </SettingsContext.Provider>
  );
}
// src/components/Example.tsx
import {useContext} from 'react';
import {SettingsContext, AuthContext, NotifyContext} from '../contexts/AppContext';

export function Example() {
  const settings = useContext(SettingsContext); // always defined
  const auth = useContext(AuthContext);
  const notify = useContext(NotifyContext);

  return (
    <div>
      <p>Theme: {settings.theme}</p>
      {auth ? <p>User: {auth.userId}</p> : <p>Not logged in</p>}
      <button onClick={() => notify('Hello from Example!')}>Notify</button>
    </div>
  );
}

All three contexts follow the type-safe patterns explained above.

Summary

  • React createContext with TypeScript requires the default value argument to be assignable to the generic type T declared in the function call.
  • The core implementation in packages/react/src/ReactContext.js stores the default value in _currentValue and _currentValue2, using it as a fallback when no Provider is present.
  • Full object defaults provide the safest consumption experience, eliminating the need for null checks in components.
  • Union types with null or undefined allow you to represent uninitialized states explicitly, forcing runtime checks where appropriate.
  • Function defaults prevent runtime errors by supplying no-op implementations for callback contexts.
  • Always ensure the default value matches the shape of your interface to avoid TypeScript compilation errors.

Frequently Asked Questions

Why does TypeScript complain when I pass null to createContext without a union type?

TypeScript enforces that the default value argument must satisfy the generic type T. If you declare createContext<User>(null), the compiler sees that null is not assignable to User. To fix this, declare the context as createContext<User | null>(null), which tells TypeScript that the context may legally contain either a User object or null.

What happens if a component uses useContext without a matching Provider?

React returns the default value provided to createContext. According to the implementation in packages/react/src/ReactContext.js, this value is stored in the context object's _currentValue property during initialization. If you provided a full object default, the component receives that object. If you used a null default with a union type, the component receives null and should handle that case accordingly.

Can I use undefined instead of null for optional context values?

Yes. You can declare the context as createContext<Settings | undefined>(undefined) if you prefer checking for undefined over null. The choice between null and undefined is largely stylistic, but you must remain consistent with your runtime checks. TypeScript will enforce that you handle the undefined case before accessing properties on the context value.

Why should I provide a function as the default value for callback contexts?

Providing a no-op function (such as () => {}) prevents runtime errors when a component calls the context function outside of a Provider. Without a default function, the context value would be undefined, leading to "undefined is not a function" errors. A no-op default satisfies the TypeScript function signature while safely doing nothing when invoked.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →