How to pass a setter hook to a child component and satisfy TypeScript

Viewed 5306

I want to pass a setter hook (setValuesList) from the parent (App.tsx) to the childcomponent (AddNumber.tsx)

I defined the hook in the parent component like this:

const [valuesList, setValuesList] = useState<number[]>([]);

And I want to pass the hook to the childcomponent like this:

<AddNumber valuesList={valuesList} setValuesList={setValuesList} />

However I get a Typescript error:

(JSX attribute) AppProps.setValuesList: (num: number) => void
Type 'Dispatch<SetStateAction<number[]>>' is not assignable to type '(num: number) => void'.
  Types of parameters 'value' and 'num' are incompatible.
    Type 'number' is not assignable to type 'SetStateAction<number[]>'.ts(2322)
AddNumber.tsx(5, 3): The expected type comes from property 'setValuesList' which is declared here on type 'IntrinsicAttributes & AppProps'

In the childcomponent I am defining an interface for the props like this:

interface AppProps {
  valuesList: number[];
  setValuesList: (num: number) => void;
}

Does anyone see what I am doing wrong here?

my repository is at: https://github.com/acandael/number-list

Thanks for your help,

Anthony

3 Answers

This the type definition for useState hook

function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];

So if you pass the type number[] for the state value when calling useState the type for the setter function will be React.Dispatch<SetStateAction<number[]>>

import { SetStateAction } from 'react';

// this interface will satisfy if you want to pass setValuesList directly
interface AppProps {
  valuesList: number[];
  setValuesList: React.Dispatch<SetStateAction<number[]>>;
 
  // setValuesList: (val: number[]) => void  will also satisfy, but it is
  // better to be explict and pass React.Dispatch<SetStateAction<number[]>>
}

if you don't want to modify the interface you can create and pass another function.

// you don't need to modify the interface if you take this approach -
// the interface already satisfies the type required in this case
const updateValue = (num: number) => {
 setValuesList(prev => prev.concat(num))
}

<AddNumber valuesList={valuesList} setValuesList={updateValue} />

The error means you're assigning the wrong thing to the wrong type. setValuesList is callable but is of type Dispatch<SetStateAction<number[]>> which is obviously different from (num: number) => void.

I see two ways of resolving this:

  • change the type of setValueList in AppProps to Dispatch<SetStateAction<number[]>> (there's also the cheaty any type, but I don't recommend it if you can avoid it).
  • You wrap setValuesList in a function (should look like (num: number) => setValuesList(num)), that does what you want and will respect the type system

For the second solution, good canditates could also be useCallback or useMemo, but I'm not familiar enough with them... But looking at the docs might help you out.

The setter function from useState is of type Dispatch<SetStateAction<number[]>>

If you want your interface to work you should pass (num: number) => void function type:

<AddNumber
  valuesList={valuesList}
  setValuesList={(num: number) => {
    setValuesList(num);
  }}
/>
Related