Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

227
Views
Typescript two object union - problem with making typescript think it's the other type

I encountered a really weird problem and I'm not sure if the problem is related to badly written types or it's a weird typescript thing.

So I have this props type:

type SelectAnswersProps = (
  | {
      onChange: Dispatch<SetStateAction<MultiSelectAnswerOptionsType | null>>;
      value: MultiSelectAnswerOptionsType;
      isMultiSelect: true;
    }
  | {
      onChange: Dispatch<SetStateAction<SelectAnswerOptionsType | null>>;
      value: SelectAnswerOptionsType;
      isMultiSelect: false;
    }
) & {
  displayMode?: SelectAnswerOptionsType['displayMode'];
  disabled?: boolean;
};

And then I'm trying to force typescript to read what type is being passed to onChange method.

if (isMultiSelect) {
      onChange({
        answers,
        isHeadless,
      });
    } else {
      onChange({
        answers,
        displayMode,
      });
    }

So the first onChange is correct as it shows its type as onChange: (value: SetStateAction<MultiSelectAnswerOptionsType>) => void but the second one I'd expect to be like in the types specified in props with isMultiSelect being false but instead type of 2nd onChange is as follows: onChange: (value: SetStateAction<MultiSelectAnswerOptionsType> & SetStateAction<SelectAnswerOptionsType>) => void so kinda mix of both even though from props types you would say it's either one or another.

I reckon I've done something silly here. Really appreciate all your help.

Thanks

about 4 years ago · Santiago Trujillo
1 answers
Answer question

0

You have defined the props to be (simplified for readability):

props: { onChange: MultiSelectDispatcher, ... } | { onChange: SelectDispatcher, ... }

That means, the props can only be the one or the other, and Typescript doesn't know which one it is, and infers that from the usage:

if( isMultiSelect ){
  onChange({ answers, isHeadless }); // <-- TS thinks: probably the "MultiSelect" one
} else {
  onChange({ answers, displayMode });  // <-- TS already decided "MultiSelect" earlier
}

So Typescript decides which type the whole props object probably is at some point, and sticks to that decision from there on.

Use a type guard (maybe)

As a solution you might be able to use type guards (maybe not this easy, see the "But ..." below), like:

const guardMultiSelectProps = ( theProps: unknown ): theProps is MultiSelectProps => {
    return theProps.isMultiSelect; 
}

if( guardMultiSelectProps( theProps ) ){
  onChange({ answers, isHeadless }); // <-- TS thiks: probably the "MultiSelect" one
} else {
  onChange({ answers, displayMode });  // <-- TS already decided "MultiSelect" earlier
}

But also Typescript might have inferred the type of the props even earlier, or will "suddenly" do that if you make some changes to your code later.

Define the type of onChange (if possible)

I suggest, if possible, not to define two alternatives for the props object, but instead define only one type for the props, with two alternative onChange handlers. (The React props should stay consistent anyway)

E.g:

props: {
    onChange: MultiSelectDispatcher | SelectDispatcher,
    ...
}

And also use a type guard, like:

// Note: This type guard works, but is not inherently type safe.
const guardMultiSelectOnChange = ( onChange: unknown, multiSelect: boolean ): onChange is MultiSelectDispatcher => {
    return multiSelect;
}

if( guardMultiSelectOnChange( onChange, isMultiSelect )) {
    onChange({ answers, isHeadless });
} else {
    onChange({ answers, displayMode });
}

Still problems ...

My last type guard guardMultiSelectOnChange makes another problem obvious:

We always assumed that the onChange function is the correct one if isMultiSelect is one or the other. But nothing prevents someone to pass isMultiSelect: false and the MultiSelectDispatcher.

It would be better to type guard the function signature itself, instead of just stating onChange is MultiSelectDispatcher if some other property (isMultiSelect) has some value.

But that will be a somewhat larger refactoring, outside the scope of this question, I guess.

about 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!