Home > Back-end >  TypeScript React setState with spread operator
TypeScript React setState with spread operator

Time:04-12

I have this interface .

interface parts {
    general: boolean,
    source: boolean,
    target: boolean
}

When init state in Parent component and type it with that interface, TypeScript works as expected.

const Parent: React.FC = () => { 
    const [parts, setParts] = useState<parts>({
        general: false,
        source: false,
        target: false,
        wrongKey: "Typescript reports problem"
    })
    
    return (
        <Child setParts={setParts} />
    )
}

But when it pass to child, TypeScript doesn't work as expected.

const Child: React.FC<{setParts: React.Dispatch<React.SetStateAction<parts>> }> =({setParts}) => { 
    setParts((parts: parts) => {
        ...parts,
        wrongKey: "Typescript doesn't report problem"
    })

    return (
        <div></div>
    )
}

With types, like boolean or string, it works as should, but there is some problem with dictionary.

CodePudding user response:

Excess property check only triggered when you try to assign an object literal. For other cases TypeScript only checks if the shape of object matches with the requested type. The reasoning is that it is most likely a developer error when you pass a wrong shaped object inlined. What could be the benefit to allow that? So it is a typo or you want something else.

But if you pass a variable, it only has to check if shape is ok, there can't be really any runtime issue. You can do, for example, the following:

 const [parts, setParts] = useState<parts>({
        general: false,
        source: false,
        target: false,
        wrongKey: "Typescript reports problem"
    } as parts)

That way TypeScript will make sure you pass 'something' which can be shaped as parts. Don't know it tho if it is useful, tho, consider it just as an example:)

I'm not sure if this is really an issue that you are not aware about passing an extraneous property. I would like to stress if you forgot to pass a property to satisfy the shape of the required type, TypeScript will tells you that, and that is what important.

If you really want to deny extra properties, please check this answer: Is it possible to restrict TypeScript object to contain only properties defined by its class?

UPDATE 1: Why excess property check is not triggered, as actually we return with an object literal and I said excess property check run in those case, this is kinda a contradiction, but not, actually.

Consider this example:

type Parts = {
general: boolean;
source: boolean;
target: boolean;
}

type FunctionType = (parts: Parts) => Parts;

function ShowsError(parts: Parts): Parts {
 return {
   ...parts,
   someExtraPropertyWhichTriggerError:'Ooops'
 }
}

const myArrowFunction = (parts: Parts) => ({
  ...parts,
  someExtraPropertyWithoutError:'Why no report?:('
});


const DoesntShowError: FunctionType = myArrowFunction;

So what is happening there? Looks like two identical function, one is arrow, one is normal, why there is no error in case of arrow function? Same arguments, same return type, same object returned.

The key difference is, when we define our arrow function its return statement NOT contextually binded to Parts. TypeScript can't know where it is going to be used. So its generate a shape for its return type, and go on. Then we assign it to DoesntShowError which requires us assign a function with a given type. And the arrowfunction satisfy that requirement. Thats all, I think, hope it helps.

CodePudding user response:

You will get an error if you do

setParts({...parts, wrongKey: "Typescript report problem"})

OR

setParts((parts: parts) => ({
        ...parts,
        wrongKey: "Typescript report problem"
    }))

in your child component.

What you are doing in the case is using an arrow function with braces in the setParts and that too without the use of the return keyword, that means your arrow function will not return anything and hence no error produced..

As per the React Docs we can have two ways for setState one with object syntax and other with function which they have shown as below

this.setState((prevState, props) => ({
  counter: prevState.counter   props.increment
}));

My understanding of arrow function syntax is like () => {} where flower brackets are followed after arrow =>, but as per the sample it is round braces instead of flower brackets.

Look at setState function syntax to understand the difference.

  • Related