react-hook-form: unregister doesnt clear component value - reactjs

i have one text field which i am manually unregistering. it is successfully getting unregister and data is excluded from formdata, however user entered value still stays in the text field. i am expecting value also get cleared from component as well. i even tried
setValue('fieldName',"")
is not working. not sure if i am doing something wrong.
so if i re register my text field and trigger validation, you will see required field validation but value is still present in text field
code below:
const App = () => {
const { register, handleSubmit, unregister, errors, setValue } = useForm();
const onSubmit = (data) => {
alert(JSON.stringify(data));
};
useEffect(() => {
register("person.firstName", { required: true });
register("person.lastName", { required: true });
// }
}, [register]);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>First Name</label>
<input
type="text"
name="person.firstName"
onChange={(e) => setValue("person.firstName", e.target.value)}
/>
{errors?.person?.firstName && <p> First name required</p>}
<label>Last Name</label>
<input
type="text"
name="person.lastName"
onChange={(e) => setValue("person.lastName", e.target.value)}
/>
{errors?.person?.lastName && <p> Last name required</p>}
<button
type="button"
onClick={() => {
setValue("person.lastName", "");
unregister("person.lastName");
}}
>
unregister lastName
</button>
<input type="submit" />
</form>
);
};
here is my CSB
i would appreciate any help

the error occurs because you apply your register at useEffect:
useEffect(() => {
register("person.firstName", { required: true });
register("person.lastName", { required: true });
// }
}, [register]);
instead, if you apply to ref at your input fields setValue("person.lastName", "") will clear the field as expected:
<input
ref={register({ required: true })}
type="text"
name="person.firstName"
onChange={(e) => setValue("person.firstName", e.target.value)}
/>

Related

Formik use debounce on change input

I have this simple example using useDebouncedCallback from use-debounce. When i write to input it remains the same with no value. What iam doing wrong?
const SignupForm = () => {
const formik = useFormik({
initialValues: {
firstName: "",
},
});
const debounced = useDebouncedCallback(
// function
(event) => {
formik.handleChange(event);
},
// delay in ms
1000
);
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
name="firstName"
type="text"
onBlur={formik.handleBlur}
onChange={(e) => {
e.persist();
debounced(e);
}}
value={formik.values.firstName}
/>
<button type="submit">Submit</button>
</form>
);
};
It's not working because you have a controlled component (due to value={formik.values.firstName}).
When formik.handleChange is called (in an debounced way), e.target.value is empty because React keeps the input field in sync with formik.values.firstName which remains empty (its initial value is firstName: "").
To make it work, you can use the formik setFieldValue and pass the input name and value to the debounced function like this :
const debounced = useDebouncedCallback(
(field, value) => formik.setFieldValue(field, value),
1000
);
...
<input
id="firstName"
name="firstName"
type="text"
onBlur={formik.handleBlur}
onChange={(e) => {
const { name, value } = e.target;
debounced(name, value);
}}
value={formik.values.firstName}
/>
Here is a stackblitz example

Why I can't fill anything in my form in React?

So I have a form, and I need users to fill this form and when they send a message, it should come to my gmail. I use EmailJS service for this.
So my form looks like this:
So as you see, users can send me messages, and my code looks like this:
Usestate for sending data:
const [toSend, setToSend] = useState({
from_name: '',
to_name: '',
message: '',
reply_to: '',
subject: ''
});
onSubmit function:
const onSubmit = (e) => {
e.preventDefault();
send(
'service_id',
'template_id',
toSend,
'user_id'
)
.then((response) => {
console.log('SUCCESS!', response.status, response.text);
})
.catch((err) => {
console.log('FAILED...', err);
});
reset();
};
handleChange function:
const handleChange = (e) => {
setToSend({ ...toSend, [e.target.name]: e.target.value });
};
useform hook:
const {register, handleSubmit, formState: { errors }, reset, trigger} = useForm();
Whole form:
<form onSubmit={handleSubmit(onSubmit)}>
<input type="text"
placeholder="Name"
name="from_name"
value={toSend.from_name}
onChange={handleChange}
id="name" {...register('name', { required: "Name is required" })}
onKeyUp={() => {
trigger("name");
}}/>
{errors.name && (<span className="danger_text">{errors.name.message}</span>)}
<input type="text"
placeholder="Email"
name="reply_to"
value={toSend.reply_to}
onChange={handleChange}
id="email" {...register("email", { required: "Email is Required" ,
pattern: {
value: /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: "Invalid email address",
}})}
onKeyUp={() => {
trigger("email");
}}
/>
{errors.email && (
<small className="danger_text">{errors.email.message}</small>
)}
<input type="text"
placeholder=
"Subject"
id="subj"
name="subject"
value={toSend.subject}
onChange={handleChange}/>
<input type="text"
placeholder="Message"
id="msg"
name="message"
value={toSend.message}
onChange={handleChange}
{...register('msg', { required: true })}/>
{errors.msg && (<small className="danger_text">Enter your message</small>)}
<input type="submit" className="btn_red" value="Send a message"></input>
</form>
So my problem is that I can't fill anything in inputs. When I try to type something it just doesn't type in, I'm guessing it has something to do with "value=.." in all inputs, but I'm not sure what's exactly the problem here.
You don't need to define onChnage, value, onKeyUp on your inputs, when you call register on input, it returns onChange,onBlur,ref, then react-hook-forms will control the values by ref. so below example should solve your problem:
import { useForm } from 'react-hook-form';
...
function MyComp() {
const {
register,
handleSubmit,
formState: { errors },
reset,
} = useForm();
const onSubmit = (toSend) => {
send(
'service_id',
'template_id',
toSend,
'user_id'
)
.then((response) => {
console.log('SUCCESS!', response.status, response.text);
})
.catch((err) => {
console.log('FAILED...', err);
});
reset();
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
type="text"
placeholder="Name"
name="from_name"
id="name"
{...register('from_name', { required: 'Name is required' })}
/>
{errors.from_name && (
<span className="danger_text">{errors.from_name.message}</span>
)}
<input
type="text"
placeholder="Email"
name="reply_to"
id="email"
{...register('reply_to', {
required: 'Email is Required',
pattern: {
value: /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Invalid email address',
},
})}
/>
{errors.reply_to && (
<small className="danger_text">{errors.reply_to.message}</small>
)}
<input
type="text"
placeholder="Subject"
id="subj"
name="subject"
{...register('subject')}
/>
<input
type="text"
placeholder="Message"
id="msg"
name="message"
{...register('message', { required: true })}
/>
{errors.message && <small className="danger_text">Enter your message</small>}
<input type="submit" className="btn_red" value="Send a message"></input>
</form>
);
}
BTW, consider that register works with input's name, not id.

Antd form doesn't identify input values

I have created my react form with antd. I have added antd validation for the form. But my form doesn't know whether I have filled the form or not. Whenever I filled the form and submitted it, it doesn't call onFinish method. Instead it fails and calls onFinishFailed method and gives me validation error messages.
I have created it in correct way according to my knowledge. But there is something missing I think. Here's my code.
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const history = useHistory();
const [form] = Form.useForm();
const layout = {
labelCol: { span: 4 },
wrapperCol: { span: 8 },
};
const onChangeName = (e) => {
setName(e.target.value);
console.log(name);
}
const onAddCategory = (values) => {
let req = {
"name": values.name,
"description": values.description
}
postCategory(req).then((response) => {
if (response.status === 201) {
message.success('Category created successfully');
history.push('/categorylist');
}
}).catch((error) => {
console.log(error);
message.error('Oops, error occured while adding category. Please try again');
});
}
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
console.log('State:', name, description);
};
return (
<React.Fragment>
<Form
form={form}
name="control-hooks"
onFinish={onAddCategory}
onFinishFailed={onFinishFailed}
{...layout}
size="large"
>
<Form.Item
name="name"
rules={[
{
required: true,
message: 'You can’t keep this as empty'
}, {
max: 100,
message: 'The category name is too lengthy.',
}
]}
>
<label>Category name</label>
<Input
placeholder="Category name"
className="form-control"
value={name}
onChange={onChangeName}
/>
</Form.Item>
<Form.Item
name="description"
rules={[
{
required: true,
message: 'You can’t keep this as empty'
}, {
max: 250,
message: 'The description is too lengthy',
}
]}
>
<label>Description</label>
<Input.TextArea
placeholder="Description"
className="form-control"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</Form.Item>
<Form.Item shouldUpdate={true}>
<Button
type="primary"
htmlType="submit"
className="btn btn-primary"
>
Add category
</Button>
</Form.Item>
</Form>
</React.Fragment>
)
In this form I have managed state using hooks. In onFinishFailed method I have logged my input values with state and they have values. But form doesn't identify it.
How do I resolve this. Please help.
I found the issue. Here I had added label inside form item. It was the reason for the unexpected behavior. Once I took the label outside the form item problem was solved.
<label>Category name</label>
<Form.Item
name="name"
rules={[
{
required: true,
message: 'You can’t keep this as empty'
}, {
max: 100,
message: 'The category name is too lengthy.',
}
]}
>
<Input
placeholder="Category name"
className="form-control"
value={name}
onChange={onChangeName}
/>
</Form.Item>

Only one form submitting correctly with react hooks

I have a main App component that has a form and another component that also renders a separate form. I want the second form to submit correctly using its separate handleSecondSubmit function, but it does not seem to use it.
Here's the full code and a StackBlitz: Stackblitz Demo
import React from "react";
import { useForm } from "react-hook-form";
const SecondForm = () => {
const { handleSubmit, register } = useForm();
const handleSecondSubmit = e => {
e.preventDefault();
console.log("2nd form");
};
return (
<form onSubmit={e => handleSubmit(handleSecondSubmit)}>
<input name="test" type="text" ref={register({ required: true })} />
<input type="submit" />
</form>
);
};
const App = () => {
const { register, handleSubmit, watch, errors } = useForm();
const onSubmit = data => console.log(data);
console.log(watch("example"));
return (
<>
<form onSubmit={handleSubmit(onSubmit)}>
<input name="example" defaultValue="test" ref={register} />
<input name="exampleRequired" ref={register({ required: true })} />
{errors.exampleRequired && <span>This field is required</span>}
<input type="submit" />
</form>
<SecondForm />
</>
);
};
export default App;
Can someone explain what's happening here? Thank you
You need to call the handleSubmit directly when applying the handler, and the signature for the returned function has the event as the second argument (the first being the data)
const SecondForm = () => {
const { handleSubmit, register } = useForm();
const handleSecondSubmit = (data, e) => {
e.preventDefault();
console.log("2nd form", data);
};
return (
<form onSubmit={handleSubmit(handleSecondSubmit)}>
<input name="test" type="text" ref={register({ required: true })} />
<input type="submit" />
</form>
);
};
https://stackblitz.com/edit/react-v7cdfh
try this :
return (
<form onSubmit={e => handleSubmit(handleSecondSubmit(e))}>
<input name="test" type="text" ref={register({ required: true })} />
<input type="submit" />
</form>
);
};
you forgot to pass e to handleSecondSubmit(e)
Seems you need to pass the onSubmit event object through to your second form's handler.
onSubmit={e => handleSubmit(handleSecondSubmit(e))}
https://stackblitz.com/edit/react-fduvdv

Conditional validation with react hook form

Here is my form looks like and also CodeSanbox. currently I'm using react-hook-form
as you can see form has 3 inputs. Submit button should be disabled until all the required fields are entered.
Two use case:
If "Check" is unchecked:
only "id" should be validated and submit button should get enabled. "firt" and "last" names should not be part of form data
If "Check" is checked
all the fields should be validated
first and last names are only required if "Check" is checked. so its not checked then form should only validate "ID" field. if "Check" is checked then all fields should get validated.
problem I'm having is if I enter id, form state is still "invalid". Form is expecting to enter values for first and last name.
I would appreciate any help.
I have updated your CodeSanBox code and also adding the full code here:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { useForm } from "react-hook-form";
import "./index.css";
function App() {
const {
register,
handleSubmit,
errors,
formState,
unregister,
setValue,
getValues,
reset
} = useForm({
mode: "onBlur",
reValidateMode: "onBlur",
shouldUnregister: true
});
//console.log(formState.isValid);
console.log(errors);
const [disabled, setDisabled] = useState(true);
const onSubmit = (data) => {
alert(JSON.stringify(data));
};
useEffect(() => {
// #ts-ignore
if (disabled) {
console.log("unregister");
reset({ ...getValues(), firstName: undefined, lastName: undefined });
unregister(["firstName", "lastName"]);
} else {
console.log("register");
register("firstName", { required: true });
register("lastName", { required: true });
}
}, [disabled]);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="id">ID</label>
<input
name="id"
placeholder="id"
ref={register({ required: true, maxLength: 50 })}
/>
{errors.id && <p>"ID is required"</p>}
<fieldset disabled={disabled}>
<legend>
<input
type="checkbox"
name={"name"}
ref={register}
onClick={() => setDisabled(!disabled)}
/>
<span>Check</span>
</legend>
<label htmlFor="firstName">First Name</label>
<input
name="firstName"
placeholder="Bill"
onChange={(e) => {
console.log(e.target.value);
setValue("firstName", e.target.value);
}}
ref={register({ required: !disabled })}
/>
{errors.firstName && <p>"First name is required"</p>}
<label htmlFor="lastName">Last Name</label>
<input
name="lastName"
placeholder="Luo"
onChange={(e) => setValue("lastName", e.target.value)}
ref={register({ required: !disabled })}
/>
{errors.lastName && <p>"Last name is required"</p>}
</fieldset>
<input type="submit" disabled={!formState.isValid} />
</form>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
First I found that you set disabled state as false which should be true as an initial value, and regarding the issue, I have used reset and getValues functions when the disabled state changes.
EDIT for you to recognize code changes easy, I have restored all the code at CodeSanBox.
This whole validation behavior (UX) is definitely making things a bit harder, however, there are a couple of things that you should leverage from the library such as:
watch
validate
getValues
import React from "react";
import ReactDOM from "react-dom";
import { useForm } from "react-hook-form";
import "./index.css";
function App() {
const {
register,
handleSubmit,
errors,
formState: { isValid, touched },
getValues,
trigger,
watch
} = useForm({
mode: "onBlur"
});
const onSubmit = (data) => {
alert(JSON.stringify(data));
};
const validate = (value) => {
if (getValues("name")) { // read the checkbox value
return !!value;
}
return true;
};
const isChecked = watch("name"); // watch if the name is checked
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="id">ID</label>
<input
name="id"
placeholder="id"
ref={register({ required: true, maxLength: 50 })}
/>
{errors.id && <p>"ID is required"</p>}
<fieldset disabled={!isChecked}>
<legend>
<input
type="checkbox"
name={"name"}
ref={register}
onChange={() => trigger()} // you want update isValid due to state change, and also those extra two inputs become required
/>
<span>Check</span>
</legend>
<label htmlFor="firstName">First Name</label>
<input
name="firstName"
placeholder="Bill"
ref={register({
validate
})}
/>
// make sure input is touched before fire an error message to the user
{errors.firstName && touched["firstName"] && (
<p>"First name is required"</p>
)}
<label htmlFor="lastName">Last Name</label>
<input
name="lastName"
placeholder="Luo"
ref={register({
validate
})}
/>
{errors.lastName && touched["lastName"] && (
<p>"Last name is required"</p>
)}
</fieldset>
<input type="submit" disabled={!isValid} />
</form>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
CSB:
https://codesandbox.io/s/react-hook-form-conditional-fields-forked-n0jig?file=/src/index.js:0-1831
on your ref, dont use hard coded bool true, ref={register({ required: true})}, but your dynamic ref={register({ required: disabled })}
do notice that because your mode: "onBlur" config, the button won't be abled until id field blurred
You just need to replace true .from ref: required:true..... Instead use const 'disabled' ....in input of first and last name .
So as to achieve dynamic change

Resources