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
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} />;
}
Can't understand how to validate and render error massages in my FormInput component (below of input tag).
Advice please.
No understanding of error output.`
SmartForm
import React from "react";
import { useForm, get } from "react-hook-form";
export default function SmartForm({
defaultValues,
register,
children,
...rest
}) {
const props = { ...rest };
const methods = useForm(defaultValues);
const {
handleSubmit,
formState: { errors },
} = methods;
return (
<>
<form onSubmit={handleSubmit}>
{React.Children.map(children, (child) => {
return child.props.name
? React.createElement(child.type, {
...{
...child.props,
register: methods.register,
key: child.props.name,
},
})
: child;
})}
{Object.values(errors)}
<input type="submit"></input>
</form>
</>
);
}
`
FormInput
import React from "react";
export default function FormInput({ register, rules, name, error, ...rest }) {
return (
<>
<input
{...register(
name,
rules
)}
{...rest}
className={styles.input}
/>
{error?.name && <div className={styles.error}>{error.name.message}</div>}
</>
);
}
LoginForm
import React from "react";
import SmartForm from "../SmartModalForm/smartModalForm";
import FormInput from "./FormInput/formInput";
export default function LoginForm(props) {
const onSubmit = (e) => {
e.preventDefault();
console.log(e);
};
return (
<SmartForm
handleClose={handleClose}
handleSubmit={onSubmit}
>
<FormInput
name="email"
rules={{ required: "This is required." }}
/>
<FormInput
name="password"
rules={{ required: "This is required." }}
/>
</SmartForm>
);
}
import React from "react";
export default function FormInput({ register, rules, name, error, ...rest }) {
return (
<>
<input
{...register(
name,
rules
)}
{...rest}
className={styles.input}
/>
{error?.name && <div className={styles.error}>{error.name.message}</div>}
</>
);
}
In SmartFormComponent you are just passing register from useFormReturn and not all methods. So, in FormInput, error is undefined.
Update React.createElement in SmartFormComponent as below
React.createElement(child.type, {
...{
...child.props,
methods, // not just register, but pass entire `useFormReturn` values
key: child.props.name,
},
})
Getting the error in browser Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()
My code:
import { yupResolver } from '#hookform/resolvers/yup'
import { useState } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form'
import { contactSchema } from 'schemas/schemas'
import { InputFloatLabel } from './components/Inputs/InputFloatLabel'
type TypeFormInput = {
name: string
email: string
textarea: string
}
export const Register = () => {
const [isLoading, setIsLoading] = useState(false)
const {
register,
handleSubmit,
formState: { errors },
} = useForm<TypeFormInput>({ resolver: yupResolver(contactSchema) })
const onSubmit: SubmitHandler<TypeFormInput> = async ({ name, email }) => {
console.log('🚀 ~ file: Register.tsx ~ line 25 ~ email', email)
console.log('🚀 ~ file: Register.tsx ~ line 25 ~ name', name)
}
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<InputFloatLabel
type="text"
placeholder="Name"
{...register('name')}
/>
<button type="submit">{isLoading ? 'Loading' : 'Send Mail'}</button>
</div>
</form>
</div>
)
}
And the Input comp:
import { useState } from 'react'
type typeInput = {
placeholder: string
type?: string
}
export const InputFloatLabel: React.FC<typeInput> = ({ type, placeholder, ...props }) => {
const [isActive, setIsActive] = useState(false)
const handleTextChange = (text: string) => {
if (text !== '') setIsActive(true)
else setIsActive(false)
}
return (
<div>
<input
{...props}
id={placeholder}
type={placeholder ? placeholder : 'text'}
onChange={(e) => handleTextChange(e.target.value)}
/>
<label htmlFor={placeholder}>
{placeholder ? placeholder : 'Placeholder'}
</label>
</div>
)
}
I don't have this issue with ChakraUI that I've built but now just doing plain input as a separate component getting that issue.
I have tried some suggestions from here, but still can't fix it: https://github.com/react-hook-form/react-hook-form/issues/85
So the issue is that I think that the {...register("name"}} line actually includes a ref property. You could console.log that out to verify; this is what I found to be true when using {...field} with the ControlledComponent. A very quick fix to get rid of the console error is to just, after the line with the spread, to add a ref={null} to override this ref that is being passed in from the library.
You forgot to forward the ref in your InputFloatLabel. See https://reactjs.org/docs/forwarding-refs.html
In your case it would look like this:
export const InputFloatLabel: React.FC<typeInput> =
// Use React.forwardRef
React.forwardRef(({type, placeholder, ...props}, ref) => {
const [isActive, setIsActive] = useState(false)
const handleTextChange = (text: string) => {
if (text !== '') setIsActive(true)
else setIsActive(false)
}
return (
<div>
<input
ref={ref /* Pass ref */}
{...props}
id={placeholder}
type={placeholder ? placeholder : 'text'}
onChange={(e) => handleTextChange(e.target.value)}
/>
<label htmlFor={placeholder}>
{placeholder ? placeholder : 'Placeholder'}
</label>
</div>
)
})
In https://react-hook-form.com/faqs, scroll to "How to share ref usage?" may help?
import React, { useRef } from "react";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit } = useForm();
const firstNameRef = useRef(null);
const onSubmit = data => console.log(data);
const { ref, ...rest } = register('firstName');
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...rest} name="firstName" ref={(e) => {
ref(e)
firstNameRef.current = e // you can still assign to ref
}} />
<button>Submit</button>
</form>
);
}
the field element and register function pass a ref to the element. If you define a custom React Component and try to use it within a controller or with register, funky things can happen. I found using React.forwardRef() solves the problem when using Custom Components.
CustomSwitchComponent.tsx
import React from 'react';
import {FormControlLabel, Switch, SxProps, Theme} from "#mui/material";
const TradingStrategyEditFormInstructionsInputSwitch:
// Note this type allows us to do React.forwardRef(Component)
React.ForwardRefRenderFunction<HTMLButtonElement, {
label: string,
checked: boolean,
onBlur: React.FocusEventHandler<HTMLButtonElement>
}> = ({label, ...rest}) => {
// Spreading ...rest here will pass the ref :)
return <FormControlLabel control={<Switch {...rest} />}
labelPlacement={"top"}
label={label}
/>;
};
// *Huzzah!*
export default React.forwardRef(TradingStrategyEditFormInstructionsInputSwitch);
CustomSwitchController.tsx
<Controller
control={formCtx.control}
name={fieldPath}
key={type + side + key}
render={({field}) => {
return <TradingStrategyEditFormInstructionsInputField
{...field}
label={key}
checked={field.value}
onBlur={() => {
field.onBlur()
handleOnBlur(key)
}}
/>
}}/>
Idk if we're allowed to link YT Videos, but techsith has a great video on using forwardRef with useRef that should clear things up.
https://www.youtube.com/watch?v=ScT4ElKd6eo
Your input component does not export ref as props since it is a functional component.
React.useEffect(() => {
register('name', { required: true });
}, []);
<InputFloatLabel
type="text"
placeholder="Name"
name="name"
// Remove the register from here
/>
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 am trying to send my dob data from my Main class to a child component (RegisterAccount.jsx) and validate it at child component using yup and withFormik Field. The problem is that:
I could not make yup or formik/yup to validate dob field.
DatePicker doesn't show selected value inside the textbox after selected.
Please check my below code:
Here is my Main.jsx class
// Some imports were removed to keep everything looks cleaner
import RegisterAccount from "RegisterAccount.jsx";
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
dob: ""
}
}
render() {
return (
<Container fluid>
<Switch>
<Route exact path="/" render={() => <RegisterAccount data={this.state.dob} />} />
</Switch>
</Container>
)
}
}
export default Main;
Here is my RegisterAccount.jsx
// Some imports were removed to keep everything looks cleaner
import { Form as FormikForm, Field, withFormik } from "formik";
import * as Yup from "yup";
import DatePicker from "react-datepicker";
const App = ({ props }) => (
<FormikForm className="register-form " action="">
<h3 className="register-title">Register</h3>
<Form.Group className="register-form-group">
<DatePicker
tag={Field}
selected={props.data.dob}
onChange={(e, val) => {
console.log(this);
this.value=e;
props.data.dob = e;
console.log(props.data.dob);
}}
value="01-01-2019"
className="w-100"
placeholderText="BIRTH DATE"
name="dob" />
{touched.username && errors.username && <p>{errors.username}</p>}
</Form.Group>
</FormikForm>
);
const FormikApp = withFormik({
mapPropsToValues({ data }) {
return {
dob: data.dob || ""
};
},
validationSchema: Yup.object().shape({
dob: Yup.date()
.max(new Date())
})(App);
export default FormikApp;
Use setFieldValue method from formik's injected props.
Define it on onChange handler for your inputsetFieldValue('dob','Your Value').
You can access it from
const MyForm = props => {
const {
values,
touched,
errors,
handleChange,
handleBlur,
handleSubmit,
setFieldValue
} = props;
return (
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={e => {
setFieldValue("name", "Your value"); // Access it from props
}}
onBlur={handleBlur}
value={values.name}
name="name"
/>
</form>
);
};
const MyEnhancedForm = withFormik({
// your code
})(MyForm)
Reference - https://jaredpalmer.com/formik/docs/api/formik#setfieldvalue-field-string-value-any-shouldvalidate-boolean-void