Using formik with typescript (Ionic) - reactjs

I try to use Formik library for validating forms however I have trouble passing onChange function to my input components.
Here is the main component:
import React from 'react';
import { IonHeader, IonContent, IonToolbar, IonTitle, IonButton, IonPage, IonList } from '#ionic/react';
import { useFormik } from 'formik';
import BasicInput from '../components/form/BasicInput';
const Login: React.FC = () => {
const validate = (values: {
name: string;
}) => {
const errors = {};
if (!values.name) {
}
return errors;
}
const formik = useFormik({
initialValues: {
name: ''
},
validateOnChange: false,
validateOnBlur: false,
validate,
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
})
const {
name
} = formik.values
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Login</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<IonList>
<BasicInput
id="name"
placeholder={"Your name"}
onChange={formik.handleChange}
/>
</IonList>
<div>
<IonButton onClick={handleLogin}>
Done
</IonButton>
</div>
</IonContent>
</IonPage>
)
}
export default Login;
And here is my input component:
import React from 'react';
import { IonInput, IonItem } from '#ionic/react';
const BasicInput: React.FC<{
id: string,
placeholder: string,
onChange: () => any
}> = (props) => {
return (
<IonItem>
<IonInput
placeholder={props.placeholder}
onIonChange={props.onChange}
>
</IonInput>
</IonItem>
)
}
export default BasicInput;
Error im getting is "Type '(eventOrPath: string | ChangeEvent) => void | ((eventOrTextValue: string | ChangeEvent) => void)' is not assignable to type '() => any'." on my onChange prop.
How to properly declare it for TS?

The message indicates where is the problem.
In BasicInput, you are defining a prop onChange: () => any. But clearly, the function that is going to be called will need to receive the new input value as a parameter and does not need to return anything, so your prop definition should be something like this:
{
// ...
onChange: (newValue: string) => void;
}
But, if you hover your mouse over the onIonChange prop of <IonInput />, you can see that the passed function does not receive a string as a parameter!
This is the full definition: https://github.com/ionic-team/ionic/blob/976e68da5b030baf26dc60034ff80d8db2cff8e6/core/src/components.d.ts#L4137
It actually fires a CustomEvent. Having this event e, you can access the input value via e.target.value.
The full code becomes this:
import React from 'react';
import { IonInput, IonItem } from '#ionic/react';
const BasicInput: React.FC<{
id: string,
placeholder: string,
onChange: (newValue: string) => void
}> = (props) => {
return (
<IonItem>
<IonInput
placeholder={props.placeholder}
onIonChange={(e) => props.onChange(e.target.value)}
>
</IonInput>
</IonItem>
)
}
export default BasicInput;

Related

React currency input field with customInput example

I am trying to render customInput in React currency input field, What I have so far is:
import { TextField, TextFieldProps } from '#mui/material';
import React from 'react';
import CurrencyInput from 'react-currency-input-field';
import { Controller, RegisterOptions, useFormContext } from 'react-hook-form';
type IProps = {
name: string;
rules?: RegisterOptions;
defaultValue?: string;
};
type Props = IProps & TextFieldProps;
export default function RHFCurrencyField({ name, rules, defaultValue, ...other }: Props) {
const { control } = useFormContext();
return (
<Controller
name={name}
rules={rules}
control={control}
render={({ field, fieldState: { error } }) => (
<CurrencyInput
name={name}
groupSeparator=" "
defaultValue={defaultValue}
decimalsLimit={2}
onValueChange={(value, name) => {
field.onChange(value);
}}
customInput={() => <CurrencyTextField {...other} {...field} />}
/>
)}
/>
);
}
export const CurrencyTextField = React.forwardRef((props: Props, ref: any) => {
return <TextField {...props} ref={ref} />;
});
And I am getting a warning, and input i
react-dom.development.js:67 Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Check the render method of CurrencyInput.
What am I missing ?

Textarea input fields in Chakra UI wont submit to react hook form

I am using nextjs (v13), react (v18) chakraUI and react hook form.
If I use Inputs (only), I can submit this form. If I change the description field to be a Textarea (from ChakraUI), the form displays on the page, but will not submit. I get no errors in the console - I can't see what's causing the issue.
Is it possible to submit data from a Textarea via react-hook-form?
import * as React from "react"
import { gql } from "#apollo/client"
import { Button, Stack, Textarea, Text } from "#chakra-ui/react"
import { useRouter } from "next/router"
import { useCreateIssueGroupMutation } from "lib/graphql"
import { useForm } from "lib/hooks/useForm"
import Yup from "lib/yup"
import { ButtonGroup } from "./ButtonGroup"
import { Form } from "./Form"
import { FormError } from "./FormError"
import { Input } from "./Input"
import { Modal } from "antd"
const _ = gql`
mutation CreateIssueGroup($data: IssueGroupInput!) {
createIssueGroup(data: $data) {
id
}
}
`
interface Props {
onClose: () => void
}
const IssueGroupSchema = Yup.object().shape({
title: Yup.string().required(),
description: Yup.string().required(),
})
export function AdminCreateIssueGroupForm(props: Props) {
const router = useRouter()
const [createIssueGroup] = useCreateIssueGroupMutation()
const defaultValues = {
title: "",
description: "",
}
const form = useForm({ defaultValues, schema: IssueGroupSchema })
const handleSubmit = (data: Yup.InferType<typeof IssueGroupSchema>) => {
return form.handler(() => createIssueGroup({ variables: { data: { ...data } } }), {
onSuccess: (res, toast) => {
toast({ description: "Issue group created" })
form.reset()
props.onClose()
},
})
}
return (
<Form {...form} onSubmit={handleSubmit}>
<Stack>
<Input name="title" label="Title" />
// this input works and allows me to submit the form
{/* <Input name="description" label="Description" /> */}
// the next 2 lines do not work. The page renders but the form does not submit
<Text mb='8px' fontWeight="medium" fontSize="sm" > Description</Text>
<Textarea name="description" rows={4} />
<FormError />
<ButtonGroup>
<Button onClick={props.onClose}>Cancel</Button>
<Button
type="submit"
isLoading={form.formState.isSubmitting}
isDisabled={form.formState.isSubmitting}
color="brand.white"
fontWeight="normal"
backgroundColor="brand.orange"
_hover={{
backgroundColor: "brand.green",
color: "brand.white",
}}
>
Create
</Button>
</ButtonGroup>
</Stack>
</Form>
)
}
My Form component has:
import * as React from "react"
import type { FieldValues, UseFormReturn } from "react-hook-form"
import { FormProvider, useFormContext } from "react-hook-form"
import { Box } from "#chakra-ui/react"
import * as Sentry from "#sentry/nextjs"
import { useToast } from "lib/hooks/useToast"
interface FormContainerProps {
onSubmit?: (values: any) => Promise<any> | any
onBlur?: (values: any) => Promise<any> | any
}
const FormContainer: React.FC<FormContainerProps> = (props) => {
const toast = useToast()
const { handleSubmit } = useFormContext()
const onSubmit = async (values: any) => {
try {
if (props.onBlur) {
return await props.onBlur(values)
}
if (props.onSubmit) {
return await props.onSubmit(values)
}
} catch (e) {
console.log(e)
Sentry.captureException(e)
toast({
title: "Application error",
description: "Something went wrong. We have been notified!",
status: "error",
})
return
}
}
return (
<Box
as="form"
w="100%"
{...(props.onSubmit && { onSubmit: handleSubmit(onSubmit) })}
{...(props.onBlur && { onBlur: handleSubmit(onSubmit) })}
>
{props.children}
</Box>
)
}
interface Props<T extends FieldValues> extends UseFormReturn<T>, FormContainerProps {
children: React.ReactNode
isDisabled?: boolean
}
export function Form<T extends FieldValues>({ onSubmit, onBlur, isDisabled, ...props }: Props<T>) {
return (
<FormProvider {...props}>
<fieldset disabled={isDisabled}>
<FormContainer {...{ onSubmit, onBlur }}>{props.children}</FormContainer>
</fieldset>
</FormProvider>
)
}
Input has:
import * as React from "react"
import { useFormContext } from "react-hook-form"
import type { InputProps } from "#chakra-ui/react";
import { FormControl, Input as CInput } from "#chakra-ui/react"
import { InputError } from "./InputError"
import { InputLabel } from "./InputLabel"
interface Props extends InputProps {
name: string
label?: string
subLabel?: string
}
export const Input = ({ label, subLabel, ...props }: Props) => {
const {
register,
formState: { errors },
} = useFormContext()
const fieldError = errors?.[props.name]
return (
<FormControl isInvalid={!!fieldError} isRequired={props.isRequired}>
<InputLabel label={label} subLabel={subLabel} name={props.name} />
<CInput {...register(props.name)} mb={0} {...props} />
<InputError error={fieldError} />
</FormControl>
)
}
Each form component connected to React Hook Form needs to receive a register or be wrapped by a Controller component. Your input component receives this by useFormContext as you mentioned:
<CInput {...register(props.name)} mb={0} {...props} />
However, TextArea component doesn't receive anything from Hook Form, in that case, you need to use the same register('').
An example of this implementation (live on CodeSandbox):
function App() {
const { register, handleSubmit } = useForm({
defaultValues: {
title: "",
description: ""
}
});
return (
<>
<form onSubmit={handleSubmit((data) => console.log(data))}>
<Heading>Welcome to Chakra + TS</Heading>
<p>Title</p>
<Input {...register("title")} />
<p>Description</p>
<Textarea {...register("description")} />
<Button type="submit">Submit</Button>
</form>
</>
);
}
Useful links:
Register
Controller
Live Example

dynamic import component inside another using path in react

I am new to react. I have a task for importing a component inside another one dynamically.
as you can see I have an AutoComplete component that uses react-select; what I want is when user searches for a value and the value is not provided by the list, the user clicks on Create ... option and a dialog opens containing the needed component ( the component need to by imported at that time). Now I am passing the component as child but I want it more dynamic like passing the path of Component as prop and the AutoComplete loads by the path. any solution?
sample component uses AutoComplete:
<Autocomplete
apiUrl="/coreums/api/provinces"
onChange={(field, value) => {
console.log(`${field} ${value}`);
}}
onCreate={(field) => {
console.log(field);
}}
name="province"
componentName=""
entity={null}
>
{/* <ProvinceUpdate></ProvinceUpdate> */}
<h1>Hello</h1>
</Autocomplete>
as you see I am passing a h1 and it renders but for another component like ProvinceUpdater just want to pass my components path and it will be rendered.
which changes need my AutoComplete?
here is my AutoComplete:
import React, {
Component,
FC,
Suspense,
lazy,
useEffect,
useState,
} from 'react';
import Axios from 'axios';
import CreatableSelect from 'react-select/creatable';
import Select from 'react-select';
import { AvForm } from 'availity-reactstrap-validation';
import { Button } from 'primereact/button';
import { Dialog } from 'primereact/dialog';
interface Props {
name: string;
apiUrl: string;
entity: any;
filterBy?: string;
onChange: (field: string, value: any) => void;
onCreate: (field: string) => void;
componentName?: string;
}
const Autocomplete: FC<Props> = ({
name,
apiUrl,
entity,
onChange,
children,
onCreate,
filterBy = 'name',
componentName,
}) => {
const [myOptions, setMyOptions] = useState([]);
const [childDialogVisible, setChildDialogVisible] = useState(false);
useEffect(() => {
Axios.get(`services/${apiUrl}?cacheBuster=${new Date().getTime()}`).then(
(res) => {
// console.log(res)
setMyOptions(
res.data.map((obj) => {
return { value: obj.id, label: obj[filterBy] };
})
);
}
);
}, []);
const handleChange = (newValue: any, actionMeta: any) => {
if (newValue) onChange(name, newValue.value);
};
const handleInputChange = (inputValue: any, actionMeta: any) => {
Axios.get(
`services/${apiUrl}?name.contains=${inputValue}&cacheBuster=${new Date().getTime()}`
).then((res) => {
setMyOptions(
res.data.map((obj) => {
return { value: obj.id, label: obj.name };
})
);
});
};
const handleCreate = (inputValue) => {
setChildDialogVisible(true);
onCreate(inputValue);
};
const renderFooter = () => {
return (
<div>
<Button
label="No"
icon="pi pi-times"
onClick={() => setChildDialogVisible(false)}
className="p-button-text"
/>
<Button
label="Yes"
icon="pi pi-check"
onClick={() => setChildDialogVisible(false)}
autoFocus
/>
</div>
);
};
return (
<>
<CreatableSelect
options={myOptions}
onChange={handleChange}
onCreateOption={handleCreate}
onInputChange={handleInputChange}
menuPortalTarget={document.body}
/>
<Dialog
header="Header"
visible={childDialogVisible}
style={{ width: '50vw' }}
footer={renderFooter()}
onHide={() => setChildDialogVisible(false)}
>
<Suspense fallback={() => <p>Loading</p>}>{children}</Suspense>
</Dialog>
</>
);
};
export default Autocomplete;

Ionic React-Hook-Forms

Trying to use Ionic React Hook Forms, could anyone help me with the below questions
Trying to build forms dynamically, how can we pass the properties of the ion element dynamically. in the below example i want to pass maxlength and text property (coming from backend / formFields) to IonInput Element
How to pre-populate the value to the input field
Form.tsx
import {
IonContent,
IonPage,
IonText,
IonButton,
} from "#ionic/react";
import React from "react";
import "./Form.css";
import { useForm } from "react-hook-form";
import * as globalStructre from "../Components/Content";
import Input from "../Components/Input";
import Date from "../Components/Date";
import Select from "../Components/Select";
import Textarea from "../Components/Textarea";
import Toggle from "../Components/Toggle";
const Form: React.FC = () => {
const { control, handleSubmit } = useForm();
const formFields: globalStructre.IngressProps[] = [
{
type: "Input",
name: "email",
component: ['type=\'email\''],
label: "Email",
value: "",
},
{
type: "Input",
name: "fullName",
component: ['maxlength = 60', 'type=\'text\''], //properties
label: "Full Name",
value: "test",
},
{
type: "Input",
name: "password",
component: ['maxlength = 12', 'type=\'password\''],
label: "Password",
value: "",
},
];
const registerUser = (data: any) => {
console.log("creating a new user account with: ", data);
};
const buildForm = (field: globalStructre.IngressProps, key: any) => {
let inputElement = null;
switch (field.type) {
case "Input":
inputElement = (
<Input
key={key}
name={field.name}
control={control}
component={field.component}
value={field.value}
label={field.label}
rules={field.rules}
/>
);
break;
}
return inputElement;
};
return (
<IonPage>
<IonContent>
<div className="ion-padding">
<IonText color="muted">
<h2>Create Account</h2>
</IonText>
<form onSubmit={handleSubmit(registerUser)}>
{formFields.map((field, index) =>
//<Input {...field} control={control} key={index} />
buildForm(field, index)
)}
<IonButton expand="block" type="submit" className="ion-margin-top">
Register
</IonButton>
</form>
</div>
</IonContent>
</IonPage>
);
};
export default Form;
Input.tsx
import React, { FC } from "react";
import { IonItem, IonLabel, IonInput, IonText } from "#ionic/react";
import { Controller} from "react-hook-form";
import * as globalStructre from './Content';
import './Input.css';
const Input: FC<globalStructre.IngressProps> = ({
key,
name,
control,
component,
value,
label,
rules,
errors,
}) => {
return (
<>
<IonItem >
{label && <IonLabel position="floating">{label}</IonLabel>}
<Controller
render = {({ onChange, onBlur, value }) => (
<IonInput
key={key}
value ={value}
onIonChange={onChange}
/>
)}
name={name}
control={control}
rules = {rules}
/>
</IonItem>
</>
);
};
export default Input;

Prop does not exist on type 'PropsWithChildren' when trying to dispatch the action using react-redux

I am getting this error while trying to dispatch the action:
Property 'fetch_feed_loc' does not exist on type 'PropsWithChildren<{ onSubmitForm: any; }>'.
Property 'fetch_feed_loc_srch' does not exist on type 'PropsWithChildren<{ onSubmitForm: any; }>'.
But when I console.log props it is showing me the actions exist as the props.
import React, { useState } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { IonLabel, IonInput, IonIcon, IonItem, IonButton, IonBadge, IonSelect, IonSelectOption } from '#ionic/react';
import {navigateOutline} from 'ionicons/icons';
import { connect } from 'react-redux';
import { fetch_feed_loc, fetch_feed_loc_srch } from '../actions';
const LocationSearch: React.FC<{onSubmitForm: any}> = props => {
console.log(props);
const [data, setData] = useState<object>({});
const initialValues = {
location: '',
searchTerm: ''
}
const {control, handleSubmit, errors, formState, setValue} = useForm({
defaultValues: initialValues,
mode : 'onChange'
})
const onSubmit = (data: any) => {
const submitttedData: any = data;
setData(submitttedData);
submitttedData.searchTerm == '' ? props.fetch_feed_loc(submitttedData.location) : props.fetch_feed_loc_srch(submitttedData.location, submitttedData.searchTerm)
};
return(
<div className="ion-padding">
<form onSubmit={handleSubmit(onSubmit)} >
<IonItem className="cm-form-field" lines="none">
<IonLabel position="floating">Location</IonLabel>
<Controller
as={IonInput}
control={control}
onChangeName="onIonChange"
onChange={([selected]) => {
return selected.detail.value;
}}
name="location"
rules={{
required: true,
}}
/>
{errors.location ? renderErrorMessage('location'): null }
</IonItem>
<IonItem className="cm-form-field" lines="none">
<IonLabel position="floating">Search</IonLabel>
<Controller
as={IonInput}
control={control}
onChangeName="onIonChange"
onChange={([selected]) => {
return selected.detail.value;
}}
name="searchTerm"
rules={{
minLength: { value: 3, message: "Must be min 3 chars long" }
}}
/>
</IonItem>
{errors.searchTerm ? renderErrorMessage('searchTerm'): null }
<IonButton
expand="block"
shape="round"
size="default"
type="submit"
disabled={formState.isValid === false}
>
<IonIcon slot="start" icon={navigateOutline}></IonIcon>
Submit
</IonButton>
</form>
</div>
)
}
export default connect(null, { fetch_feed_loc, fetch_feed_loc_srch })(LocationSearch);
I don'n know what exactly you keep inside the props but you have to describe it within type or interface. Jus imaging your props keeps two objects: product and user so in this case you need to describe those objects:
type Props = {
product: {
title: string;
description: string;
price: number;
...
},
user: {
userName: string;
userEmail: string:
...
}
}
And later you can use this type and destruct your props:
const LocationSearch: React.FC<Props> = ({product, user})=> ...
One more thing, do not use ANY type while using TypeScript, TypeScript is used for types.

Resources