Giving formik reference to custom hook - javascript

I'm trying to separate formik validation to a custom hook. The hook returns yup falidation schema, but it needs functions from formik bag to build that schema. How could I get the reference to formik in the component root, and pass it down to custom hook? The component looks like this:
export const MyView = () => {
let formikRef: FormikProps<FormData> | undefined = undefined;
const { validationSchema } = useValidationSchema(formikRef);
...
return (
<Formik
initialValues={initialValues}
onSubmit={submitForm}
enableReinitialize={true}
validateOnChange={false}
validateOnBlur={false}
validationSchema={validationSchema}
>
{(formik) => {
formikRef = formik;
return (
<>
...
</>
);
}}
</Formik>
);
}
This method of getting the ref does not work. The ref actually receives the correct value, but the custom hook only receives initial value (undefined). I also tried storing the ref to state, but that leads to infinite render loop. Additionally I tried using innerRef, but again, custom hook always receives undefined reference.
EDIT:
Managed to get it working by using useFormik and FormikProvider. Now there's a problem with reference order: formik uses validationSchema and other way around. I solved this by adding a function to custom hook:
const { validationSchema, setFormik } = useValidationSchema();
const formik = useFormik<FormData>({
initialValues: initialValues,
onSubmit: submitForm,
enableReinitialize: true,
validateOnChange: false,
validateOnBlur: false,
validationSchema: validationSchema,
});
setFormik(formik);
Is there a more elegant solution?

Ended up with
export const MyView = () => {
const { validationSchema, setFormikForValidation } = useValidationSchema();
const formik = useFormik<FormData>({
initialValues: initialValues,
onSubmit: submitForm,
enableReinitialize: true,
validateOnChange: false,
validateOnBlur: false,
validationSchema: validationSchema,
});
setFormikForValidation(formik);
return (
<FormikProvider formik={formik}>
...
</FormikProvider>
);
}

You can you useFormikContext hook. This is a hook provided by formik and you can use it in any of the children of a formik instance.
Your form:
import Formik from 'formik';
const Form = (props) => {
return (
<Formik {...props}>
<FormBody/>
</Formik>
);
}
your form body:
import {useFormikContext} from 'formik';
const FormBody = () => {
const {values, ...etc} = useFormikContext();
return (
...
);
}

Related

React-hook-form Cannot read properties of undefined (reading 'target')

Apologies in advanced, I am really struggling with React-hook-forms, any help is much appreciated.
I've managed to get my form fields prepopulated from a database, but my problem arises when trying to use custom inputs.
I have a Checkbox component like so:
import React from "react";
const Checkbox = React.forwardRef(
(
{
label,
name,
value,
onChange,
defaultChecked,
onBlur,
type,
...rest
}: any,
forwardedRef: any
) => {
const [checked, setChecked] = React.useState(defaultChecked);
React.useEffect(() => {
if (onChange) {
onChange(checked);
}
}, []);
React.useEffect(() => {
}, [checked]);
return (
<div onClick={() => setChecked(!checked)} style={{ cursor: "pointer" }}>
<label htmlFor={name}>{label}</label>
<input
type="checkbox"
value={value}
name={name}
onBlur={onBlur}
ref={forwardedRef}
checked={checked}
{...rest}
/>
[{checked ? "X" : " "}]{label}
</div>
);
}
);
export default Checkbox;
Which gets default values and applies them to the component:
import "./styles.css";
import Checkbox from "./Checkbox";
import { useForm } from "react-hook-form";
const defaultValues = {
gender: "male"
};
export default function App() {
const { handleSubmit, register } = useForm({
defaultValues: defaultValues
});
const onSubmit = async (data: any) => {
console.log(data, "data");
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Checkbox
{...register("gender")}
name={"gender"}
value={"boy"}
label={"Boy"}
// when this added, the custom checks work but everything is checked
// defaultChecked={defaultValues}
/>
</form>
);
}
But when I call the onChange function I get the following error:
Cannot read properties of undefined (reading 'target')
The behavior is working as expected when just clicking on the checkboxes, and the default state is loaded for the native inputs, but it doesn't reflect the custom checkbox UI without user interaction.
Here's a codesandbox showing my issue.
What am I doing wrong

get value when updated,outside of formik using useRef()

I'm trying to get the value of an autocomplete field outside of my formik component everytime it changes, and i did something like this:
const formRef=useRef<any>(null);
useEffect(() => {
console.log("eee!!",formRef.current?.values?.flagTR)
},[formRef.current?.values]);
return (
<Formik
initialValues={initialValues}
onSubmit={handleSumbit}
enableReinitialize={true}
validateOnChange={true}
validationSchema={ProduitSchema}
innerRef={formRef}
>
the useEffect is triggered the first time the component renders,but then when i change the value of the autocomplete the useEffect doesn't get triggered.
what should i do in order to trgger useEffect everytime a value from formik changes?
Hey so I don't think the approach you are going with here is going to work, it's really not what the useRef hook is for.
The correct approach for what it looks like you want to do is to properly use Formik's context to mapValuesToProps or get access to the values, errors, and validation states.
You can use withFormik() to set up your initial form values and mapValuesToProps. Then you can use within the form component formik's useFormikContext() which will give you access to values, errors, setValues, etc
export default withFormik({
handleSubmit: () => {},
validateOnChange: true,
validateOnBlur: true,
validateOnMount: true,
mapPropsToValues: () => ({ name: '', email: '' }),
initialValues: { name: '', email: '' }
})(MyFormComponent)
In the MyFormComponent you can then call useFormikContext() and do whatever you want when the values change.
const { setValues, values, isValid, validateForm } = useFormikContext()
If for some reason this is not what you want to do, or it does not solve your problem, the only way to achieve what you want in React alone is to useState and and setState each time onChange eg
const MyFormComponent = () => {
const [nameField, nameMeta] = useField(name)
const [emailField, emailMeta] = useField(email)
const [formState, setFormState] = useState({name: '', email: ''})
return (
<Formik
enableReinitialize
validateOnBlur={false}
initialValues={formState}
onSubmit={() => {}}
>
{props => {
const onChange = e => {
const targetEl = e.target
const fieldName = targetEl.name
setFormState({
...formState,
[fieldName]: targetEl.value
})
return props.handleChange(e)
}
return (
<>
<input {...nameField} onChange={onChange}>
<input {...emailField} onChange={onChange}>
</>
)
</Formik>
)

React hook form returning blank object on submit

Here's my simplified problem:
https://codesandbox.io/s/busy-fire-mm91r?file=/src/FormWrapper.tsx
And the code:
export const FormItemWrapper = ({ children }) => {
return <FormItem>{children}</FormItem>;
};
export const FormWrapper = ({ children }) => {
const { handleSubmit } = useForm();
const onSubmit = (data) => {
console.log(data); // logs { }
};
return <Form onFinish={handleSubmit(onSubmit)}>{children}</Form>;
};
export default function App() {
const { control, watch } = useForm();
console.log(watch()); // logs { }
return (
<FormWrapper>
<FormItemWrapper>
<Controller
control={control}
name="tmp"
render={({ field }) => <Input {...field} />}
/>
</FormItemWrapper>
<FormItemWrapper>
<Button htmlType="submit">Save</Button>
</FormItemWrapper>
</FormWrapper>
);
}
The problem:
React-hook-form doesn't seem to see the data I type in. I can get it using antd, but can't with react-hook-form. Why? What am I missing?
watch() logs only once, and it logs { }. onSubmit logs { }
You have created two different form instances with useForm call. If you want to get current form context inside Controller you should use useFormContext and wrap your form in FormProvider.
Working example:
https://codesandbox.io/s/admiring-lehmann-mgd0i?file=/src/App.tsx

Formik Initial Values out of Sync with Redux

This form is used to edit a user profile. It should load with up to date user data for the initial values. To handle this, I've added a redux fetchUser action in useEffect and set the initial values of the form to the currentUser in the redux store. fetchUser hits a database endpoint and updates the currentUser in the redux store.
However, if I submit the form and reload the page, the initial data is not updated.
I'm not sure why my initial values/form is not up to date with my redux store. The form will update with the right data if I navigate away from it and navigate back to it twice.
My assumption is that the form is not resetting the initial value each time the component is rendering. It looks like that data is persisting. I have tried passing in an object to resetForm() to force the initial values to update, but this results in the same issue. If
Here is the full react component:
import { Formik, Form, Field, ErrorMessage } from "formik"
//redux action that fetches current user
import { fetchUser } from "../../actions"
import { connect } from "react-redux"
import profileSchema from "./yupSchema"
import updateProfile from "./updateAxiosCall"
const submitData = (data, resetForm, tagNames) => {
const newProfile = { ...data}
//end point call - working as intended
updateProfile(newProfile)
resetForm()
}
const ProfileForm = ({ currentUser, fetchUser }) => {
const [formData, setFormData] = useState(null)
useEffect(() => {
//grab user on component render
fetchUser()
setFormData({
linkedin: currentUser.linkedin,
github: currentUser.github,
instagram: currentUser.instagram,
facebook: currentUser.facebook,
twitter: currentUser.twitter,
header: currentUser.header,
bio: currentUser.bio,
})
}, [])
if (formData === null) {
return <div>Not ready</div>
} else {
return (
<Formik
initialValues={formData}
validationSchema={profileSchema}
onSubmit={(values, { resetForm }) => {
submitData(values, resetForm, tagNames)
}}
>
{({ errors, resetForm, handleChange, touched, values, onSubmit }) => (
<Form
style={{
display: "flex",
flexDirection: "column",
width: "100%",
}}
autoComplete="off"
>
//This is up to date
<h1>{currentUser.header}</h1>
<TextField
error={errors.header && touched.header}
onChange={handleChange}
name="header"
variant="outlined"
value={values.header || ""}
id="header"
margin="normal"
label="header"
helperText={
errors.header && touched.header ? errors.header : null
}
/>
{/* ...additional <TextField> components */}
<Button
style={{ margin: "10px 0px" }}
size="large"
margin="normal"
type="submit"
variant="contained"
color="primary"
>
Save
</Button>
</Form>
)}
</Formik>
)
}
}
function mapStateToProps(state) {
return {
currentUser: state.currentUser,
}
}
export default connect(mapStateToProps, { fetchUser })(ProfileForm)
put enableReinitialize={true} as a Formik property.
so this:
<Formik
enableReinitialize={true}
initialValues={formData}
...
#Jared answer works for me by using enableReinitialize:true hook
const formik = useFormik({
initialValues: {
title: "",
text: "",
authorid: `${user.id}`,
cat_id: '1',
},
validateOnBlur: true,
onSubmit,
enableReinitialize: true,
});
You might need to separate your useEffect hook into 2 useEffects. One hook for the "componentDidMount" cycle (e.g. []) and another watching changes to currentUser.
Example:
const [formData, setFormData] = useState(null)
useEffect(() => {
//grab user on component render
fetchUser()
}, [])
useEffect(() => {
setFormData({
linkedin: currentUser.linkedin,
github: currentUser.github,
instagram: currentUser.instagram,
facebook: currentUser.facebook,
twitter: currentUser.twitter,
header: currentUser.header,
bio: currentUser.bio,
})
}, [ currentUser ])

Get null for innerRef of Formik

When I try to get reference on formik, I get null in current field of ref object.
const formikRef = useRef();
...
<Formik
innerRef={formikRef}
initialValues={{
title: '',
}}
onSubmit={(values) => console.log('submit')}
>
And next:
useEffect(() => {
console.log(formikRef);
}, []);
Get:
Object {
"current": Object {
"current": null,
},
}
How can I fix this problem?
Help please.
If you want to call submit function outside Formik, you can use useImperativeHandle. Document
// Children Component
const Form = forwardRef((props, ref) => {
const formik = useFormik({
initialValues,
validationSchema,
onSubmit,
...rest // other props
})
useImperativeHandle(ref, () => ({
...formik
}))
return ** Your form here **
})
and using:
// Parent Component
const Parent = () => {
const formRef = useRef(null)
const handleSubmitForm = (values, actions) => {
// handle logic submit form
}
const onSubmit = () => {
formRef.current.submitForm()
}
return (<>
<Form ref={formRef} onSubmit={handleSubmitForm} />
<button type="button" onClick={onSubmit}>Submit</button>
</>)
}
Read the ref only when it has value, and add the dependency in useEffect on the ref.
useEffect(() => {
if (formikRef.current) {
console.log(formikRef);
}
}, [formikRef]);
Remember, that refs handle it's actual value in .current property.
What worked for me was adding variables inside useEffect's [].
For my case, it was [ref.current, show].
Add an if(ref.current) {...} before any ref.current.setFieldValue in useEffect body as well or ref.current?.setFieldValue.
This cost me several hours, I hope you're better off.

Categories

Resources