I have a react HoC which is add two params (function to translate and current locale) into component props. It's works good. But i start rewrite project with TypeScript, and i have no idea how to do that.
My point is very similar as how-to-handle-props-injected-by-hoc-in-react-with-typescript. But i have one more HoC into my HoC.
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl } from 'react-intl';
export function withTranslate(Component) {
function WithTranslate(props) {
const { intl, ...compProps } = props;
const translate = key => {
return intl.formatMessage({
id: key
});
};
return <Component {...compProps} t={translate} locale={intl.locale} />;
}
WithTranslate.displayName = `withTranslate(${Component.displayName ||
Component.name}`;
WithTranslate.propTypes = {
intl: PropTypes.shape({
locale: PropTypes.string.isRequired,
formatMessage: PropTypes.func.isRequired
}).isRequired
};
return injectIntl(WithTranslate);
}
injectIntl from "react-intl" have typings
interface InjectedIntlProps {
intl: InjectedIntl;
}
interface InjectIntlConfig {
intlPropName?: string;
withRef?: boolean;
}
function injectIntl<P>(component: React.ComponentType<P & InjectedIntlProps>, options?: InjectIntlConfig):
React.ComponentClass<Pick<P, Exclude<keyof P, keyof InjectedIntlProps>>> & { WrappedComponent: React.ComponentType<P & InjectedIntlProps> };
I try to do this with
interface WithTranslateProps {
t: (key:string) => string;
locale: string;
}
export function withTranslate<T extends object>(Component:ComponentType<T & WithTranslateProps>):
ComponentType<T & WithTranslateProps> {
function WithTranslate<P>(props:P & InjectedIntlProps) {
const { intl, ...compProps } = props;
const translate = (key:string) => {
return intl.formatMessage({
id: key
});
};
return <Component {...compProps} t={translate} locale={intl.locale} />;
}
WithTranslate.displayName = `withTranslate(${Component.displayName ||
Component.name}`;
return injectIntl(WithTranslate);
}
It's not working.
TS2322: Type '{ t: (key: string) => string; locale: string; }' is not assignable to type 'T'.
TS2322: Type 'ComponentClass, any> & { WrappedComponent: ComponentType; }' is not assignable to type 'ComponentType'.
Type 'ComponentClass, any> & { WrappedComponent: ComponentType; }' is not assignable to type 'ComponentClass'.
Type 'Component, any, any>' is not assignable to type 'Component'.
Types of property 'props' are incompatible.
Type 'Readonly<{ children?: ReactNode; }> & Readonly>' is not assignable to type 'Readonly<{ children?: ReactNode; }> & Readonly'.
Type 'Readonly<{ children?: ReactNode; }> & Readonly>' is not assignable to type 'Readonly'.
Can anyone help me?
Related
Code
App.tsx
function App(){
return(
<>
<Slider products={products} />. // Error
</>
}
export default App;
Slider.tsx
import { useSelector, connect } from "react-redux";
import { ProductType, ProductItem, StateType } from "types";
const Slider = ({ products, number }: any) => {
// const number = useSelector((state: StateType) => state.number);
return (
<S.Slider>
<S.SliderWrapper widthSize={number}>
{products}
...
<S.SliderWrapper>
</S.Slider>
);
};
export default connect((state: any) => {
console.log(state.number);
return { number: state.number };
}, null)(Slider);
type.ts
export interface ProductItem {
id: number;
title: string;
price: number;
bgcolor: string;
colors: { code: string; img: any }[];
}
export interface ProductType {
products: ProductItem[];
}
Currently, Slider Component is using useSelector, but an error occurs when trying to change it to connect. So, how should I change the connect part and how should I modify the parameter and type of the slider component?
Error Message
TS2322: Type '{ products: { id: number; title: string; price: number;
bgcolor: string; colors: { code: string; img: any; }[]; }[]; }' is not
assignable to type 'IntrinsicAttributes & Omit<any, string | number |
symbol>'. Property 'products' does not exist on type
'IntrinsicAttributes & Omit<any, string | number | symbol>'.
Solution
Slider.tsx
const Slider = ({ number, products }: any) => {
...
...
}
export default connect((state: StateType, { products }: ProductType) => {
return { number: state.number, products };
}, null)(Slider);
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)
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
I got a problem and VSCode doesn't find any errors but my browser does. The browser tells me to assign it to a parameter but I don't know if I'm just doing it wrong but after typing (parameter) before the props inside the constructor it throws errors at me. Maybe you know a solution to this problem. Thanks in regards. (Explanation of what I am doing: I want to toggle 2 flexBoxes through buttons and make them visible/invisible.
I'm using react 16.13 and typescript 3.9.7. It worked quite well but after adding the second toggle, the described problem appears)
"Argument of type '{} | Readonly<{ any: "showContent"; showMessages: any; }>' is not assignable to parameter of type '{ showContent: any; showMessages: any; } | Readonly<{ showContent: any; showMessages: any; }>'.
Type '{}' is not assignable to type '{ showContent: any; showMessages: any; } | Readonly<{ showContent: any; showMessages: any; }>'.
Type '{}' is missing the following properties from type 'Readonly<{ showContent: any; showMessages: any; }>': 'showContent', 'showMessages'"
class Explore extends React.Component<{}, IState> {
constructor(props: {} | Readonly<{ any: 'showContent' , 'showMessages' }>) {
super(props);
this.toggleContent = this.toggleContent.bind(this)
this.toggleMessages = this.toggleMessages.bind(this)
this.state = {
showContent: false,
showMessages: false
}
}
toggleContent(event: { preventDefault: () => void}) {
event.preventDefault()
this.setState({
showContent: !this.state.showContent
})
}
toggleMessages(event: { preventDefault: () => void}) {
event.preventDefault()
this.setState({
showMessages: !this.state.showMessages
})
}
render() {
const { showContent } = this.state
const { showMessages } = this.state
I'm trying to build a decorator that injects on decorated components some props from a React Context, I've had many problems defining the type signature for the decorator itself, as the decorator call, implementation and decorated component usage would always give one error.
This is my latest approach:
The decorator:
type ContextType = {
pageContext: {
language: string;
};
};
const PageContext = React.createContext({
pageContext: {
language: 'en-US',
},
// there are more keys than pageContext
});
export function withPageContext<P>(WrappedComponent: React.ComponentType<P>) {
return (props: Omit<P, keyof ContextType>) => (
<PageContext.Consumer>
{(consumerProps: ContextType) => <WrappedComponent {...consumerProps} {...props} />}
</PageContext.Consumer>
);
}
The withPageContext call gives no errors and the decorated components have the right props.
But the decorator implementation gives the following TypeScript error on the <WrappedComponent ... /> call:
Type '{ pageContext: { language: string; }; } & Pick<P, Exclude<keyof P, "pageContext">>' is not assignable to type 'IntrinsicAttributes & P & { children?: ReactNode; }'.
Type '{ pageContext: { language: string; }; } & Pick<P, Exclude<keyof P, "pageContext">>' is not assignable to type 'P'.
'{ pageContext: { language: string; }; } & Pick<P, Exclude<keyof P, "pageContext">>' is assignable to the constraint of type 'P', but 'P' could be instantiated with a different subtype of constraint '{}'.ts(2322)
Which I'm having a hard deciphering.
The error goes away if I change the Omit<P, keyof ContextType> to just P but the decorated components now complain that they are missing the injected props...
So any help with this would be appreciated, maybe my entire idea behind the Omit<P, keyof... is wrong after all
Following is the Form component
import React from 'react';
import { Elements } from '#helpers/ImportProxy';
import { mockApi } from '#constants/api';
type Props = {
// formValues: any
};
type MapKindToComponentT = {
[key: string]: React.SFC
}
/** #component */
export const Form: React.SFC<Props> = __props => {
const renderQuestions = () =>
mockApi.map(
(question, index): React.ReactElement | undefined => {
const mapKindToComponent: MapKindToComponentT = {
radio: Elements.RadioElement,
text: Elements.InputElement,
email: Elements.InputElement,
url: Elements.InputElement,
checkbox: Elements.CheckBoxElement,
dropdown: Elements.SelectElement,
textarea: Elements.TextareaElement,
};
if(mapKindToComponent[question.kind]) {
const Element = mapKindToComponent[question.kind];
return <Element key={index} question={question} />;
}
}
);
return (
<form>
{renderQuestions()}
<div>
<button type="submit">Submit</button>
</div>
</form>
);
};
export default Form;
Value of each key of mapKindToComponent is React functional component.
Following is the error I get for it's currently defined type. Works fine with any.
Type error: Type 'FunctionComponent' is not assignable to type
'FunctionComponent<{}>'. Types of property 'propTypes' are
incompatible.
Type 'WeakValidationMap | undefined' is not assignable to type 'WeakValidationMap<{}> | undefined'.
Type 'WeakValidationMap' is not assignable to type 'WeakValidationMap<{}>'.
Type '{}' is not assignable to type 'Props'. TS2322
Solution
Make it explicit that MapKindToComponentT accepts function components of any kind.
type MapKindToComponentT = {
[key: string]: React.SFC<any>
}
Explanation
The default type parameter (the one that describes Props) for React.SFC defined in #types/react is {}.
type SFC<P = {}> = FunctionComponent<P>;
If a component expects some more precise type as its props, for example { foo: string }:
declare const SomeComponent: React.SFC<{ foo: string }>;
such a component will not be assignable to React.SFC.
const MyComponent: React.SFC = SomeComponent; // ⛔️ Compile-time error
const MyComponent: React.SFC<any> = SomeComponent; // ✅ OK