'PropsWithChildren' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.ts(1484)
If you’ve recently upgraded to TypeScript 5+ and started seeing this new error in your React project:
'PropsWithChildren' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.ts(1484)
Don’t worry — you didn’t break React.
You just met one of the most important compiler upgrades in recent TypeScript history: verbatimModuleSyntax
.
The Problem Example
Let’s say you’re using TanStack Query v5 and write a helper like this:
import { PropsWithChildren } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
export function withQueryClient(ui: React.ReactElement) {
const client = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
const Wrapper = ({ children }: PropsWithChildren) => (
<QueryClientProvider client={client}>{children}</QueryClientProvider>
);
return { ui, Wrapper };
}
TypeScript 5.0+ throws the error because you’re importing a type (PropsWithChildren
) as if it were a runtime value — and you have "verbatimModuleSyntax": true
in your tsconfig.json
.
Why This Happens
Historically, TypeScript performed import elision — it analyzed which imports were only used for types, and dropped them automatically during compilation.
But this caused headaches when mixing type imports, side-effect imports, and export elision.
So TypeScript 5 introduced a simpler and more explicit rule set:
✅ Under
verbatimModuleSyntax
, TypeScript no longer elides imports automatically.
You must explicitly mark type-only imports using thetype
modifier.
Example:
// Old way (TS < 5.0)
import { PropsWithChildren } from 'react';
// New way (TS 5+ with verbatimModuleSyntax)
import type { PropsWithChildren } from 'react';
Now, what you see in your import statements is exactly what you’ll get in the compiled JavaScript.
Fixing the Error (3 Proven Options)
✅ Option 1 — Use a Type-Only Import (Recommended)
import type { PropsWithChildren } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
export function withQueryClient(ui: React.ReactElement) {
const client = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
const Wrapper = ({ children }: PropsWithChildren) => (
<QueryClientProvider client={client}>{children}</QueryClientProvider>
);
return { ui, Wrapper };
}
👉 This is the correct, future-proof way. It satisfies the compiler and makes your imports explicit.
✅ Option 2 — Use the React Namespace
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
export function withQueryClient(ui: React.ReactElement) {
const client = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
const Wrapper = ({ children }: React.PropsWithChildren }) => (
<QueryClientProvider client={client}>{children}</QueryClientProvider>
);
return { ui, Wrapper };
}
✅ React.PropsWithChildren
references the global React types from @types/react
, so you don’t need to import anything at all.
⚠️ Option 3 — Disable verbatimModuleSyntax
(Not Recommended)
// tsconfig.json
{
"compilerOptions": {
"verbatimModuleSyntax": false
}
}
This brings back TypeScript’s older “import elision” behavior — but removes the clarity and consistency gained in 5.0+.
Use this only as a temporary migration step.
Bonus: Improving the Test Wrapper Pattern
If your wrapper is for testing (e.g., Vitest, Jest), you can enhance it a bit:
import type { PropsWithChildren } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
export function withQueryClient(ui: React.ReactElement) {
const client = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
function Wrapper({ children }: PropsWithChildren) {
return <QueryClientProvider client={client}>{children}</QueryClientProvider>;
}
return { ui, Wrapper, client };
}
Then in your tests:
import { render } from '@testing-library/react';
import { withQueryClient } from './withQueryClient';
test('renders data', () => {
const { ui, Wrapper } = withQueryClient(<MyComponent />);
render(ui, { wrapper: Wrapper });
});
Deep Dive — What verbatimModuleSyntax
Actually Does
Before TypeScript 5.0, the compiler tried to “guess” which imports were needed at runtime.
This guessing game led to subtle inconsistencies across compilers, bundlers, and file types.
Example (before TypeScript 5.0)
import { Car } from './car';
export function drive(car: Car) {
// ...
}
After transpilation, TypeScript elides the import entirely:
export function drive(car) {
// ...
}
But this behavior was inconsistent — what if ./car
had side effects?
What if another compiler (like Babel) didn’t drop it the same way?
The Fix: Simplicity via Explicitness
verbatimModuleSyntax
means:
-
Imports without
type
→ stay in emitted JS. -
Imports with
type
→ erased entirely.
“What you see is what you get.”
That makes builds predictable and aligns TypeScript with the ECMAScript standard.
Related Deprecations
-
--importsNotUsedAsValues
→ Deprecated. -
--preserveValueImports
→ Deprecated. -
--verbatimModuleSyntax
→ The new standard for clarity and correctness.
When It Breaks — Common Pitfalls
Scenario | Problem | Fix |
---|---|---|
Importing only types as values | 'X' is a type and must be imported using a type-only import |
Use import type { X }
|
Using both values & types from the same module | Compiler warns you | Combine both: import { Button, type ButtonProps } from './Button'
|
Tooling mismatch | Old Babel/TS config | Ensure your bundler supports modern ESM and TS 5+ |
Final Takeaway
verbatimModuleSyntax
is one of those rare TypeScript changes that simplifies your mental model instead of complicating it.
Old World | New World |
---|---|
Implicit import guessing | Explicit type/value imports |
Complex flags (importsNotUsedAsValues ) |
One clear rule |
Risk of side-effect elision | Deterministic output |
In short:
✅ Use
import type
for types.
✅ Useimport
for values.
🧠 Be explicit, not implicit.
With this understanding, the 'PropsWithChildren'
error is no longer a mystery — it’s a gentle nudge from TypeScript guiding you toward better module hygiene.
✍️ Written by Cristian Sifuentes — Full‑stack developer & AI/JS enthusiast focused on resilient frontends, scalable architectures, and TypeScript excellence.
Tags: #react #typescript #frontend #architecture #programming
Top comments (0)