Typescript react-redux connect type question - javascript

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);

Related

Typescript and ReactJS: previous state how to make it work

I'm having trouble making the previous state on React 18 working with Typescript version 4.8.3.
This is my refreshToken code and I get the error :
Value of type '(prev: any) => any' has no properties in common with
type 'IUserData'. Did you mean to call it?ts(2560)
on (prev: any), So I tried adding prev in my 'IUserData' (useAuth() is related to IUserData), but it doesn't work...
const useRefreshToken = () => {
const { setAuth } = useAuth();
const refresh = async () => {
const response = await axios.get('/refresh', {
withCredentials: true
});
setAuth((prev: any) => {
return {
...prev,
role: response.data.idRole,
accessToken: response.data.accessToken }
});
return response.data.accessToken;
}
return refresh;
};
export default useRefreshToken;
My IUserData interface:
export interface IUserData {
id?: number
email?: string
role?: number
accessToken?: string
auth?: {id?: number
email?: string
role?: number
accessToken?: string
}
setAuth?: Dispatch<SetStateAction<IUserData>>
setPersist?: Dispatch<SetStateAction<boolean>> | Dispatch<any>
}
When I try with (prev: IUserData) I get the error :
Value of type '(prev: IUserData) => { accessToken: any; id?: number | undefined; email?: string | undefined; role?: number | undefined; auth?: { id?: number | undefined; email?: string | undefined; role?: number | undefined; accessToken?: string | undefined; } | undefined; setAuth?: Dispatch<...> | undefined; setPersist?: Dispatch...' has no properties in common with type 'IUserData'. Did you mean to call it?ts(2560)
AuthProvider.tsx :
const AuthContext = createContext<AuthContextType>({});
interface props {
children: JSX.Element | JSX.Element[]
}
export const AuthProvider = ({ children }: props) => {
const [auth, setAuth] = useState<IUserData>({});
const [persist, setPersist] = useState(JSON.parse(localStorage.getItem("persist")!) || false);
return (
<AuthContext.Provider value={{ auth, setAuth, persist, setPersist }}>
{children}
</AuthContext.Provider>
)
}
export default AuthContext;
Do you have any suggestions?
Change the type in setAuth inside of IUserData
For example:
type AuthData = {
id?: number
email?: string
role?: number
accessToken?: string
}
export interface IUserData {
id?: number
email?: string
role?: number
accessToken?: string
auth?: AuthData,
setAuth?: Dispatch<SetStateAction<AuthData>>
setPersist?: Dispatch<SetStateAction<boolean>> | Dispatch<any>
}
and remove the "any" type from your prevState
...
setAuth((prev) => {
return {
...prev,
role: response.data.idRole,
accessToken: response.data.accessToken
}
});
...
You also need to change the type of your useState inside your AuthProvider, so change the IUserData for AuthData like below.
...
const [auth, setAuth] = useState<AuthData>({});
...

Type '{}' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes & Omit

import React from "react";
import { connect } from "react-redux";
import {
getUsersThunkCrstrong texteator, onPageChangedCreator, unfollowCreator, followCreator
} from "../../redux/users-reducer";
import togglefollowingProgress from "../../redux/users-reducer";
import Users from './Users';
import Preloader from "../common/Preloader/Preloader";
import { withAuthRedirect } from '../../hoc/withAuthRedirect';
import { compose } from "redux";
import { getUsers, getPageSize, getTotalUsersCount, getCurrentPage, getIsFetching, getFollowingInProgress } from "../../redux/users-selectors";
import { userType } from '../../types/types'
import { AppStateType } from '../../redux/redux-store'
type MapStatePropsType = {
currentPage: number,
pageSize: number,
isFetching: boolean,
totalUsersCount: number,
users: Array<userType>,
isAuth?: boolean,
followingInProgress: Array<number>,
}
type MapDispatchPropsType = {
togglefollowingProgress: () => void,
unfollowCreator: (userId: number) => void,
followCreator: (userId: number) => void,
}
type DidMountPropsType = {
getRequestUsers: (currentPage: number, pageSize: number) => void,
onPageChanged: (currentPage: number, pageSize: number) => void,
}
class UsersContainer extends React.Component<MapStatePropsType & MapDispatchPropsType & DidMountPropsType> {
componentDidMount() {
this.props.getRequestUsers(this.props.currentPage, this.props.pageSize);
this.props.onPageChanged(this.props.currentPage, this.props.pageSize);
}
render() {
return (
<div>
{this.props.isFetching ? <Preloader /> : null}
<Users
pageSize={this.props.pageSize}
onPageChanged={this.props.onPageChanged}
totalUsersCount={this.props.totalUsersCount}
currentPage={this.props.currentPage}
users={this.props.users}
togglefollowingProgress={this.props.togglefollowingProgress}
followingInProgress={this.props.followingInProgress}
unfollow={this.props.unfollowCreator}
follow={this.props.followCreator}
isAuth={this.props.isAuth}
/>
</div>
)
}
}
let mapStateToProps = (state: AppStateType): MapStatePropsType => {
return {
users: getUsers(state),
pageSize: getPageSize(state),
totalUsersCount: getTotalUsersCount(state),
currentPage: getCurrentPage(state),
isFetching: getIsFetching(state),
followingInProgress: getFollowingInProgress(state),
}
}
export default compose(
connect(mapStateToProps, {
togglefollowingProgress, getRequestUsers: getUsersThunkCreator,
onPageChanged: onPageChangedCreator, unfollowCreator, followCreator,
}),
withAuthRedirect
)(UsersContainer)
The error crashes, I can't solve it, please help.As I understand it, the error reports that there are no such properties, but they are in mapStateToProps, in the dispatches object, in the connect function { togglefollowingProgress, getRequestUsers: getUsersThunkCreator, onPageChanged: onPageChangedCreator, unfollowCreator, followCreator, }.
Error: Enter "{ page size: quantity; onPageChanged: (current page: quantity, page size: number) => unacceptable; Total number of users: number; current page: number; users: its user type[]; togglefollowingProgress: () => unacceptable; followingInProgress: [number]; unsubscribe: (user ID: number) => unacceptable; follow: (user id: number) => unacceptable; isAuth: boolean value | undefined; }"cannot be assigned to the type "IntrinsicAttributes & omit<ClassAttributes & PropsType, "isAuth"> & ConnectProps". Property "Page Size" Property "Internal attributes and Omit <Class attributes and type of details, "is Auth"> & ConnectProps".

Typescript dynamically infer type from object

I have a JS Object with React components, indexed by ID.
const MODAL_ENTITIES = {
changeEmail: ChangeEmailModal,
changeUsername: ChangeUsernameModal,
};
I would like to have a ModalEntity type which results in this:
type ModalEntity = {
id: 'changeEmail',
props: React.ComponentProps<typeof ChangeEmailModal>
} | {
id: 'changeUsername',
props: React.ComponentProps<typeof ChangeUsernameModal>
};
My problem is, I want the type to be dynamically generated from the MODAL_ENTITIES object, since I want the process of adding a modal to be as effortlessly as possible.
Is there a way to define this type dynamically? I could do this but I want to avoid generics, I would like T to be inferred:
export type ModalEntity<T extends keyof typeof MODAL_ENTITIES> = {
id: T;
props: React.ComponentProps<typeof MODAL_ENTITIES[T]>;
};
I made a mockup. The idea is to get generic T out of your ModalEntity type so that it can be used easily when you add a new modal.
Placeholders for your modals, assuming that each modal has different props:
import React from 'react';
const ChangeEmailModal: React.FC<{ id: string; name: string; email: string }> = ({ id, ...props }) => {
return (
<div id={id}>
{props.name} {props.email}
</div>
);
};
const ChangeUsernameModal: React.FC<{ id: string; otherName: string; username: string }> = ({ id, ...props }) => {
return (
<div id={id}>
{props.otherName} {props.username}
</div>
);
};
const MODAL_ENTITIES = {
changeEmail: ChangeEmailModal,
changeUsername: ChangeUsernameModal
};
Then we get the keys from your MODAL_ENTITIES in a dynamic way:
export type ModalEntities = typeof MODAL_ENTITIES;
// this gets all the keys in type ModalEntities
type StringKeys<T> = {
[k in keyof T]: k;
}[keyof T];
type ModalEntitiesKeys = StringKeys<ModalEntities>;
Finally:
export type ModalEntity = {
[K in ModalEntitiesKeys]: {
id: K;
props: React.ComponentProps<typeof MODAL_ENTITIES[K]>;
};
}[ModalEntitiesKeys];
The ModalEntity type will look like this and it's no longer generic. the type of props fields will be inferred dynamically as you requested regardless of different modal props.
type ModalEntity = {
id: "changeEmail";
props: {
id: string;
name: string;
email: string;
} & {
children?: React.ReactNode;
};
} | {
id: "changeUsername";
props: {
id: string;
otherName: string;
username: string;
} & {
children?: React.ReactNode;
};
}
You can elaborate more on this idea.

Typescript: function parameter that is one of two interfaces

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

React HoC inject props with typescript

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?

Categories