TS: custom onSubmit do not match React Hook Form types - reactjs

I'm geting type's problems when I try to use SubmitHandler type to typping onSubmit prop, I'm getting:
TS2345: Argument of type 'SubmitHandler<T>' is not assignable to parameter of type 'SubmitHandler<FieldValues>'. Type 'FieldValues' is not assignable to type 'T'. 'T' could be instantiated with an arbitrary type which could be unrelated to 'FieldValues'.
Implementation
import { FieldValues, useFormContext, SubmitHandler } from 'react-hook-form';
import { Pressable, Text } from 'react-native';
type ButtonProps<T extends FieldValues> = PressableProps & {
onSubmit: SubmitHandler<T>;
};
function SubmitButton<T = FieldValues>({ onSubmit, ...RestButtonProps }: ButtonProps<T>) {
const { handleSubmit } = useFormContext();
return (
<Pressable {...RestButtonProps} onPress={() => handleSubmit(onSubmit)}>
<Text>Submit</Text>
</Pressable>
);
}
type LoginFormValues = {
email: string;
password: string;
};
function LoginForm() {
const handleSubmit: SubmitHandler<LoginFormValues> = ({ email, password }) =>
console.warn(email, password);
return (
<SubmitButton<LoginFormValues> title="Sign In" onSubmit={handleSubmit} />
);
}

Related

react hook with generic interface

i'm trying to write a hook to be used by two of my components-datepicker and dateRangePicker.
Two components extend antd datepicker and rangerpicker, and have the same way of handling states. DatePicker is for selecting a single date and Rangepicker for range of date.
I am trying to extract common logic out of these components and make a custom hook.
Here's my DateRangePicker. Date Picker Looks almost the same.
import { useRef } from "react";
import { DateRangePickerProps } from "./types";
import * as S from "./styles";
import { ReactComponent as CalenderIcon } from "#assets/images/calender.svg";
import Footer from "./Footer";
import { useDatePicker } from "./hooks";
function DateRangePicker({
isError = false,
withFooter = false,
showNextPage = false,
format = "DD/MM/YYYY",
onChange,
defaultValue,
placeholder = ["From", "To"],
...props
}: DateRangePickerProps) {
const wrapperRef = useRef(null);
const {
confirmedValue,
isOpen,
handleOpen,
handleKeyDown,
handleChange,
handleConfirm,
handleDismiss
} = useDatePicker<DateRangePickerProps>(
defaultValue,
onChange,
withFooter,
wrapperRef
); // calling custom hook
return (
<S.Wrapper ref={wrapperRef}>
<S.RangePicker
{...props}
value={props.value || confirmedValue?.date}
open={isOpen}
onOpenChange={handleOpen}
onKeyDown={handleKeyDown}
onChange={handleChange}
isError={isError}
separator={<S.Separator>-</S.Separator>}
suffixIcon={<CalenderIcon />}
format={format}
getPopupContainer={triggerNode => triggerNode}
withFooter={withFooter}
renderExtraFooter={() =>
withFooter && (
<Footer onConfirm={handleConfirm} onDismiss={handleDismiss} />
)
}
showNextPage={showNextPage}
placeholder={placeholder}
/>
</S.Wrapper>
);
}
export default DateRangePicker;
And here's my Custom hook.
I've used Generic parameter which could either be 'DateRangePickerProps' or 'DatePickerProps'
import { useState, useEffect, useRef, RefObject } from "react";
import { DateRangePickerProps, DatePickerProps } from "./types";
type Event = MouseEvent | TouchEvent;
interface SelectedDate<T extends DateRangePickerProps | DatePickerProps> {
date: T['value'] | null
dateString: T["dateString"] | null;
}
export function useDatePicker<T extends DateRangePickerProps | DatePickerProps>(
defaultValue: T['defaultValue'],
onChange: T['onChange'],
withFooter: boolean,
wrapperRef: RefObject<HTMLElement>
) {
const [isOpen, setIsOpen] = useState<boolean>(false);
// confirmedRange, pendingRange 타입 일치 시키기
const [confirmedValue, setConfirmedValue] = useState<SelectedDate<T>>({
date: defaultValue ?? null,
dateString: null
});
const [pendingValue, setPendingValue] = useState<SelectedDate<T>>({
date: null,
dateString: null
});
const sync = useRef(true);
useEffect(() => {
// invoke user-passed onChange
if (confirmedValue.dateString !== null && confirmedValue && !sync.current) {
onChange?.(confirmedValue.date, confirmedValue.dateString);
sync.current = true;
}
}, [confirmedValue, onChange]);
useEffect(() => {
// prevent previous pendingValue from taking over
setPendingValue({ date: null, dateString: null });
}, [isOpen]);
useOnClickOutside(wrapperRef, () => {
if (withFooter) setIsOpen(false);
});
const handleChange: T['onChange'] = (date, dateString) => {
// I get error here; Parameter 'date' implicitly has an 'any' type.ts(7006)
if (withFooter) setPendingValue({ date, dateString });
else {
sync.current = false;
setConfirmedValue({ date, dateString });
}
};
const handleOpen = (internalOpenState: boolean) => {
if (withFooter && !internalOpenState) return;
setIsOpen(internalOpenState);
};
const handleConfirm = () => {
// prevent null(incomplete date range) from taking over
if (pendingValue) {
setConfirmedValue(pendingValue);
sync.current = false;
}
setIsOpen(false);
};
const handleDismiss = () => {
setIsOpen(false);
};
const handleKeyDown: T["onKeyDown"] = key => {
if (key.code === "Escape") {
setIsOpen(false);
}
};
return {
confirmedValue,
isOpen,
handleOpen,
handleKeyDown,
handleChange,
handleConfirm,
handleDismiss
};
}
DateRangePickerProps and DatePickerProps share common properties but with slight difference. For example, 'onChange' is gets parameters of (moment, string) in datePicker, whereas ([moment, moment], [string, string]) is valid in rangePicker.
I'm using following types
import type { DatePickerProps as AntDatePickerProps } from "antd";
import { RangePickerProps as AntRangePickerProps } from "antd/lib/date-picker";
export interface CustomProps {
isError?: boolean;
withFooter?: boolean;
showNextPage?: boolean;
}
export type DatePickerProps = AntDatePickerProps &
CustomProps & { dateString: string };
export type DateRangePickerProps = AntRangePickerProps &
CustomProps & { dateString: [string, string] };
export interface ButtonProps {
size?: "small" | "medium" | "large";
$type?: "primary" | "secondary";
children?: React.ReactNode;
}
export interface FooterProps {
onDismiss: () => void;
onConfirm: () => void;
}
It seems like my approach of generic interface isn't well understood by typescript.
My understanding is that typescript can't figure out which of interface will be passed.
I've left another question regarding this and got an answer, but couldn't implement it.
Generic Function and index access
Can anyone come up with better and error-free approach of dealing with this?
Thanks in advance.

How to create conditional types in typescript?

So I have a Form component in react which takes props to handle various different situations. As I am using it for an online store it will mostly be sending input data to the server, and it needs to work with zod and infered tRPC typings. and I made this generic type, which I thought would mean I can pass either login data or signup data and it would work either way.
export type FormType<T extends SignUpForm | LoginForm> = T extends SignUpForm ? SignUpForm : LoginForm;
Instead I get this error in the form component
value={form[types]} // form refers to state object
// const form: SignUpForm | LoginForm Element implicitly has an 'any' type
// because expression of type 'string' can't be used to index type 'SignUpForm | LoginForm'
and this error in the parent element
onResponse={handleRequest} // onResponse refers to Forms callback hook
Type '(data: SignUpForm) => Promise<void>' is not assignable to type
'(data: SignUpForm | LoginForm) => Promise<void>'.
Types of parameters 'data' and 'data' are incompatible.
Type 'SignUpForm | LoginForm' is not assignable to type 'SignUpForm'.
Type 'LoginForm' is missing the following properties from type 'SignUpForm':
firstname, lastname, day, month, yearts(2322)
and the types of each is just an object with strings as values.
This app was also created using create-t3-app if that helps.
I'm not sure If I've explained this well so please feel free to edit this post or ask questions
types.ts
export interface SignUpForm {
firstname: string;
lastname: string;
email: string;
password: string;
day: string;
month: string;
year: string;
}
export interface LoginForm {
email: string;
password: string;
}
export type FormType<T extends SignUpForm | LoginForm> = T extends SignUpForm ? SignUpForm : LoginForm;
hook.tsx
const useSetForm = (component: FormType<SignUpForm | LoginForm>) => {
// component = object, is either SignupForm or LoginForm
const [value, setForm] = useState(component);
return [value, (event: React.ChangeEvent<HTMLInputElement>) => {
setForm((oldValue) => {
return { ...oldValue, [event.target.name]: event.target.value };
});
},
] as const;
};
parent.tsx
const Signup: FC<SignupProps> = ({ }): JSX.Element => {
const handleRequest = async (data: FormType<SignUpForm>) => {
const res = await client.mutation('mongo.sign-up', data);
console.log(res)
}
return (
<Layout>
<div className=''>
<Form
formData={{ firstname: '', lastname: '', email: '', password: '', }}
target={'mongo.sign-up'}
buttons={['day', 'month', 'year']}
onResponse={handleRequest} />
{/** pass types to create input fields, pass endpoint to target, buttons only needs definition for date of birth elements */}
{/** onResponse returns complete form obj */}
</div>
</Layout>
)
}
export default Signup
FormComponent.tsx
interface FormProps {
formData: FormType<SignUpForm>
target: `mongo.${'sign-up' | 'login'}` | `sendgrid.send-email`;
onResponse: (data: SignUpForm | LoginForm) => Promise<void>
};
export const Form: FC<FormProps> = ({ target, onResponse, formData }): JSX.Element => {
const [form, setForm] = useSetForm(formData);
const handleCallback = useCallback((data: SignUpForm | LoginForm) => {
onResponse(data)
// pass data to parent elements
}, [onResponse])
return (
<>
<form onSubmit={handleCallback}>
<>
{Object.keys(formData).map((types) => {
/** type is passed from parent elements, */
return (
<input
key={types}
name={types}
value={form[types]}
type={types}
placeholder={types}
onChange={setForm} />)
//renders form text input elements
})}
</>
<button type='submit' disabled={false}> Submit </button>
</form>
</>
)
}

How to use redux-form component with typescript and custom props

I am trying to create a functional component in a react + typescript project that is using redux-form. I have created a generic form component and I want to pass a prop to it. Everything is working except the interface definition for the component props. I can use any and it works but then whats the point. I've tried a few different combinations from other posts I've found from a google search (commented out) but nothing is working.
The code:
import React from 'react';
import {
Field,
reduxForm,
InjectedFormProps,
WrappedFieldProps,
WrappedFieldMetaProps,
} from 'redux-form';
import { Stream } from '../../constants';
interface CustomInputFieldProps {
label?: string;
}
interface CustomComponentProps extends InjectedFormProps {
onSubmit(values: any & Stream): void;
}
// type StreamFormProps = CustomComponentProps & InjectedFormProps<{}, CustomComponentProps>;
// CustomComponentProps & InjectedFormProps<{}, CustomComponentProps>
// what to replace any with?
const StreamForm = ({ handleSubmit, onSubmit }: any) => {
// values is actually typed as any o_0
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/9ce52af612e29ff0bac4317bde78d0acab29afdb/types/redux-form/v6/lib/Form.d.ts#L5
const onFormSubmit = (values: any): void => {
const { title, description } = values;
onSubmit({ title, description });
};
const renderInput = ({ input, label, meta }: WrappedFieldProps & CustomInputFieldProps): JSX.Element => {
const className = `field ${meta.error && meta.touched ? 'error' : ''}`;
return (
<div className={className}>
<label>{label}</label>
<input {...input} />
{renderError(meta)}
</div>
);
};
const renderError = ({ error, touched }: WrappedFieldMetaProps) => {
if (touched && error) {
return (
<div className="ui error message">
<div className="header">{error}</div>
</div>
);
}
};
return (
<form onSubmit={handleSubmit(onFormSubmit)} className="ui form error">
<Field
name="title"
component={renderInput}
label="Enter Title"
/>
<Field
name="description"
component={renderInput}
label="Enter Description"
/>
<button className="ui button primary">Submit</button>
</form>
);
};
const validate = (formValues: any): { title?: string; description?: string; } => {
const errors: { title?: string; description?: string; } = {};
if (!formValues.title) {
errors.title = 'You must enter a title';
}
if (!formValues.description) {
errors.description = 'You must enter a description';
}
return errors;
};
export default reduxForm({ form: 'streamForm', validate })(StreamForm);
The error I am getting:
Argument of type '({ handleSubmit, onSubmit }: StreamFormProps) => JSX.Element' is not assignable to parameter of type 'ComponentType<InjectedFormProps<any, {}, string>>'.
Type '({ handleSubmit, onSubmit }: StreamFormProps) => JSX.Element' is not assignable to type 'FunctionComponent<InjectedFormProps<any, {}, string>>'.
Types of parameters '__0' and 'props' are incompatible.
Type 'PropsWithChildren<InjectedFormProps<any, {}, string>>' is not assignable to type 'StreamFormProps'.
Property 'onSubmit' is missing in type 'InjectedFormProps<any, {}, string> & { children?: ReactNode; }' but required in type 'CustomComponentProps'.ts(2345)
StreamForm.tsx(17, 3): 'onSubmit' is declared here.

Get type definition for props.dispatch() for using TypeScript with React-Redux

I have this functional/stateless component:
import React from 'react';
import {useFormik} from 'formik';
import {connect} from "react-redux";
function mapStateToProps(){
return {
foo: "bar"
}
}
interface OwnProps {
propFromParent: number
}
type StateProps = ReturnType<typeof mapStateToProps>
type Props = StateProps & OwnProps
const SignupForm = (props: Props) => {
const formik = useFormik({
initialValues: {
email: '',
name: '',
password: ''
},
onSubmit(values) {
props.dispatch() // props.dispatch is not defined!
}
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="name">Full Name</label>
<input
id="name"
name="name"
type="name"
onChange={formik.handleChange}
value={formik.values.name}
/>
<button type="submit">Submit</button>
</form>
);
};
export default connect<StateProps, null, Props>(mapStateToProps)(SignupForm);
so I am getting this compile error:
So how can I include the type definition so that props.dispatch is defined?
Just looking for help with the proper TS definitions.
Please see the React-Redux docs on "Static Typing", which show how to handle defining the correct types for what connect will pass into your component.
Specifically, we recommend using the ConnectedProps<T> helper, like this:
import { connect, ConnectedProps } from 'react-redux'
interface RootState {
isOn: boolean
}
const mapState = (state: RootState) => ({
isOn: state.isOn
})
const mapDispatch = {
toggleOn: () => ({ type: 'TOGGLE_IS_ON' })
}
const connector = connect(
mapState,
mapDispatch
)
// The inferred type will look like:
// {isOn: boolean, toggleOn: () => void}
type PropsFromRedux = ConnectedProps<typeof connector>
interface Props extends PropsFromRedux {
backgroundColor: string
}
const MyComponent = (props: Props) => (
<div style={{ backgroundColor: props.backgroundColor }}>
<button onClick={props.toggleOn}>
Toggle is {props.isOn ? 'ON' : 'OFF'}
</button>
</div>
)
export default connector(MyComponent)
You need to add a new function and pass it to connect() as second argument
...
function mapDispatchToProps(dispatch): IDispatchProps {
return {
dispatch
};
}
connect(mapStateToProps, mapDispatchToProps)

Can't handle typescript with react and redux

I am new at typescript and really struggling. I can't understand where to begin and where to end . Yes , there are many recourses on internet , but i couldn't manage to get that information and use in my project . Hope to meet some help here. I have also done some typechecking and if you find something that i could have done better , please just tell me to improve it.
So now i am struggling with redux mapStateToProps and mapDispatchToProps . I have tried many variants , but every time i have got some kind of errors.
I will post my code which represents my dashboard component which is connected to state .
import * as React from 'react';
import { connect } from 'react-redux';
import SearchIcon from '../SvgIcons';
import MapComponent from '../Map';
import { getEventInfo, getUserInfo } from '../../actions';
interface StateProps {
userReducer: {
accessToken: string
},
eventReducer: {
events: object[]
}
}
interface DispatchProps {
dispatchUserInfo: () => void;
dispatchEventInfo: (accessToken: string, query: string) => void;
}
interface OwnProps {
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void,
accessToken: string,
events: object[]
}
type Props = StateProps & DispatchProps & OwnProps;
class DashboardPage extends React.Component<Props, {}> {
componentDidMount() {
const { dispatchUserInfo } = this.props;
dispatchUserInfo();
}
handleEventSearch = e => {
e.preventDefault();
const { dispatchEventInfo, accessToken } = this.props;
const query: string = e.target.children[0].value;
dispatchEventInfo(accessToken, query);
}
render() {
const { events } = this.props;
return (
<div className="dashboard-container">
<div className="search-event">
<form className="search-event__form" onSubmit={this.handleEventSearch}>
<input
autoComplete="off"
type="text"
name="search-event"
placeholder="Search an event"
className="search-event__input"
aria-label="Enter search text"
/>
<button type="submit" className="search-event__button">
<SearchIcon />
Search
</button>
</form>
<p className="sign-out">
Sign out
</p>
</div>
<div className="google-map">
<MapComponent events={events} />
</div>
</div>
);
}
}
const mapStateToProps = (state: StateProps) => {
const accessToken = state.userReducer.accessToken || '';
const events = state.eventReducer || [];
return {
accessToken,
events
};
};
const mapDispatchToProps = (dispatch: DispatchProps) => ({
dispatchEventInfo(query: string, token: string) {
dispatch(getEventInfo(query, token));
},
dispatchUserInfo() {
dispatch(getUserInfo());
}
});
export default connect(mapStateToProps, mapDispatchToProps)(DashboardPage);
These are typescript errors
1) Refers tohandleEventSearch`method
[ts] Parameter 'e' implicitly has an 'any' type.
2) refers to mapDispatchToProps
[ts] Cannot invoke an expression whose type lacks a call signature. Type 'DispatchProps' has no compatible call signatures.
3) refers to mapDispatchToProps in connect HOC
Argument of type '(dispatch: DispatchProps) => { dispatchEventInfo(query: string, token: string): void; dispatchUserInfo(): void; }' is not assignable to parameter of type 'MapDispatchToPropsParam<{ dispatchEventInfo(query: string, token: string): void; dispatchUserInfo(): void; }, {}>'.
Type '(dispatch: DispatchProps) => { dispatchEventInfo(query: string, token: string): void; dispatchUserInfo(): void; }' is not assignable to type 'MapDispatchToPropsFunction<{ dispatchEventInfo(query: string, token: string): void; dispatchUserInfo(): void; }, {}>'.
Types of parameters 'dispatch' and 'dispatch' are incompatible.
Type 'Dispatch>' is not assignable to type 'DispatchProps'.
Property 'dispatchUserInfo' is missing in type 'Dispatch>'.
Also if you can provide me very good sources to learn about react and redux with typescript , so i could easily write my code.
You need to supply event type explicitly, any or
React.SyntheticEvent<...something>
Dispatch parameter should be Dispatch type from Redux
Also a little tip, you can define your props like:
ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps> & OwnProps
and remove unnecessary interfaces

Resources