skip to Main Content

The following TypeScript type:

type Errors = ({
  err1: false;
} | {
  err1: true;
  msg1: string;
}) & ({
  err2: false;
} | {
  err2: true;
  msg2: string;
})

Which is basically a intersection of two discriminated unions, works as expected in plain TypeScript:

const customError: Errors = {
  err1: false,
  err2: false,
  attributeThatShouldntBeAllowed: 1 // It gives an IDE error
}

However, when it comes to React, particularly with useState hook:

const [errors, setErrors] = useState<Errors>({
  err1: false,
  err2: false
}) // proper initialization with Errors type, nothing wrong here

It becomes not a little bit strict about the type:

setErrors(prevState => ({
  ...prevState,
  attributeThatShouldntBeAllowed: "lol",
  anotherAttribute: 1
})) // Doesn't produce any errors

What exactly happens here?

As described, I had this issue in my current project and I’ve just created a basic sandbox. This is it’s tsconfig.json:

{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

Also, here it is the tsconfig.json of my current project:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "jsx": "react",
    "allowSyntheticDefaultImports": true,
    "baseUrl": ".",
    "paths": {
      "@domain/*": ["./src/domain/*"],
      "@infra/*": ["./src/infra/*"],
      "@types/*": ["./src/types/*"],
      "@controllers/*": ["./src/controllers/*"]
    }
  },
  "include": ["src", "node_modules"],
}

2

Answers


  1. Chosen as BEST ANSWER

    Apparently, it is a normal behaviour of the structural type in TypeScript, that, considering a generic object type T, an object that has every attribute of T and any aditional attributes is assignable to T. If I explicitly determine the return function of setState, like this:

    setErrors((state): Errors => ({
      ...state,
      attributeThatShouldntBeAllowed: "lol", // Now it produces an error
      anotherAttribute: 1
    }))
    

    it works as expected.

    TypeScript type compatibility

    Don't widen return types of function expressions #241

    Typescript: why not strict return type?


  2. You may simplify your type. Try this:

    type Errors = {
      err1?: boolean;
      err2?: boolean 
      msg1?: string;
      msg2?: string 
    }
    

    As per your setState:

    setErrors(prevState => ({
      ...prevState,
      youNeverUpdatedErrors,
      attributeThatShouldntBeAllowed: "lol",
      anotherAttribute: 1
    }))
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search