1

Why does the TypeScript not throw an error when a variable is passed to useState? Is there a way I can still type check when passing a variable?

type BuildingNames = [
  'farm',
  'sawmill',
];

type BuildingType = { [n in BuildingNames[number]]?: number };

interface ITownContext {
  buildings: BuildingType;
  setBuildings: (buildings: BuildingType) => void;
}

interface TownProviderProps {
  children: ReactNode;
}

const defaultValues = {
  resourceBuildings: {
    farm: 1,
    asdfasdf: 5,
  },
};

const TownContext = createContext<ITownContext>({} as ITownContext);

const TownProvider = ({ children }: TownProviderProps) => {
  // no error
  const [buildings, setBuildings] =
    useState<BuildingType>(defaultValues.buildings);
  
  // error occurs
  const [buildingsDirect, setBuildingsDirect] =
    useState<BuildingType>({ asdf: 1 });

  return (
    <TownContext.Provider
      value={{
        buildings,
        setBuildings,
      }}
    >
      {children}
    </TownContext.Provider>
  );
};

export { TownContext, TownProvider };
Hyrial
  • 1,728
  • 3
  • 15
  • 27
  • 1
    `defaultValues` is untyped. It may be okay, it may not, as your code is free to change it without restrictions, so it can't be statically verified. `{ asdf: 1 }` is a constant, so we know its value, and it is obviously not a `BuildingType`. – Amadan Aug 08 '22 at 02:24

1 Answers1

0

The difference you see between the 2 usages of useState is the effect of TypeScript excess property check, which triggers only when doing an assignment of an object literal (i.e. as opposed to assigning the reference of another variable, like defaultValues.buildings).

The fact that excess property check is not enforced when assigning the reference of another variable, enables a classic OOP usage of passing a subtype (here an object with more properties than strictly necessary).

Still, even when excess property check does not kick in, TS still checks for other errors: should one of the properties not match its type, it will be reported.

const defaultValues = {
  buildings: {
    farm: "string instead of number",
    asdfasdf: 5,
  },
};

useState<BuildingType>(defaultValues.buildings) // Types of property 'farm' are incompatible. Type 'string' is not assignable to type 'number'.
//                     ~~~~~~~~~~~~~~~~~~~~~~~

Playground Link

ghybs
  • 47,565
  • 6
  • 74
  • 99
  • Thanks for the answer! So there is no way to check if an object with an unknown property is passed to `useState` with this pattern? Do you know of another way? – Hyrial Aug 08 '22 at 03:28
  • There are some workarounds see e.g. https://stackoverflow.com/questions/54775790/forcing-excess-property-checking-on-variable-passed-to-typescript-function – ghybs Aug 08 '22 at 04:52