# How to Define Default Values in React createContext with TypeScript

> Learn how to define default values in React createContext with TypeScript. Satisfy type constraints by providing object literals, union types, or no-op functions.

- Repository: [Meta/react](https://github.com/facebook/react)
- Tags: how-to-guide
- Published: 2026-02-16

---

**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`](https://github.com/facebook/react/blob/main/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`](https://github.com/facebook/react/blob/main/packages/react/src/ReactContext.js). At line 14, the function signature is effectively:

```typescript
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`](https://github.com/facebook/react/blob/main/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.

```tsx
// 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.

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

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

export const AuthContext = createContext<User | null>(null);

```

```tsx
// 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.

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

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

export const ToastContext = createContext<ShowToast>(() => {});

```

```tsx
// 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:

```tsx
// 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>
  );
}

```

```tsx
// 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`](https://github.com/facebook/react/blob/main/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`](https://github.com/facebook/react/blob/main/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.