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.
Related
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>
)
I am beginner in Reactjs. I was building an form application using the same. There I was asked to set value of input field from the server, which can be updated by user i.e. an controlled input component.
I fetched the value in parent state then I passed the value to the child state and from there I set value of input field. Now the problem arises when I update the value in parent state then the value isn't getting updated in the child state.
See the code below -
App.jsx
import { useEffect, useState } from "react";
import { Child } from "./child";
import "./styles.css";
export default function App() {
const [details, setDetails] = useState({});
useEffect(() => {
fetch("https://reqres.in/api/users/2")
.then((res) => res.json())
.then((data) => setDetails(data));
}, []);
useEffect(() => {
console.log("data of details", details?.data);
}, [details]);
return (
<div className="App">
<h1>Testing</h1>
<Child details={details} setDetails={setDetails} val={details?.data} />
</div>
);
}
Child.jsx
import { useState } from "react";
export const Child = ({ details, setDetails, val }) => {
const [value, setValue] = useState({
save: true,
...val
});
const handleChange = (e) => {
setValue({ ...value, email: e.target.value });
};
const handleSave = () => {
setDetails({
...details,
data: { ...details.data, email: value.email }
});
console.log("Data",value);
};
const handleDelete = () => {
setDetails({ ...details, data: { ...details.data, email: "" } });
console.log("Data",value);
};
return (
<div className="cont">
<input type="text" value={value.email} onChange={handleChange} />
{value.save && <button onClick={handleSave}>save</button>}
<button onClick={handleDelete}>Delete</button>
</div>
);
};
Codesandbox Link:
https://codesandbox.io/s/testing-m3mc6?file=/src/child.jsx:0-801
N.B. I have googled for solution I saw one stackoverflow question also but that wasn't helpful for me as I am using functional way of react.
Any other method of accomplishing this would be appreciated.
Try this in child component:
useEffect(()=>{
setValue({
value,
...val
});
}, [val])
I have a component that uses a prop that recieves data from its parent. I need to edit the value of the prop Im not sure if its possible. I tried using state to copy
const Form = ({ details, form }) => {
useEffect(() => {
details.name = 'greg'
}, [])
}
Is it possible to do something like this? I get an error something like object is not extensible.
Assuming, details is a state variable, you can't update state that way. State is supposed to be immutable. To update state, you can do something like:
import React, {useState, useEffect} from "react";
const Parent = () => {
const [form, setForm] = useState({});
const [details, setDetails] = useState({ name: "bill" });
return (
<Form details={details} form={form} setDetails={setDetails} />
);
};
const Form = ({ details, form, setDetails }) => {
useEffect(() => {
setDetails({
...details,
name: "greg",
});
}, []);
return (
<div>Hello</div>
);
}
I have a component within a component. The parent component (Form) is expensive to render because of complex caluculations. Because of this if I hold down a key in the username input in the child component (UserForm) the page starts dropping frames (new characters don't show for a bit) because it renders the parent (which is expensive) as quickly as possible. To solve this I started storing a local instance of the username prop into state in the child component and then debouncing that state before passing it up to the parent.
The issue is when the parent component modifies the username (such as by reverting the changes) while the debounce in the child component is still in progress, the parent component's modification is overwritten by the child component's debounced output.
const UserForm = ({ value, onChange }) => {
const debouncedOnChange = useCallback(
lodash.debounce(updatedValue => {
onChange(updatedValue);
}, 500),
[onChange]
);
const [username, setUsername] = useState(value.username);
useEffect(() => {
setUsername(value.username);
}, [value.username]);
const handleUsernameOnChange = useCallback(
event => {
setUsername(event.target.value);
debouncedOnChange({
...value,
username: event.target.value,
});
},
[debouncedOnChange]
);
return (
<div>
<input value={username} onChange={handleUsernameOnChange} />
</div>
);
};
const INITIAL_STATE = {
user: {
username: 'John',
},
dog: {
name: 'Cliffard'
}
};
// <Form/> Is expensive to render because of complex calculations
const Form = () => {
const [value, setValue] = useState(INITIAL_STATE);
const [backup, setBackup] = useState(INITIAL_STATE);
return (
<form>
<UserForm
value={value.user}
onChange={updatedUserFormValue => {
setValue({
...value,
updatedUserFormValue,
});
}}
/>
<button
onClick={() => {
setValue({
...value,
user: { ...backup.user },
});
}}
>
Reset User Form
</button>
</form>
);
};
I started using the <AutoSave/> component created by Jared Palmer:
const AutoSave = ({debounceMs}) => {
const formik = useFormikContext()
const debouncedSubmit = useCallback(
debounce(formik.submitForm, debounceMs),
[formik.submitForm, debounceMs]
)
useEffect(() => debouncedSubmit, [debouncedSubmit, formik.values])
return <>{!!formik.isSubmitting && 'saving...'}</>
}
The main problem is when I enter the page, <AutoSave/> submits the form once the page is mounted, how to prevent this behavior?
Example:
<Formik onSubmit={values => callMyApi(values)} initialValues={{bla: 'bla-bla'}}>
{() => (
//...My beautiful field components...
<AutoSave debounceMs={300}/>
)}
</Formik>
Well, I didn't get a normal idea. Decided to use flag with hook usePrevious:
import {useRef} from 'react'
const usePrevious = value => {
const ref = useRef()
const prev = ref.current
ref.current = value
return prev
}
Now it looks like:
const MyForm = () => {
const [shouldUpdate, setShouldUpdate] = useState(false)
const previousShouldUpdate = usePrevious(shouldUpdate)
useEffect(() => {
setShouldUpdate(true)
return () => {setShouldUpdate(false)}
}, [])
<Formik onSubmit={values => {
if (previousShouldUpdate) {
return callMyApi(values)
}
}} initialValues={{bla: 'bla-bla'}}>
{() => (
//...My beautiful field components...
<AutoSave debounceMs={300}/>
)}
</Formik>
}
Any ideas to make it better?