skip to Main Content

Consider the following logic:

There is a multi step form with exactly four steps and a back button. A user can go forward and backwards, but if he is on the first or last step, the back button should go to the previous screen.

Now consider the code made to satisfy that (It was made with React Native, but it can be done similarly independent of the context):

const [step, setStep] = useState<1 | 2 | 3 | 4>(1)
// ...
if (step === 1 || step === 4) {
    // exit the screen
} else {
    setStep(step - 1)
}

The line inside the else block shows Argument of type 'number' is not assignable to parameter of type 'SetStateAction<2 | 1 | 3 | 4>'. ts(2345). However, hovering step inside the else block gives the type 2 | 3, which should be fine to subtract 1, since the minimum value possible is 1.

Is this a bug? Am I missing something? How can I write valid code for this use case?

2

Answers


  1. Whilst you might be providing a number you’re registering a concrete type against step. It’s no different to doing:

    type Foo = 1 | 2 | 3 | 4;
    
    const [step, setStep] = useState<Foo>(1)
    

    Due to this you can’t reduce the value by N as typescript doesn’t see it as a numeric format.

    The only way off the top of my head you’d be able to resolve this is with:

    const [step, setStep] = useState<number>(1)
    
    if (step === 1 || step === 4) {
      // exit the screen
    } else {
      setStep((value) => value - 1)
    }
    
    Login or Signup to reply.
  2. Types don’t do math. step - 1 is type number which is broader than 1 | 2 | 3 | 4.
    One path would be to remove the subtraction:

    const [step, setStep] = useState<1 | 2 | 3 | 4>(1)
    // ...
    if (step === 1 || step === 4) {
        // exit the screen
    } else if (step === 2) {
        setStep(1)
    } else {
        setStep(2)
    }
    

    but that becomes quite easily error prone and is less obvious. In this case I guess I would "help" the compiler by casting

    const [step, setStep] = useState<1 | 2 | 3 | 4>(1)
    // ...
    if (step === 1 || step === 4) {
        // exit the screen
    } else {
        setStep((step - 1) as 1 | 2 | 3 | 4) // even "as 1 | 2" if you like
    }
    

    It’s also worth creating a type alias to avoid duplicating the type (e.g. type Steps = 1 | 2 | 3 | 4).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search