Where the check happens
@evanion/compose 2.1.0 and its patch releases up to 2.1.1, the version on npm. Values on this page were produced by running the source at 77650ee, a later commit than the 2.1.0 tag, and are not re-executed.
In a complex React application, it’s easy to pass the wrong props to a provider, especially when the provider list is long. Because ComposeProvider is a generic wrapper, it could technically render a provider with missing or incorrect props without any immediate warning—only for the app to crash at runtime.
To prevent this, @evanion/compose moves the validation to compile time using advanced TypeScript generics.
Validating Providers at Different Stages
Depending on how you define your provider entries, the compiler checks the props at different times. Consider three providers: CartProvider (no props), ThemeProvider (requires theme), and CurrencyProvider (requires currency).
const = (
<
={[
// A bare component, for a provider whose props are all optional.
,
// A `provider()` call, checked where it is written.
(, { : 'dark' }),
// A tuple, checked where the array reaches `ComposeProvider`.
[, { : 'SEK' }],
]}
>
<>Brass: Birmingham</>
</>,
);
; // -> '<div id="cart"><div id="dark"><div id="SEK"><p>Brass: Birmingham</p></div></div></div>'The checks differ by entry type:
provider()calls: This helper checks props at the call site. You get autocomplete and immediate errors as you type, making it the preferred way to define tuples.- Bare components: Used for providers with all-optional props (like
CartProvider). If a bare component is used but requires mandatory props, the compiler will flag it. - Hand-written tuples: If you write
[Component, Props]manually, the check is deferred until the array is passed toComposeProvider.
Type Safety for Inline Arrays
When you write your provider array inline within the providers prop, ComposeProvider uses a TypeScript const type parameter to ensure every entry is checked against its own component.
If you provide an incorrect prop for ThemeProvider, the compiler identifies exactly which entry in the array is failing.
const = (
<
={[
ThemeProvider, [, {}], [, { theme: 'blue' }], [ThemeProvider, { theme: 'dark', accent: 'green' }], ]}
>
<>Brass: Birmingham</>
</>,
);
; // -> '<div><div><div id="blue"><div id="dark"><p>Brass: Birmingham</p></div></div></div></div>'The compiler can detect multiple types of errors:
| Entry | Compiler Report | Meaning |
|---|---|---|
ThemeProvider | this component requires props — … | You used a bare component, but it has mandatory props. |
[ThemeProvider, {}] | Property 'theme' is missing | The props object is missing a required key. |
[ThemeProvider, { theme: 'blue' }] | Type 'string' is not assignable to type 'never' | The value provided is not among the allowed literals. |
[ThemeProvider, { theme: 'dark', accent: 'green' }] | unknown prop 'accent' | You provided a prop the component doesn’t declare. |
Debugging Compose Errors
To make these complex TypeScript failures readable, @evanion/compose wraps them in a ComposeError type. When a check fails, the expected type of the entry becomes ComposeError<"message">, except for a missing or mistyped prop value, where it becomes the shape the entry should have had.
Since no value can match this type, the entry fails to compile, and the message is displayed directly in the TypeScript output.
| Message | Meaning |
|---|---|
unknown prop 'x' | The props object contains a key the component does not declare. |
this component requires props — ... | A bare component was used, but it requires mandatory props. |
first tuple element must be a component | The tuple is ordered incorrectly or the first element is not a component. ComposeProvider rejects this entry earlier, as a Provider or AnyComponent mismatch. |
second tuple element must be props... | The tuple contains two components instead of a component and its props. |
not a valid provider | The entry is neither a component nor a valid two-element tuple. ComposeProvider rejects this entry earlier, as a Provider mismatch. |
Solving the “Type ‘string’ is not assignable to type ‘never’” Error
You may encounter a confusing error like Type 'string' is not assignable to type 'never' when providing a value outside the allowed literals (e.g., 'blue' when only 'light' or 'dark' are accepted).
This happens because TypeScript intersects your provided type with the correct type; since they don’t overlap, the result is never.
The Fix: Use the provider() helper. It avoids this intersection and reports the error at the call site with a much clearer message: Type '"blue"' is not assignable to type '"light" | "dark"'.
Maintain Checks in Separate Constants
In real applications, provider arrays are often moved to a const or a config file to keep the JSX clean. Keeping the check explains how to maintain these compile-time checks when the array is moved out of the JSX.