Skip to content

Inconsistent excess property checking with $state #17947

@ferdinand-varta

Description

@ferdinand-varta

Describe the problem

Hi Svelte team,

I ran into a small but noticeable difference in TypeScript behavior when using $state that caught me off guard during a refactor.
In our codebase, we renamed an optional property (e.g. from optional to optionalRenamed in the type). I expected TypeScript to flag any leftover usages of the old name — which it does in normal object literals and in some $state patterns, but not in others.

Here's a minimal example:

type Company = {
  required: string;
  optionalRenamed?: string;
};

const company1: Company = $state({
  required: "",
  optional: "" // No TS error — old property is silently accepted
});

const company2: Company = {
  required: "",
  optional: "" // TS errors (good): 'optional' does not exist in type 'Company'.ts(2353)
};

const company3 = $state<Company>({
  required: "",
  optional: "" // TS errors (good): 'optional' does not exist in type 'Company'.ts(2353)
});

In the company1 case TypeScript doesn't catch the extra / obsolete property. Only when using the explicit generic ($state<Company>(…)), or when assigning a plain literal, do I get the helpful excess property error.
This made the rename refactor less safe than I expected since old prop names can linger without warnings.
I understand this is probably just how TypeScript handles fresh object literals vs inferred generics, but from a Svelte user's perspective it creates an inconsistency between two very similar-looking patterns.
Is this difference considered expected / by design? Or is there interest in making the behavior more consistent (e.g. so the inline $state({ … }) pattern also triggers the stricter check when assigned to a typed variable)?

Thanks for any insight and your amazing work with svelte!

Describe the proposed solution

  • add a note in the $state type docs
    explicitly warning about this difference and recommending the generic form when strictness is desired.
  • improve $state or make the generic form $state the strongly encouraged default
  • ?

Importance

would make my life easier

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions