I have a page to edit Product, props value are taken from an API and it's coming from parent component. How do I set this value to my Datepicker input? Because defaultValue from react-hook-form is cached at the first render within the custom hook, so I'm getting null.
const ProductEdit = (props) => {
const { product } = props;
const { control, register, handleSubmit, formState: { errors } } = useForm();
........
<Controller
control={control}
name='dateLaunch'
defaultValue={() => {
console.log(product); // return null
}}
render={({ field }) => (
<DatePicker
selected={field.value}
onChange={(date) => field.onChange(date)}
/>
)}
/>
}
defaultValues is cached at the first render within the custom hook
You have to use reset here and call it when product changed via useEffect. I assume, that product has a property named dateLaunch.
const ProductEdit = (props) => {
const { product } = props;
const { control, register, handleSubmit, reset, formState: { errors } } = useForm();
useEffect(() => {
reset(product)
}, [product]);
........
<Controller
control={control}
name='dateLaunch'
defaultValue={null}
render={({ field }) => (
<DatePicker
selected={field.value}
onChange={(date) => field.onChange(date)}
/>
)}
/>
you can use register
const ProductEdit = (props) => {
const { product } = props;
const { control, register, handleSubmit, formState: { errors } } = useForm({
defaultValues: {
inputOneName: props.name,
inputTwoName: props.lastname
}
});
Related
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
I am using react-native and react-hook-form with react-native-toast-notifications to display errors in my form. I want to trigger a toast.show('my custom message') if there's a particular validation error on the field, the component looks like this:
export default function RegisterScreen(props) {
// we initialize Toasts
const toast = useToast();
// loading state
const [loading, setLoading] = useState(false);
// we manage the errors
const [newError, setNewError] = useState();
// we setup the use of dispatch
const dispatch = useDispatch();
// setup of our form
const {
control,
handleSubmit,
formState: { errors },
} = useForm();
// our submit handler
const onSubmit = async data => {
setLoading(true);
setNewError(null);
console.log(data);
try {
await dispatch(authActions.signup(data.email, data.password));
} catch (err) {
toast.show(err.message, { type: 'danger', duration: 4000 });
setLoading(false);
}
};
// returning the view
return (
<>
<ScrollView>
{errors.email && toast.show('email invalid')}
<Controller
control={control}
rules={{
required: true,
}}
render={({ field: { onChange, onBlur, value } }) => (
<Input
placeholder='Solo emails vĂ¡lidos'
onBlur={onBlur}
onChangeText={onChange}
value={value}
error={errors.email}
name={t('auth:email')}
autoFocus
keyboardType='email-address'
autoComplete='email'
autoCorrect={false}
returnKeyType='next'
textContentType='emailAddress'
/>
)}
name='email'
/>
</>
);
}
By triggering the error.email I want to render a Toast, but it tells me "Error: Text strings must be rendered within a component.". I tried also {error.email && ()=> toast.show('my message') and it doesn't work also.
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
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.
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?