I have a registration form using the formik library.
I want to implement this:
If submitting the form, the submit button and input fields should be disabled.
To achieve that, I did as follows:
destructured isSubmitting and setSubmitting,
setSubmitting(true) in onSubmit,
disabled: isSubmitting within inputProps of FFInput Component, and
I also wrote disabled={isSubmitting} in submit button.
But still disabled don't work.
How to fix this problem?
Code in codesandbox:
https://codesandbox.io/s/quiet-sea-zkix7
For some reason another error was added in the sandbox, it is not present in the text editor
Note: I've commented within the code (below) to highlight these are relevant changes.
const FForm = () => {
const {
// ...
handleSubmit, handleChange, isSubmitting, setSubmitting // destructured here:
} = useFormik({
initialValues: { username: '', email: '', password: '', confirm_password: '' },
validateOnBlur: false,
validateOnchange: false,
validationSchema: yup.object().shape({...}),
onSubmit: async (formValues) => {
console.log('submit', formValues);
setSubmitting(true) //disabled
try {
const res = api('posts', { method:'POST', body: JSON.stringify(formValues) });
console.log('Result!',res);
} catch(e) {
console.error(e);
} finally {
setSubmitting(false);
}
},
});
return (
<form className="fform" onSubmit={handleSubmit}>
<FFInput
label="username"
id="username"
inputProps={{
//...
disabled: isSubmitting, //disabled
}}
error={errors.username}
/>
<FFInput
label="email"
id="email"
inputProps={{
// ...
disabled: isSubmitting, //disabled
}}
error={errors.email}
/>
<FFInput
label="password"
id="password"
inputProps={{
// ...
disabled: isSubmitting, //disabled
}}
error={errors.password}
/>
<FFInput
label="Confirm password"
id="confirm_password"
inputProps={{
// ...
disabled: isSubmitting, //disabled
}}
error={errors.confirm_password}
/>
<button type="submit" disabled={isSubmitting}> // disabled
Submit Form
</button>
</form>
);
};
export default FForm;
Formik gives you a bunch of tools as second parameter of your onSubmit handler, you get setSubmitting with that and then use that setSubmitting function to control the submitting state like this:
onSubmit: async (formValues, { setSubmitting }) => {
console.log('submit', formValues);
setSubmitting(true) //disabled
try {
const res = api('posts', { method:'POST', body: JSON.stringify(formValues)});
console.log('Result!',res);
} catch(e) {
console.error(e);
} finally {
setSubmitting(false);
}
}
Related
I am using react-hooks and yup for my contact form. I have added my yup schema as expected and error boundaries but they don't trigger when I try to submit it with empty fields. The empty form goes straight to the database which I don't want. Why is it not triggering?
My contact form code is as follows:
import { useForm } from "react-hook-form";
import * as yup from "yup";
import { yupResolver } from "#hookform/resolvers/yup";
import { ErrorMessage } from "#hookform/error-message";
const schema = yup.object().shape({
fullName: yup.string().required("Please enter your full name"),
email: yup.string().required("Please enter your email"),
select: yup
.string()
.oneOf(["webSolutions", "mobileSolutions", "devOps", "research", "uiux"])
.required("Please choose one of our services"),
message: yup.string().required("Message can not be left blank"),
});
const Contact = () => {
const [fullName, setFullName] = useState("");
const [email, setEmail] = useState("");
const [message, setMessage] = useState("");
const [selecttype , setSelectType ] = useState([]);
const {
formState: { errors },
} = useForm({
resolver: yupResolver(schema),
});
const handleSubmit = (e) => {
e.preventDefault();
db.collection("contacts")
.add({
fullName: fullName,
email: email,
selecttype : selecttype ,
message: message,
})
.then(() => {
alert("message has been submitted");
})
.catch((error) => {
alert(error.message);
});
setFullName("");
setEmail("");
setMessage("");
setSelectType("");
};
return (
<>
<Container>
<FormBg>
<ImageBg>
<Img src={Image} />
</ImageBg>
</FormBg>
<FormWrap>
<FormContent>
<Form onSubmit={handleSubmit}>
<Icon to="/">
<img src={dLogo} />
</Icon>
<FormH1>Fill in your request details below</FormH1>
<FormLabel value="fullName">
Full Name <RequiredTag>*</RequiredTag>
</FormLabel>
<FormInput
type="text"
name="fullName"
onChange={(e) => setFullName(e.target.value)}
/>
<ErrorTag>
<ErrorMessage errors={errors} name="fullName" />
</ErrorTag>
<FormLabel value="email">
Email <RequiredTag>*</RequiredTag>
</FormLabel>
<FormInput
type="email"
name="email"
placeholder="example#email.com"
onChange={(e) => setEmail(e.target.value)}
/>
<ErrorTag>
<ErrorMessage errors={errors} name="email" />
</ErrorTag>
<FormLabel value="services">
What would you wants to do for you?
<RequiredTag>*</RequiredTag>
</FormLabel>
<select
onChange={(e) => {
const selectedOption = e.target.value;
setSelectType (selectedOption);
console.log(selectedOption);
}}
>
<option>Select ...</option>
<option value="webSolutions">Web Solutions</option>
<option value="mobileSolutions">Mobile Solutions</option>
<option value="devOps">DevOps Solutions</option>
<option value="research">Research Assistance</option>
<option value="uiux">UI/UX Design</option>
</select>
<ErrorTag>
<ErrorMessage errors={errors} name="select" />
</ErrorTag>
<FormLabel value="message">
Message <RequiredTag>*</RequiredTag>
</FormLabel>
<textarea
name="message"
placeholder="Tell us more about your request like bugdets, questions and more"
onChange={(e) => setMessage(e.target.value)}
/>
<ErrorTag>
<ErrorMessage errors={errors} name="message" />
</ErrorTag>
<FormButton type="submit">Submit Request</FormButton>
<Text>We respond within 48 hours😉</Text>
</Form>
</FormContent>
</FormWrap>
</Container>
</>
);
};
export default Contact;```
You can also set .required() on your yup.object().shape({...}) which will make sure that the object will not be empty when validating.
It will look like this:
const schema = yup.object().shape({
// Your shape fields
}).required(); // -> here is where you add it
Edit
You are also not using React Hook Form's submit wrapper, this is probably why the form gets submit even though there are errors.
const { handleSubmit } = useForm();
const yourSubmitHandler = () => {
// ...
};
return (
<Form onSubmit={handleSubmit(yourSubmitHandler)}>
{/* Rest of the form */}
</Form>
);
I found the answer. Since I was using react-hooks useForm and yup to validate my form, I had to use the data captured in the form by passing it to the onSubmit function to my firebase db like this:
const onSubmit = (data) => {
db.collection("contacts")
.add({
fullName: data.fullName,
email: data.email,
select: data.select,
select2: data.select2,
message: data.message,
})
.then(() => {
alert("message has been submitted");
})
.catch((error) => {
alert(error.message);
});
};
This works perfectly and I hope it does for anyone else who might be using react-hook useForm and yup for validation.
I have a problem in the input validation. The validation works when I submit and a error message appears, but when I press the first key on the keyboard nothing appears in the textarea and the error message disappears; after that, I can write normally. Its an inconvenience and I don't know why its happening. I am using the TextArea from Material UI. The code snippet of a login form is below.
const schema = yup.object().shape({
username: yup.string().matches(/^[a-z0-9]+$/, 'Must be all lower-case letters.').required(),
password: yup.string().required(),
})
const Login = props => {
const [formValues, setFormValues] = React.useState({
username: "",
password: ""
});
const { register,errors, handleSubmit } = useForm({
resolver: yupResolver(schema),
mode: 'onSubmit',
});
const onSubmit = async (data, e) => {
e.preventDefault()
const isValid = await schema.isValid(data)
if(isValid){
console.log(data);
}
}
return (
<Container component="main" maxWidth="xs">
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<TextField
autoFocus
required
fullWidth
id="username"
label="Username"
name="username"
value={formValues.username}
inputRef={register}
helperText = {errors.username?.message}
/>
<TextField
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
value={formValues.password}
inputRef={register}
helperText = {errors.password?.message}
/>
<Button
type="submit"
fullWidth
className={classes.submit}
>
Login
</Button>
</form>
</div>
</Container>
);
}
I worked around this (in React Native) by using reValidateMode:"onBlur" in the useForm() options.
This way it doesn't retry the validation until the user leaves the input.
If the mutation is successful, I am trying to setAdded to true in the .then of ```submitForm()``. If this is true, I want to show a message from the SuccessfulMessage(). However, when I log the value of added, I keep seeing false.
Since addedis not changed to true. I am unable to see any message when mutation is successful. Why doesn't it change?
export default function AddUserPage() {
const [state, setState] = useState({
firstName: '',
lastName: '',
email: '',
password: '',
phoneNumber:'',
loggedIn: false,
});
const [added, setAdded] = useState(false);
function SuccessMessage(){
if (added)
{
console.log('User Added');
return (
<Typography>
User Added
</Typography>)
}
}
useEffect(() => {
if(added){
SuccessMessage();
}
},[] );
function submitForm(AddUserMutation: any) {
const { firstName, lastName, email, password, phoneNumber } = state;
if (firstName && lastName && email && password && phoneNumber) {
AddUserMutation({
variables: {
firstName: firstName,
lastName: lastName,
email: email,
password: password,
phoneNumber: phoneNumber,
},
}).then(({ data }: any) => {
setAdded(true);
console.log('doing', added);
console.log('ID: ', data.createUser.id);
console.log('doing', added);
})
.catch(console.log)
}
}
return (
<Mutation mutation={AddUserMutation}>
{(AddUserMutation: any) => (
<div>
<PermanentDrawerLeft></PermanentDrawerLeft>
<Formik
initialValues={{ firstName: '', lastName: '', email: '', password: '', phoneNumber: '' }}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
}}
validationSchema={schema}
>
{props => {
const {
values: { firstName, lastName, email, password, phoneNumber },
errors,
touched,
handleChange,
isValid,
setFieldTouched
} = props;
const change = (name: string, e: any) => {
e.persist();
handleChange(e);
setFieldTouched(name, true, false);
setState( prevState => ({ ...prevState, [name]: e.target.value }));
};
return (
<div className='main-content'>
<form style={{ width: '100%' }}
onSubmit={e => {e.preventDefault();
submitForm(AddUserMutation);SuccessMessage()}}>
<div>
<TextField
variant="outlined"
margin="normal"
id="firstName"
name="firstName"
helperText={touched.firstName ? errors.firstName : ""}
error={touched.firstName && Boolean(errors.firstName)}
label="First Name"
value={firstName}
onChange={change.bind(null, "firstName")}
/>
<TextField
variant="outlined"
margin="normal"
id="email"
name="email"
helperText={touched.email ? errors.email : ""}
error={touched.email && Boolean(errors.email)}
label="Email"
value={email}
onChange={change.bind(null, "email")}
/>
<Button
type="submit"
disabled={!isValid || !email || !password}
>
Add User</Button>
</div>
</form>
</div>
)
}}
</Formik>
</div>
)
}
</Mutation>
);
}
Your console.log() directly after calling setAdded will not show true as state updates are async an will only be visible on the next render. Also your SuccessMessage will never be triggered because you did not provide any dependencies for your useEffect(). This means it will only ever be called after mount
You need to add added to the dependency list:
useEffect(() => {
if(added){
SuccessMessage();
}
},[added]);
But actually I don't see any reason to trigger it in a useEffect anyways. Why not just call it in the mutation handler?
Also if you are already using hooks you can use useMutation.
Also you can't return JSX from a handler. It will not do anything. How should react even know where to display your <Typography>User Added</Typography>? You must render everything in the component itself depending on the state.
Sorry, but ...
... it looks like a great example of abusing react ... result of skipping basic tutorials, docs and mixing random oudated examples.
async nature of useState (and setState) - you can't expect updated value;
using useEffect param without knowledge how it affects behaviour;
'useEffect' not required at all;
returning components from event handler instead of [data driven] conditional rendering;
if useMutation used you don't need to pass mutation as param to event handler;
you're using Formik (with fields validation) then checking params (fields) in handler is simply unnecessary;
you can simply use variables: { ...state } if variables props names are matching;
Formik manages values, you don't need to duplicate this using local state - any reason?;
Formik has hooks, too ;) use useFormik();
... event handlers, binding...
I'm creating a new form with the help of Redux Form, following this simple example of a form validation I'm getting an error message I don't know where it's coming from:
const renderTextField = ({
input, label, meta: { touched, error }, children, type, placeholder,
}) => (
<TextField
className="material-form__field"
label={label}
type={type}
error={touched && error}
children={children}
value={input.value}
placeholder={placeholder}
onChange={(e) => {
e.preventDefault();
input.onChange(e.target.value);
}}
/>
);
renderTextField.propTypes = {
input: PropTypes.shape().isRequired,
label: PropTypes.string,
meta: PropTypes.shape({
touched: PropTypes.bool,
error: PropTypes.string,
}),
children: PropTypes.arrayOf(PropTypes.element),
type: PropTypes.string,
placeholder: PropTypes.string,
};
renderTextField.defaultProps = {
meta: null,
label: '',
children: [],
type: 'input',
placeholder: '',
};
const validate = (values) => {
const errors = {};
if (values.new_password_check !== values.new_password) {
errors.new_password = 'ERROR';
}
console.log(errors);
return errors;
};
class AccountSettings extends PureComponent {
static propTypes = {
handleSubmit: PropTypes.func.isRequired,
reset: PropTypes.func.isRequired,
};
render() {
const { handleSubmit, reset } = this.props;
return (
<form className="material-form" onSubmit={handleSubmit}>
<div>
<span className="material-form__label">Password (Optionnal)</span>
<Field
name="new_password"
component={renderTextField}
placeholder="Fill out this field only if you want to change your password"
type="password"
/>
</div>
<div>
<span className="material-form__label">Password Check (Optionnal)</span>
<Field
name="new_password_check"
component={renderTextField}
placeholder="Re-enter your new password for security reason if you wish to change it"
type="password"
/>
</div>
<div>
<span className="material-form__label">Current Password</span>
<Field
name="password"
component={renderTextField}
placeholder="Enter your current password to confirm the changes"
type="password"
/>
</div>
<ButtonToolbar className="form__button-toolbar">
<Button color="primary" type="submit">Update profile</Button>
<Button type="button" onClick={reset}>
Cancel
</Button>
</ButtonToolbar>
</form>
);
}
}
export default reduxForm({
form: 'profile_settings_form', // a unique identifier for this form
validate, // <--- validation function given to redux-form
})(AccountSettings);
When trying to validate values.new_password_check !== values.new_password whether it's true or not I always get the following error in my console:
Failed prop type: Invalid prop `error` of type `string` supplied to `ForwardRef(FormControl)`, expected `boolean`.
I do not do any propType for error, and my newly created error variable is errors. I really do not understand where this is coming from.
error needs to be a boolean not a string. To display your error message do this instead:
<TextField
className="material-form__field"
label={label}
type={type}
error={(touched && (typeof error !== 'undefined' && error != '')} // This says there is an error
helperText={touched && error} // This will show the error message
children={children}
value={input.value}
placeholder={placeholder}
onChange={(e) => {
e.preventDefault();
input.onChange(e.target.value);
}}
/>
first of all thank you so much for your time and good willing to help!
It's the first time I am trying to manage the logic of a form using the formik npm library. It's been really easy to setup and nothing is broken.
But I'm having a problem: The form is being submitted anyway and its causing me troubles because it's redirecting to the /profile and it shouldn't.
This is my form:
<Formik
initialValues={{ identifier: "sdf", password: "fgh" }}
validate={values => {
let errors = {};
// REGEX
let regex = !/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i;
if (!values.identifier) {
errors.identifier = "El correo electrónico es requerido";
} else if (regex.test(values.identifier)) {
errors.identifier = "Invalid email address";
}
if (!values.password) {
errors.password = "El email es requerido";
}
return errors;
}}
handleSubmit={(values, { setSubmitting }) => {
// trying to see what im receiving.
// I've seen this code searching around and I wanted to try
console.log(values, setSubmitting);
setTimeout(() => {
// submit them do the server. do whatever you like!
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 1000);
}}
onSubmit={values => {
const { identifier, password } = values;
this.context
.login({ identifier, password })
.then(() => {
window.location = "/profile";
this.setState({ isLoading: true, hasErrors: false });
})
.catch(err => {
console.error(err);
this.setState({ isLoading: false, hasErrors: true });
});
}}
render={({
values,
errors,
handleSubmit,
handleChange,
handleBlur
}) => (
<form onSubmit={handleSubmit} name="login">
<Label htmlFor="login__username">
{i18n.FORM.LABEL.USERNAME}
</Label>
{errors.identifier && (
<span color="red">{errors.identifier}</span>
)}
<Input
value={values.identifier}
type="text"
name="login__username"
placeholder={i18n.FORM.LABEL.USERNAME__PLACEHOLDER}
onChange={handleChange}
onBlur={handleBlur}
required
data-cy="identifier"
/>
<Label htmlFor="login__password">
{i18n.FORM.LABEL.PASSWORD}
</Label>
{errors.password && (
<span color="red">{errors.password}</span>
)}
<Input
value={values.password}
type="password"
name="login__password"
placeholder={i18n.FORM.LABEL.PASSWORD__PLACEHOLDER}
onChange={handleChange}
onBlur={handleBlur}
required
data-cy="password"
/>
<ActionsWrapper theme={theme}>
<Button type="submit" size="large" fullWidth>
{i18n.PAGE.LOGIN.BUTTON__SUBMIT}
</Button>
</ActionsWrapper>
</form>
)}
/>
And the function that handles the submit of the form is the one below:
handleOnSubmit = values => {
const { identifier, password } = values;
this.context
.login({ identifier, password })
.then(() => {
window.location = "/profile";
this.setState({ isLoading: true, hasErrors: false });
})
.catch(err => {
console.error(err);
this.setState({ isLoading: false, hasErrors: true });
});
};
I've tried to search how people deals with this but, all the examples I've found did not use anything or care about the prevent of the form.
Why is that? What I'm doing wrong? Any advice? Resources?
Thank you so much for everything!
that's not the default behavior of Forms integrated with formik.
Firstly, I think you're missing the "noValidate=true" attribute on your html form element.
Secondly, I don't think you need "handleSubmit" prop on the Formik tag. Just the onSubmit prop works.