Argument of type 'string' is not assignable to parameter of type '...' (TS2345)
Filed under Next.js & React errors
You passed a value whose type is broader than what the function accepts — usually a plain string where one of a fixed set of values is required — because the value lost its specific type somewhere between where it was created and where it's used.
Fastest route
Give your AI this prompt
Copy it, replace the bracketed part with your error, and send it.
Preview the prompt
My build fails with TypeScript error TS2345: "Argument of type '[given]' is not assignable to parameter of type '[wanted]'" at [file/line]. Please: 1) trace the value being passed back to where it is created, and tell me exactly where its type widens from the narrow type to the general one, 2) fix it at that source — an explicit type on the useState or prop, or 'as const' on the constant — rather than at the call site, 3) if the value comes from a URL parameter, form, storage, or fetch, add a real check against the allowed values before it is passed on, 4) if the given type includes undefined, show me the code path where the value can be missing and handle it there. Constraints: no 'as any', no ts-ignore, and do not loosen the function's parameter type to plain string to make the error stop. Make one change at a time and tell me how to verify the build passes.
Fix it yourself
Follow the repair
- Read the whole error, backwards. It says: parameter wants type A; you gave type B. Name the pair out loud — "it wants 'sm' | 'lg', I'm giving string" — because the fix is always about B, never about arguing with A. If B includes
undefined, skip ahead to step 5; that's a different sub-problem. - Find where the value went broad. Follow the variable back to its birth: a prop, a state hook, a constant, a fetch, a URL parameter. One of those spots declared or implied the wide type. Editing the call site where the error appeared treats the symptom; the widening spot is the cause, and other call sites are probably quietly suffering too.
- Type the value where it's born. Give state its type explicitly —
useState<'sm' | 'lg'>('sm')— declare props with the real union instead ofstring, and mark constant listsas constso their members keep their literal types. Do this once at the source and the error disappears everywhere downstream at the same time. - Validate outside strings before use. A URL parameter that should be a size is a claim, not a fact. Check it against the allowed list — a simple
['sm','lg'].includes(raw)with a fallback, or your schema library's enum — and only pass it on once it has earned the narrow type. This is the honest version of casting: the check is the proof TypeScript was asking for. - Narrow the maybes. If the given type is
string | undefinedand a definite string is wanted, decide where "missing" should be handled: a default value at the source, an early return while it's absent, or an explicit check before the call. What TypeScript is really saying is "one path through your code hands over nothing" — and it's usually right. - Assert only as a last, honest resort. After a real validation, a single targeted assertion (
as Size) is fine — it documents "I checked this." Reaching foras anyat the call site instead silences this error by disabling the alarm system, and the invalid value then fails later, at runtime, in front of a user, with no message half as clear as TS2345.
Why this happensOptional explanation
TypeScript checks promises, not snapshots. A parameter typed 'sm' | 'lg' is a promise that only those two values will ever arrive. A variable typed string promises nothing except "some text" — it could legally hold 'banana' tomorrow. So TypeScript rejects the hand-off even when today's value happens to be valid. TS2345 is that rejection, and the two type names in the message are the whole diagnosis: what was wanted, and what you actually promised.
Values lose their narrow type at predictable places in AI-built apps. State is the biggest: useState('sm') widens to string the moment anything calls the setter. Props get declared as string because it's easy. Constants in a plain array are just strings. And anything arriving from outside your code — URL parameters, form fields, local storage, a fetch response — genuinely is an unproven string, whatever it looks like. The error then appears far away, at the one strict function in the chain, which is why it feels like the strict function is the problem. It isn't; it's the messenger.
- Widened state:
useStatewithout an explicit type generalises the value to string. - Lazy props: a component declares
size: stringwhen only two sizes exist. - Outside data: URL params, forms, and JSON are strings until your code proves otherwise.
- The maybe types:
string | undefinedhanded to a function that needs a definite string.
Quick answers
My string holds exactly the right value. Why is it still an error?
Because 'string' means ANY string, and the function only accepts specific ones. TypeScript can't prove your variable holds an allowed value — even if, right now, it does. The variable needs the narrower type, not just a compatible current value.
Can't I just add 'as' and cast it?
Only after you've actually validated the value (checked it against the allowed list). An assertion is you claiming the proof TypeScript asked for; using it on unchecked user input or URL parameters just moves the bug to runtime, where it fails silently instead of at build time.
Why does useState keep giving me 'never' or 'string | undefined'?
Nothing was ever stored, so the state starts as a guess. With no initial value it guesses undefined; with a general value it widens to the general type. Give useState the type explicitly — useState<'sm' | 'lg'>('sm') — and the state keeps the narrow type everywhere it's used.