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;
Related
I have a Formik form which uses check-boxes and selects. For the select field I am using react-select. There is no problem to submit the form but when I want to reset the form the react-select part is not clearing and also when I want to prefill the form after a push of a button with setFieldValue, again the react-select part is not responding.
How can I make it work this form with react-select within Formik?
https://codesandbox.io/s/wonderful-breeze-dyhi3b
App.js
import React from "react";
import FormikReactSelect from "./components/FormikReactSelect";
function App() {
return <FormikReactSelect />
}
export default App;
FormikReactSelect.js
import React, {useRef} from "react";
import {Form, Field, Formik, FieldArray} from "formik";
import SelectField from "./SelectField";
function FormikReactSelect() {
const formikRef = useRef();
const drinkDist = ["Wine", "Beer", "Whiskey"];
const countryDist = ["US", "FR", "DE", "BE", "IT"];
const arrToValLab = (arr) => {
if (arr !== undefined) {
const valLab = arr.map((t) => ({
value: t,
label: t,
}));
return valLab;
} else {
return null;
}
};
const onClick = () => {
if (formikRef.current) {
formikRef.current.setFieldValue("drinks", ["Whiskey"]);
}
if (formikRef.current) {
formikRef.current.setFieldValue("countries", ["FR"]);
}
};
return (
<>
<Formik
innerRef={formikRef}
enableReinitialize={true}
initialValues={{drinks: [], countries: []}}
>
{({
values,
handleChange,
handleSubmit,
handleBlur,
resetForm,
setFieldValue,
}) => (
<Form noValidate>
Drinks:
<FieldArray
name="drinks"
render={(arrayHelpers) => (
<div>
{drinkDist.map((r, i) => (
<div key={i}>
<label>
<Field
name="drinks"
type="checkbox"
value={r}
checked={values.drinks.includes(r)}
onChange={(e) => {
if (e.target.checked) {
arrayHelpers.push(r);
} else {
const idx = values.drinks.indexOf(r);
arrayHelpers.remove(idx);
}
}}
/>
{" " + r}
</label>
</div>
))}
</div>
)}
/>
Countries:
<Field
component={SelectField}
name="countries"
options={arrToValLab(countryDist)}
/>
<button
type="button"
onClick={() => {
resetForm();
}}
>
Reset
</button>
{JSON.stringify(values, null, 2)}
</Form>
)}
</Formik>
Sending preset values:
<button onClick={onClick}>Set Field</button>
</>
);
}
export default FormikReactSelect;
SelectFireld.js
import React from "react";
import Select from "react-select";
import {useField} from "formik";
export default function SelectField(props) {
const [field, state, {setValue, setTouched}] = useField(props.field.name); // eslint-disable-line
const onChange = (value) => {
let arrValue = [];
value.map((k) => arrValue.push(k.value));
setValue(arrValue);
};
return <Select {...props} isMulti onChange={onChange} onBlur={setTouched} />;
}
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
How to integrate Quill (rich-text-editor) with react-final-form?
Thats it works for me
import { FC } from 'react';
import { Field } from 'react-final-form';
import ReactQuill from 'react-quill';
const FormQuill: FC<{ name: string }> = ({ name }) => (
<Field name={name}>
{({ input: { value, ...input } }) => (
<ReactQuill
value={value}
onChange={(newValue, delta, source) => {
if (source === 'user') {
input.onChange(newValue);
}
}}
/>
)}
</Field>
);
export default FormQuill;
Hey there I am new to formik library, and I am trying to use react-draft-wysiwyg component with Formik. Here is my code.
RichTextEditor.js
import React, { useState } from "react";
import { EditorState, convertToRaw, ContentState } from "draft-js";
import { Editor } from "react-draft-wysiwyg";
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
import draftToHtml from "draftjs-to-html";
import htmlToDraft from "html-to-draftjs";
import { useField } from "formik";
const RichTextEditor = (props) => {
const [field, meta, helpers] = useField(props);
const { value } = meta;
const { setValue } = helpers;
const prepareDraft = (value) => {
const draft = htmlToDraft(value);
const contentState = ContentState.createFromBlockArray(draft.contentBlocks);
const editorState = EditorState.createWithContent(contentState);
return editorState;
};
const [editorState, setEditorState] = useState(
value ? prepareDraft(value) : EditorState.createEmpty()
);
const onEditorStateChange = (editorState) => {
const forFormik = draftToHtml(
convertToRaw(editorState.getCurrentContent())
);
setValue(forFormik);
setEditorState(editorState);
};
return (
<div>
<Editor
editorState={editorState}
wrapperClassName="demo-wrapper"
editorClassName="demo-editor"
onEditorStateChange={onEditorStateChange}
{...props}
{...field}
/>
</div>
);
};
export default RichTextEditor;
and AddPost.js
import React from "react";
import { Row, Col, Card, Form, Input } from "antd";
import { useFormik, Field } from "formik";
import RichTextEditor from "../RichTextEditor/RichTextEditor";
const initialValues = {
title: "",
body: "",
};
export default function AddContent() {
const formik = useFormik({
initialValues,
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<Row>
<Col span={19}>
<Card>
<>
<h1>
{formik.values.title ? formik.values.title : "Content Title"}
</h1>
<Form onSubmit={formik.handleSubmit}>
<Input
id="title"
name="title"
placeholder="Content Title"
onChange={formik.handleChange}
value={formik.values.email}
/>
<Field
name="body"
component={RichTextEditor}
value={formik.values.body}
onChange={formik.handleChange}
// {...formik.getFieldProps("body")}
/>
</Form>
</>
</Card>
</Col>
<Col span={5}></Col>
</Row>
);
}
But I am getting following error
TypeError: formik.getFieldProps is not a function
Field
src/Field.tsx:181
178 | unregisterField(name);
179 | };
180 | }, [registerField, unregisterField, name, validate]);
> 181 | const field = formik.getFieldProps({ name, ...props });
| ^ 182 | const meta = formik.getFieldMeta(name);
183 | const legacyBag = { field, form: formik };
184 |
I had the same problem and fixed it with the following fix.
In short, wrap inside the render with a FormikProvider.
AddPost.js
// add FormikProvider
import { useFormik, Field, FormikProvider } from "formik";
const formik = useFormik({
initialValues,
onSubmit: (values) => {...},
});
// wrap with FormikProvider
return (
<FormikProvider value={formik}>
<Row>...your-code...</Row>
</FormikProvider>
)
this error is raised because you are using getFieldProps() function on Field component provided by formik and not on normal component like Input which is the two is working differently.
you should use it like that.
<Form onSubmit={formik.handleSubmit}>
<Input
id="title"
placeholder="Content Title"
{...formik.getFieldProps("title")}
/>
<Input
component={RichTextEditor}
{...formik.getFieldProps("body")}
/>
</Form>
for more information about getFieldProps();
https://formik.org/docs/tutorial#getfieldprops
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;