Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

type check and spread operator (setState)

Using spread operator or Object.assign will lose strict typescript check.

type State = {
  key1: string;
  key2: string;
};

function App() {
  const [state, setState] = React.useState<State>({
    key1: "",
    key2: ""
  });
  React.useEffect(() => {
    setState(prevState => {
      return { ...prevState, ...{ key3: "" } };//no ts error
    });
    setState(prevState => {
      return Object.assign({}, prevState, { key3: "" });//no ts error
    });
    setState({key1:'', key2:'', key3: ''});//ts error!!
  }, []);
  return <div>{JSON.stringify(state)}</div>;
}

I've try with Vanilla TS, still problem

type Person = {
    name: string,
    age: number
}
const person1:Person = {...{name: 'a', age: 20, salary: 20}};//no error
const person2:Person = Object.assign({name: 'a', age: 20, salary: 20});//no error
const person3:Person = {name: 'a', age: 20, salary: 20};//ts error

typescript playground link

It seems there are some bugs with ts. I want to know if there is a simple way to update the state with strict type checking? Could anyone guide me through this?

like image 694
dabuside Avatar asked Mar 04 '23 18:03

dabuside


2 Answers

Yes, this seems to be a known issue from within the Typescript community. Check this discussion on Github out

There is a workaround for this. Reference to it can be found here.

TLDR;

You basically type your new state before merging the two to form a new one. Two correctly typed states will result in a correctly typed state.


Take a look at this snippet:

(() => {
    type State = { foo: string };
    const state: State = { foo: 'bar' };
    const assignedValues: State = { foo: 1 }; //complain expected
    const newState: State = Object.assign({}, state, assignedValues) 
})();

From this, this is what I reckon your new code will look like:

const [state, setState] = React.useState<State>({
    key1: "",
    key2: ""
  });
  React.useEffect(() => {
    setState(prevState => {
      const valueToChange: Partial<State> = { 
        key1: "I am sure that this is typed",
      }

      return Object.assign({}, prevState, valueToChange); 
    });
  }, []);
  return <div>{JSON.stringify(state)}</div>;
like image 137
iamfeek Avatar answered Mar 10 '23 21:03

iamfeek


iamfeek's answer (assign the new state to a variable/constant with the correct type in order to typecheck it, then merge in from that variable/constant) is correct as far as it goes, but you'll probably want to add Partial to your real use case.

setState(prevState => {
  const merge:Partial<State> = { key3: "" } }; // Flags up the error
  return { ...prevState, ...merge }; // Or return Object.assign({}, prevState, merge);
});

Partial<State> allows the merge object to only have a subset of State's properties, but doesn't let it have incorrect properties.

Perhaps even give yourself a utility function for it:

function mergeState(state:State, merge:Partial<State>) {
  return { ...prevState, ...merge };
}

then

setState(prevState => {
  return mergeState(prevState, { key3: "" } ); // Flags up the error
});
like image 41
T.J. Crowder Avatar answered Mar 10 '23 22:03

T.J. Crowder