Home > Enterprise >  How to conditionally set State from tow different objects each has its own type without using castin
How to conditionally set State from tow different objects each has its own type without using castin

Time:10-18

I want to make 2 diffrent objects each has slightly different type to be compatible with a state object that has both values of both types, I am getting the following error :

Property 'dataA' does not exist on type 'TypeA | TypeB'. Property 'dataA' does not exist on type 'TypeA'.ts(2339)

,so my state looks like the following

const inicialState: {
  dataA: string;
  dataB: number[];
  groupType: "" | "typeA" | "typeB";
} = {
  dataA: ''
  dataB: [],
  groupType: "";
}
const [state, setState] = useState < typeof inicialState > (inicialState);

type TypeA = {
 dataA : string
 group : 'a1'
};

type TypeB = {
 dataB : number[]
 group : 'b1'
}; 

cosnt data : (TypeA | TypeB)[] = [{dataA : 'dataA string', group : 'a1'} ,{dataB : [1,2,3,4,5] ,'b1' }]

// id in filter is coming as props so the id will decied on the object the will be filterd...
const edited = data.filter((el) => el.id === id)[0] as TypeA | TypeB;
      // Case 1
      if (edited?.mainGroup === 'a1')) {
       setState({
          ...state,
          dataA: edited.dataA,
        });
        return;
      }
      // Case 2 
      if (edited?.mainGroup === 'b1') {
       setState({
         ...state,
         dataB: edited.dataB,
        });
        return;
      }

the problem is in the if block, Typescript don't knows what I am pushing in to the state, how to solve this without the use of casting since the real object is way too big to cast every value as the current type. my partial solution was

if (edited?.mainGroup === 'b1') {
 let casted = edited as TypeB
       setState({
         ...state,
         dataB: casted.dataB,
        });
        return;
      }

Would you consider this a messy solution or can this be made more centralized rather than using casting and let ts know instantly in the if statement what kind of type we are using here since we are making a check ?

CodePudding user response:

You have a couple options here. Your types and unions are a little mixed between the different examples. But to me it seems like you have data of two types, and a separate Initial State in the useState hook. If you're already defining a type/interface for initialState, you might as well break it out into a declared interface type InitialState = {...}; useState<InitialState>();.

Option #1 - Concise Types

If group parameter is there to only define what type of data it is, you could get rid of that and use one single type. This would be my personal preference, as it cuts down on the type checking in a lot places. The only place it would be needed is where you are displaying or accessing the data.

type Type = {
    data: string | number[];
};

You can then use the typeof operator to differentiate between the two

if(typeof data === 'string') {
    // do stuff
}  else {
    // data is known to be of type number[] here 
}

You also can use the Array.isArray() function if you expanded to more than two types.

Option #2 - 'in' type guard

You also have the ability to use the 'in' function, which typescript can use to narrow down or infer the type. This works in your case because TypeA and TypeB have different properties.

if(dataA in data) {
    // data is of TypeA
}
if(dataB in data) {
    // data is of TypeB
}

Option #3 - function type guard

You also have the option to create a function that narrows down the type

function isTypeA (unknownType: TypeA | TypeB): unknownType is TypeA {
    return group === 'a1';
}

function isTypeB (unknownType: TypeA | TypeB): unknownType is TypeB {
    return group === 'b1';
}

See more here: https://www.typescriptlang.org/docs/handbook/advanced-types.html

  • Related