skip to Main Content

If I have this code:

import { useState } from "preact/hooks";
export default function Test() {
  const [state, setState] = useState(null); 
  setState('string');
}

Then I get this error:

Argument of type 'string' is not assignable to parameter of type '(prevState: null) => null'.deno-ts(2345)

I understand that it expects me to use the same value type of the initial type. However if the initial value is an actual literal like number:

-   const [state, setState] = useState(null); 
+   const [state, setState] = useState(1); 

Then the error is:

Argument of type 'string' is not assignable to parameter of type 'number | ((prevState: number) => number)'.deno-ts(2345)

Reading What is prevState in ReactJS? or Hooks – Preact Guide doesn’t help me understand:

  • What does ((prevState: null) => null) | null mean?
  • Why is the first case the required type not null | ((prevState: null) => null)?
  • How can I use multiple type? For example, the initial value is 1 or null, but I then set the value to 'string' afterward?

2

Answers


  1. ((prevState: null) => null) | null means you can call the state setter function with an updater callback (where the previous state is null and the returned value of the callback is also null) or you can pass it null directly.

    Example: setState(null) or setState((prev) => null).

    The order of these two options in the type definition is unimportant.

    To allow more than one type to be held in that state, you need to be explicit in your definition. Without passing a type, the type will be inferred by the initial value. Your initial value is null so the inferred type is null and nothing else.

    To allow a string or null, you can use const [state, setState] = useState<null | string>(null);

    The resulting type definition would be (at least close to) ((prevState: null | string) => null | string) | null | string

    Login or Signup to reply.
  2. Although your question is partly answered, I think your second question is still not answered. So I will still submit this one:

    Answer to the three questions:

    1. React expects either a new state value or a state updater function. So since the type of your state is inferred to be null, TS compiler is simply complaining that it would either expect null (new state value) or ((prevState: null) => null) | null (state updater function). This method should return a new value based on the previous value. Doc

    2. null type in unions is usually merged into other types.

    Look at the below code.

    let x : null| number  = 'string';
    

    The error is Type ‘string’ is not assignable to type ‘number’. and not Type ‘string’ is not assignable to type ‘number | null’.

    enter image description here

    Now if we write something like the below:

    let y : null  = 'string';
    

    The error will be:

    enter image description here

    As you can see there is no omitting of null as there is no union.

    Playground Link for the above

    This is a trivial example of what is happening above. And when the code is changed to:

    const [state, setState] = useState(1); 
    

    The error is as expected and now includes the number part of the union:

    'number | ((prevState: number) => number)'

    1. For multiple types, as mentioned in the comments, just use something like a union and you are good:
    const [val,setVal] = useState<number | null | string | boolean>(null)
    

    Edit:
    If you are keen on getting null in your error, depending on the version of TS involved you can tweak it.

    The below playground is using TS 4.x.x, and I can enable/disable strictNullChecks by clicking on TS Config button. Once you enable strictNullChecks

    Playground

    strictNullChecks does what it says. It starts including null ( and undefined) in your checks (in this case a union)

    Doc

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