Reset and setFieldValue with react-seelect within Formik - reactjs

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} />;
}

Related

how to validate inputs in a child component (formik)

I generated Field in CustomField and call it in the CustomForm component. I want to validate each Customefield in itself with validateAsync but validateAsync is not working and meta. the error object is always empty.
this is my CustomForm component:
import { Field, Form, Formik, yupToFormErrors } from "formik";
import React, { Component } from "react";
import CustomField from "./customeField";
import * as Yup from "yup";
class CustomForm extends Component {
state = {};
render() {
return (
<Formik
initialValues={{ name1: "", name2: "" }}
onSubmit={(values) => {
console.log(values);
}}
>
<Form>
<CustomField name="name1" lable="name1"></CustomField>
<CustomField name="name2" lable="name2"></CustomField>
<button type="submit">send</button>
</Form>
</Formik>
);
}
}
export default CustomForm;
and this is my CustomField component
import React, { Component } from "react";
import { Field, Form, Formik, useField, ErrorMessage } from "formik";
import * as Yup from "yup";
const CustomField = ({ lable, ...props }) => {
const [field, meta] = useField(props);
const validateAsync = () =>
Yup.object({
name1: Yup.string().required("error"),
});
return (
<div>
<label htmlFor={props.id || props.name}>{lable}</label>
<Field validate={validateAsync} {...field} {...props} />
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : (
<div className="error"></div>
)}
</div>
);
};
export default CustomField;
there are two problems in your code, the first one is how you use the Yup for validation and the second problem is about that validateAsync function, as Formik doc mentions you have to get input value from the first argument and as result, you can return undefined (which means there is no error) or a string (error message), BTW it's also possible to return a promise that indicates is input value valid or not.
here is how you can go with this case:
const CustomField = ({ label, name }: CustomFieldProps) => {
const [field, meta] = useField({ name });
const validate = (x: string) => {
const error_msg = "Error!!!";
try {
Yup.string().required(error_msg).validateSync(x).toString();
} catch (error) {
return error_msg;
}
};
return (
<div>
<Field validate={validate} {...field} />
{meta.touched && meta.error && <div className="error">{meta.error}</div>}
</div>
);
};
p.s: here is the link of a working sample sandbox if you need a playground:
https://codesandbox.io/s/formik-field-validate-rx3snh?file=/src/App.tsx:173-650

multiple fields with yup and react hook forms

I'm trying to do this with yup and react-hook-forms
For example, if I'm given an array of ids, then I would like to have a field for each id. The ids are random (i.e., we could have 4 ids or 100 ids). For now, I just want to see if all the input is filled (.required())
This is how I would handle validation without any libraries
export default function App(){
const [ids, setIds] = React.useState(arr1)
const inputValues = React.useRef({});
const handleSubmit = () => {
const { current: values } = inputValues;
console.log(values);
};
const validateInput = event => {
const { name, value } = event.target;
// validation done here
if(true){
inputValues.current[name] = value;
}
};
return (
<div>
<form onSubmit={handleSubmit}>
{ids.map(num => (
<input name={num} onChange={validateInput} required key={num} />
))};
<button type="submit">submit</button>
</form>
</div>
);
}
https://stackblitz.com/edit/react-ts-4jnfx2?file=App.tsx
Now how would I do this with yup and react hook forms to validate the input ?
As per the sandbox below, you can find an implementation with React Hook Form as shown here;
As it's pretty easy to follow from it's own documentation here, you don't need any other variables. So that I made it a bit simplification and only used necessary handleSubmit function, instead of validateInput and references.
import { useState } from "react";
import { useForm } from "react-hook-form";
export function App() {
const { register, handleSubmit } = useForm();
const [data, setData] = useState("");
const [ids] = useState(["1", "3", "40", "18"]);
return (
<form
onSubmit={handleSubmit((data) => {
console.log(data);
setData(JSON.stringify(data));
})}
>
{ids.map((num) => (
<input key={num} name={num} {...register(num, { required: true })} />
))}
<p>{data}</p>
<input type="submit" value="submit" />
</form>
);
}
Please check below code. I don't have experience with TypeScript so I have wrote it in JavaScript
App.js
import React from 'react';
import { useForm } from 'react-hook-form';
import { yupResolver } from '#hookform/resolvers/yup';
import * as yup from 'yup';
const arr1 = [1, 2, 3, 4, 5, 6, 7, 8];
const createSchema = (keys, valueSchema) => yup.object().shape(keys.reduce((acc, key) => ({ ...acc, [key]: valueSchema }), {}));
const mySchema = createSchema(arr1, yup.string().required());
const App = () => {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: yupResolver(mySchema),
});
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
{arr1.map((item) => (
<div key={item} style={{ border: '1px solid black' }}>
<input {...register(`${item}`)} />
<p>{errors?.[item]?.message}</p>
</div>
))}
<input type="submit" />
</form>
);
};
export default App;

How to call child submit method from another child component

Im trying to solve this for 2 days but cant.
How to call onSubmit method in CreateProject component when onApply function called in ModalContent component with Typescript and react-hook-form.
The idea: when I clicked button in ModalContent it should call react-hook-form onSumbit method in CreateProject. Then CreateProject calls onSubmit in parent component of these two childs.
Or maybe the idea of such component structure is wrong?
Thanks everyone for any answers.
Parent component:
import { ModalBody } from '../../components/modals'
import useCreateProject from '../../api/hooks/createProject'
export default function New(): JSX.Element {
const [modalVisible, setModalVisible] = React.useState(true)
const onSubmit = useCreateProject((data) => {
const { createProject } = data.data.data
console.log(data)
})
const onApply = () => {
console.log(123)
}
return (
<ModalContent
name={'Create project'}
visible={modalVisible}
onApply={onApply}
onCancel={() => setModalVisible(false)}
>
<ModalBody>
<CreateProject onSubmit={onSubmit.mutate} />
</ModalBody>
</ModalContent>
)
}
Child component
import React from 'react'
import Input from '../Inputs/Input'
import Textarea from '../Inputs/Textarea'
import FileUploader from '../uploader/FileUploader'
import Tasks from '../forms/Tasks'
import { useForm } from 'react-hook-form'
import {
draftBudgetMaxLength,
projectNameMaxLength,
descriptionMaxLength,
} from '../../constants/createProjectModal'
import ButtonSecondary from '../buttons/ButtonSecondary'
export interface IModalInputs {
create_project_name: string
}
export interface ICreateProjectProps {
onSubmit: (values: IModalInputs) => void
}
const СreateProject: React.FC<ICreateProjectProps> = ({
onSubmit,
}): React.ReactElement => {
const {
register,
getValues,
control,
handleSubmit,
formState: { errors },
} = useForm<IModalInputs>()
return (
<>
<form onSubmit={handleSubmit(() => onSubmit(getValues()))}>
<div className="p-4">
<div className="flex flex-col -m-1.5">
<div className="m-1.5">
<Input
type="text"
label="Project name"
name="create_project_name"
register={register}
error={errors.create_project_name}
options={{
required: true,
maxLength: projectNameMaxLength,
}}
/>
</div>
</div>
</div>
</form>
</>
)
}
export default СreateProject
Child component with click event
import React from 'react'
import Image from 'next/image'
import close from '../../assets/close.svg'
import ButtonSecondary from '../buttons/ButtonSecondary'
interface IModalContentProps {
children: React.ReactElement
onApply?: () => void
visible: boolean
buttonName?: string
}
const ModalContent: React.FC<IModalContentProps> = ({
name,
children,
visible,
onCancel,
onApply,
buttonName,
}) => {
return (
<>
{visible && (
<div className="flex p-4 space-x-2 justify-end">
{children}
<ButtonSecondary
click={onApply}
type={'submit'}
label={buttonName || 'Ok'}
id={buttonName}
shown={true}
styleClass="styleClass"
paddingClass="py-2 py-2 pr-4 pl-2"
/>
</div>
)}
</>
)
}
export default ModalContent
The problem was resolved with useRef and useImperativeHandle hooks.
Parent:
export default function New(): JSX.Element {
const [modalVisible, setModalVisible] = React.useState(true)
const childRef = React.useRef<any>()
const onSubmit = useCreateProject((data) => {
const { createProject } = data.data.data
console.log(data)
})
return (
<ModalContent
name={'Create project'}
visible={modalVisible}
onApply={() => childRef.current.SubmitForm()}
onCancel={() => setModalVisible(false)}
>
<ModalBody>
<CreateProject onSubmit={onSubmit.mutate} ref={childRef} />
</ModalBody>
</ModalContent>
)
}
Child:
export interface IModalInputs {
create_project_name: string
}
export interface ICreateProjectProps {
onSubmit: (values: IModalInputs) => void
}
function CreateProject(props: ICreateProjectProps, ref) {
const {
register,
getValues,
control,
handleSubmit,
formState: { errors },
} = useForm<IModalInputs>()
useImperativeHandle(ref, () => ({
SubmitForm() {
handleSubmit(() => props.onSubmit(getValues()))()
},
}))
return (
<>
<form ref={ref}>
<div className="p-4">
<div className="flex flex-col -m-1.5">
<div className="m-1.5">
<Input
type="text"
label="Project name"
name="create_project_name"
register={register}
error={errors.create_project_name}
options={{
required: true,
maxLength: projectNameMaxLength,
}}
/>
</div>
</div>
</div>
</form>
</>
)
}
export default React.forwardRef(CreateProject)

Using useFormik() with <Field /> getting error: formik.getFieldProps is not a function

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

How to dispatch event in handleSubmit in withFormik

still new to formik and react hook.
Here is my code in react.
// react
import React, { useEffect } from 'react';
import { withFormik } from 'formik';
import { useDispatch } from 'redux-react-hook';
import { takeEvery, call, put } from 'redux-saga/effects';
// row, col, field, input, buttonGroup
import {
Row,
Col,
FieldWrapper,
Input,
ButtonGroup
} from 'some-tool';
const searchTypeOption = [
....
];
const SearchForm = (props: any) => {
const {
values,
touched,
errors,
handleChange,
handleSubmit,
} = props;
return (
<form onSubmit={handleSubmit}>
<Row>
<Col md="3">
<FieldWrapper required={true}>
<Select name="searchKey" onChange={handleChange} value={values.searchKey} options={searchTypeOption} />
</FieldWrapper>
{errors.searchKey && touched.searchKey && <div>{errors.searchKey}</div>}
</Col>
<Col md="5">
<FieldWrapper>
<Input
placeholder="Search"
type="text"
onChange={handleChange}
value={values.searchValue}
name="searchValue"
/>
</FieldWrapper>
{errors.searchValue && touched.searchValue && <div>{errors.searchValue}</div>}
</Col>
</Row>
<Row>
<ButtonGroup>
<Button>Clear</Button>
<Button type="submit">Search</Button>
</ButtonGroup>
</Row>
</form>
);
};
export const Search = withFormik({
mapPropsToValues: () => ({ searchKey: '', searchValue: '' }),
// Custom sync validation
validate: values => {
let errors = {};
//if (values.hasOwnProperty('searchKey') && !values.searchKey) {
// errors.searchKey = 'Required';
//}
return errors;
},
handleSubmit: (values, { props, setSubmitting }) => {
const payload = {
searchKey: values.searchKey,
searchValue: values.searchValue
};
// NOTE: obj.props is empty.....
console.log(obj);
// How to use dispatch here or some way to fire event
dispatch({ type: 'SEARCH_DOCS', payload: payload });
},
})(SearchForm);
in handleSubmit, how do I dispatch an event, so saga and redux are able to receive them?
In order to do that you must pass a connected component so you can have access to dispatch
wrap this with formik like you do
const SearchFormFormik = withFormik(SearchForm)
Then connect it to redux
const mapDispatchToProps = {
searchDocFun,
};
const ConnectedSearchForm = connect(
null,
mapDispatchToProps
)(SearchFormFormik);
Then you can use the searchDocFun on handle submit
handleSubmit: (values, { props, setSubmitting }) => {
props.searchDocFun(values)
}

Resources