Using Typescript: Defining a function parameter that can be either of two interfaces

While browsing through this specific question, I noticed that it was somewhat related to my current issue, although there were notable differences.

In my scenario, I have a function named parseScanResults which accepts an object as its argument. This object can fall into one of two possible types. However, when using the code snippet below, TypeScript throws an error:

const ScanForm: React.FC<IScanFormProps> = ({ children, onSubmit, parseScanResults }) => {
  const [scannerActive, toggleScannerActive] = useState(false);

  const closeScanner = (): void => {
    toggleScannerActive(false);
  };

  const handleScanResults = (results: IVoucherScanResults | IBlinkCardScanResults): void => {
    const { cardString, stringMonth, stringYear } = parseScanResults(results);

    setValue('cardNumber', cardString);
    setValue('expMonth', stringMonth);
    setValue('expYear', stringYear);

    toggleScannerActive(false);
  };

  return (
    <Form onSubmit={handleSubmit(onSubmit)}>
      {children({ scannerActive, closeScanner, handleScanResults })}
    </Form>
  );
};

import CreditCardBarcodeScanner from 'src/components/scanners/credit_card_barcode_scanner';

import { IVoucherScanResults, IScannerProps, IParsedScanResults } from '../scanners/card_scanners';
import ScanForm from './scan-form';

function CreditCardBarcodeForm(): JSX.Element {
  const onSubmit = (data: { expMonth: string; expYear: string; securityCode: string; cardNumber: string }): void => {
    // Handle form data
    console.log(data);
  };

  const parseScanResults = (results: IVoucherScanResults): IParsedScanResults => {
    const { text } = results;

    const [cardString, expirationString] = text.slice().split('/');
    const stringMonth = expirationString.slice(0, 2);
    const stringYear = expirationString.slice(2, 4);

    return { cardString, stringMonth, stringYear };
  };

  return (
    <ScanForm onSubmit={onSubmit} parseScanResults={parseScanResults}>
      {({ scannerActive, closeScanner, handleScanResults }: IScannerProps) => (
        <CreditCardBarcodeScanner
          scannerActive={scannerActive}
          closeScanner={closeScanner}
          handleScanResults={handleScanResults}
        />
      )}
    </ScanForm>
  );
}

export default CreditCardBarcodeForm;

Export interface for IBlinkCardScanResults {
  cardNumber: string;
  cvv: string;
  expiryDate: {
    day?: number;
    empty?: boolean;
    month: number;
    originalString?: string;
    successfullyParsed?: boolean;
    year: number;
  };
}

export interface for IVoucherScanResults {
  text: string;
  timestamp: number;
  format: number;
  numBits: number;
}

export interface for IParsedScanResults {
  cardString: string;
  stringMonth: string;
  stringYear: string;
}

export interface for IScannerProps {
  scannerActive: boolean;
  closeScanner: () => void;
  handleScanResults: (results: IVoucherScanResults | IBlinkCardScanResults) => void;
}

export interface for IScanFormProps {
  children: (props: ICardScannerProps) => React.ReactElement;
  onSubmit: (data: { expMonth: string; expYear: string; securityCode: string; cardNumber: string }) => void;
  parseScanResults: (results: IBlinkCardScanResults | IVoucherScanResults) => IParsedScanResults;
}

https://i.sstatic.net/KbWvZ.png

The error message reads as follows:

Type '(results: IVoucherScanResults) => IParsedScanResults' is not assignable to type '(results: IBlinkCardScanResults | IVoucherScanResults) => IParsedScanResults'.
  The parameters 'results' and 'results' are incompatible.
    Type 'IBlinkCardScanResults | IVoucherScanResults' cannot be assigned to type 'IVoucherScanResults'.
      The type 'IBlinkCardScanResults' is missing the following properties from type 'IVoucherScanResults': text, timestamp, format, numBitsts(2322)

Answer №1

The issue at hand is that the function parseScanUtils can either accept an IVoucherScanResults or an IBlinkCardScanResults as a parameter, but not both at the same time. It seems like in your case, your component is only receiving one of the two.

The key difference lies in whether you are defining separate functions for each input type or a single function that accepts a union type of both.

  parseScanResults:
    ((results: IBlinkCardScanResults) => IParsedScanResults)
    | ((results: IVoucherScanResults) => IParsedScanResults);

vs.

parseScanResults:
    ((results: IBlinkCardScanResults | IVoucherScanResults) => IParsedScanResults)

EDIT

To address this issue, you can use generics to explicitly specify the parameter type for your component function:

Firstly, make the interface generic:

export interface IScanFormProps<T extends IBlinkCardScanResults | IVoucherScanResults> {
  children: (props: ICardScannerProps) => React.ReactElement;
  onSubmit: (data: { expMonth: string; expYear: string; securityCode: string; cardNumber: string }) => void;
  parseScanResults: (results: T) => IParsedScanResults;
}

Then, update your functional component accordingly:

const ScanForm = <T extends IBlinkCardScanResults | IVoucherScanResults>({ children, onSubmit, parseScanResults }: T) => {

And modify your handleScanResults function as well:

const handleScanResults = (results: T): void => {
  ...rest of code...
}

Finally, when calling the component, specify the desired type (e.g., IBlinkCardScanResults):

<ScanForm<IBlinkCardScanResults> onSubmit={onSubmit} parseScanResults={parseScanResults}>

This approach should resolve the issue.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Hold off on running the code until the image from the AJAX request is fully loaded and

Currently, I am facing a challenge with my code that aims to determine the width and height of a div only after it has been loaded with an image from an AJAX request. The dimensions of the div remain 0x0 until the image is successfully placed inside it, c ...

Exploring Attachments in ASP.NET Core MVC Views

I have placed attachments in a Shared Folder on a server. I attempted to access them using the <a> tag <a href="file:///C:">Open Attachments</a> Unfortunately, this method did not work in ASP.NET Core MVC for unknown reasons. I ...

Is there a way to efficiently execute an API function for every element within an array in a sequential manner?

I am currently facing a challenging problem while working with Angular and RxJs. I have an array containing several IDs: ids = [1,2,3,4] There is an API that can be called with a specific ID parameter to delete the corresponding item from the database: th ...

Bidirectional data binding in Vue.js enables seamless communication between parent and child components, allowing for dynamic

Trying to implement v-for and v-model for two-way data binding in input forms. Looking to generate child components dynamically, but the parent's data object is not updating as expected. Here's how my template is structured: <div class="cont ...

Retrieving input values with JQuery

HTML <td data-title="Quantity"> <div class="clearfix quantity r_corners d_inline_middle f_size_medium color_dark m_bottom_10"> <button class="btn-minus bg_tr d_block f_left" data-item-price="8000.0" data-direction= ...

Invoke a React component within a conditional statement

There is a function for exporting data in either csv or xls format based on an argument specifying the type. The functionality works flawlessly for xls but encounters issues with csv. Here's the code snippet: const exportFile = (exportType) => { ...

The custom directive containing ng-switch isn't being reevaluated when the value is updated

I have developed a unique directive that acts as a reusable form. The form includes an error message display section which utilizes ng-switch: <div ng-switch="status"> <span ng-switch-when="Error" class="text-error">An Error occurred</spa ...

Classes in Typescript can implement interfaces to streamline the functionality

Recently, I've been working on creating my custom class called AxiosError and I can't help but feel like the code is a bit repetitive. //types.ts export interface IAxiosRequest{} export interface IAxiosResponse{} export interface IAxios ...

After removing the timezone from the timestamp log, why is it now showing as one day behind?

Within my programming, I store a timestamp in the variable 'var timeStamp = finalData.obj.followers[0].timestp;', which currently outputs '2020-04-15T00:00:00.000Z.' To extract only the date and remove the time zone information, I util ...

Steps for integrating custom slot properties in MUI data grids

Custom pagination has been successfully implemented using the mui datagrid component. However, when attempting to pass props for pagination using datagrid's slotProps, an issue arises stating that the type of onChange does not match. How can this be c ...

Having trouble seeing the output on the webpage after entering the information

I can't seem to figure out why the result is not displaying on the HTML page, so for now I have it set up as an alert. <h1>Factorial Problem</h1> <form name="frm1"> Enter any number :<input type="text" name="fact1"& ...

What is the best way to retrieve the chosen option when clicking or changing using jQuery?

CSS <form name='category_filter' action='/jobseek/search_jobs/' method='get'> <select id="id_category" class="" name="category"> <option value="" selected="selected">All</option> <option v ...

Validation of nested phone numbers using jQuery

I'm trying to figure out how to incorporate the jQuery validation plug-in into a multi-step form where validation occurs on each step as the user progresses. I know that typically, the validation plug-in is triggered by a submit request, but in this c ...

Can you explain the distinction between needing ts-node and ts-node/register?

Currently, I am conducting end-to-end tests for an Angular application using Protractor and TypeScript. As I was setting up the environment, I came across the requirement to include: require("ts-node/register") Given my limited experience with Node.js, I ...

Retrieve all attributes of an element with the help of jQuery

I am attempting to extract all the attributes of an element and display their names and values. For instance, an img tag may have multiple unknown attributes that I need to access. My initial idea is as follows: $(this).attr().each(function(index, element ...

What could be causing the npm server error in my Vue.js application?

After recently setting up Node.js and Vue.js, I decided to dive into my first project on Vue titled test. As part of the process, I attempted to configure the server using the command: npm run server However, I encountered the following error message: C ...

The delete_node() function in jstree does not seem to be removing nodes

In my attempt to create a custom context menu for different nodes, I've managed to display different labels for clicks on folders or files. However, I am facing some challenges when trying to delete them. Take a look at my code snippet. Due to diffic ...

Making a REST call with values containing an apostrophe

Currently, I am utilizing REST and ajax to retrieve data from SharePoint using the URL below: https:xxxxxxxx/_vti_bin/ListData.svc/RMSD_Tasks?$orderby=IssueValue asc,StatusValue desc&$filter="+dropValue+" eq '"+secondFilterVal+"'&groupby ...

Trigger an event in jQuery when the focus moves away from a set of controls

Within a div, I have three textboxes and am looking for a way to trigger an event when focus leaves one of these inputs without transitioning to another one of the three. If the user is editing any of the three controls, the event should not be triggered. ...

Learn how to display data from the console onto an HTML page using Angular 2

I am working on a page with 2 tabs. One tab is for displaying active messages and the other one is for closed messages. If the data active value is true, the active messages section in HTML should be populated accordingly. If the data active is false, th ...