Reputation: 1992
I was looking at this question, which I thought was related to my issue. However, it is a bit different from my use case.
I have a function called parseScanResults
takes an argument that is an object. The object can be one of two types. However, typescript is throwing an error with the below code:
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 => {
// Do something with 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 IBlinkCardScanResults {
cardNumber: string;
cvv: string;
expiryDate: {
day?: number;
empty?: boolean;
month: number;
originalString?: string;
successfullyParsed?: boolean;
year: number;
};
}
export interface IVoucherScanResults {
text: string;
timestamp: number;
format: number;
numBits: number;
}
export interface IParsedScanResults {
cardString: string;
stringMonth: string;
stringYear: string;
}
export interface IScannerProps {
scannerActive: boolean;
closeScanner: () => void;
handleScanResults: (results: IVoucherScanResults | IBlinkCardScanResults) => void;
}
export interface IScanFormProps {
children: (props: ICardScannerProps) => React.ReactElement;
onSubmit: (data: { expMonth: string; expYear: string; securityCode: string; cardNumber: string }) => void;
parseScanResults: (results: IBlinkCardScanResults | IVoucherScanResults) => IParsedScanResults;
}
The error states:
Type '(results: IVoucherScanResults) => IParsedScanResults' is not assignable to type '(results: IBlinkCardScanResults | IVoucherScanResults) => IParsedScanResults'.
Types of parameters 'results' and 'results' are incompatible.
Type 'IBlinkCardScanResults | IVoucherScanResults' is not assignable to type 'IVoucherScanResults'.
Type 'IBlinkCardScanResults' is missing the following properties from type 'IVoucherScanResults': text, timestamp, format, numBitsts(2322)
Upvotes: 2
Views: 2070
Reputation: 844
Your problem is that parseScanUtils
is a either a function that gets an IVoucherScanResults
as a parameter, or a function that gets an IBlinkCardScanResults
as a parameter, while only one is true. in this case it looks like your component is receiving the first of the two.
the main point is that there is a difference between having a union of functions where each one gets a specific parameter type and having one function whose parameter is a union of two types.
parseScanResults:
((results: IBlinkCardScanResults) => IParsedScanResults)
| ((results: IVoucherScanResults) => IParsedScanResults);
vs.
parseScanResults:
((results: IBlinkCardScanResults | IVoucherScanResults) => IParsedScanResults)
EDIT
what you can do is use a generic and instead of typing your component function you can explicitly type your parameter:
let's first 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;
}
than you can update your functional component like this:
const ScanForm = <T extends IBlinkCardScanResults | IVoucherScanResults>({ children, onSubmit, parseScanResults }: T) => {
and your handleScanResults
function:
const handleScanResults = (results: T): void => {
...rest of code...
}
then all that's left to do is call the component with the wanted type (example for IBlinkCardScanResults
):
<ScanForm<IBlinkCardScanResults> onSubmit={onSubmit} parseScanResults={parseScanResults}>
I believe it should work now
Upvotes: 2