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.
Related
I have a question regarding react-final form error message when using record-level validation. I have the following field present within FormFilterFields component.
Field Names:
export const fieldNames = {
password: "field.password"
};
Field:
<Field name={fieldNames.password}>
{({ input, meta }) => {
return (
<div className={styles.textInputContainer}>
<TextInput
type={"password"}
label={"password"}
value={input.value}
onChange={(event)=> {
input.onChange(event)
}}
error={meta.error}
/>
</div>
);
}}
</Field>
Form:
<Form
onSubmit={() => {}}
initialValues={this.props.formValues}
decorators={[formFilterFieldsDecorator]}
validate={values => {
const valuesObject: any = values
const validationErrors = { errors: {} };
if (valuesObject && valuesObject.field.password && valuesObject.field.password.length < 6){
validationErrors.errors.field.password = "password is too short"; //errors.field.password is undefined here
}
return validationErrors;
}}
render={({
handleSubmit,
submitting,
pristine,
values,
form,
invalid
}) => (
<form onSubmit={handleSubmit}>
<FormFilterFields
form={form}
onSubmit={event => this.props.onHandleSubmit(event, values)}
onReset={event => this.props.onHandleReset(event, form)}
/>
<pre>{JSON.stringify(values)}</pre>
</form>
)}
/>
So essentially how would you set the error message for a field name like so:
"field.password"
I have looked at some of the examples but no dice.The alternative would be field level validation which is my last resort as part of my solution.
Any help would be much appreciated
Thanks
Did you try
validationErrors.errors['field.password'] = "password is too short";
Edit
Try this
if (!values.field || !values.field.password) {
errors.field = {password: 'Required'};
}
For example form with only email field:
const RegistrationForm = (props) => {
const { values, touched, errors, handleChange, handleBlur, handleSubmit, status } = props;
const emailFieldHelp = () => {
const { touched, errors, status, setStatus } = props;
console.log('status: ', status);
if (touched.email && errors.email) {
return errors.email;
}
if (status && !status.isUserAdded) {
setStatus({"isUserAdded": false});
return "User already exist";
}
return null;
};
return (
<Form onFinish={handleSubmit}>
<Form.Item
help={emailFieldHelp()}
validateStatus={setFieldValidateStatus('email')}
label="E-mail"
name="email"
hasFeedback={touched.email && values.email !== ''}
>
<Input
placeholder="Email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
</Form.Item>
</Form>
)
};
const RegFormView = withFormik({
validationSchema,
mapPropsToValues: () => ({
email: ''
}),
handleSubmit: async (values, { setErrors, setSubmitting, setStatus }) => {
await userService.createUser('/signup/', values)
.then(response => {
setStatus('');
const status = (response.isAdded)
? {isUserAdded: true}
: {isUserAdded: false};
setStatus(status);
setSubmitting(false);
}, (error) => {
setErrors(error);
});
},
})(RegistrationForm);
When I send form and validate it on sever side, I return 'isAdded' = false or true. Than I set status to {isUserAdded: true} or false if user not added. and I absolutely have no idea how to show this message in form under email field and keep form working. Now I can show message but than I cant send form second time becouse of status already set to {isUserAdded: true}. Do I need somehow to change status when user change email field? but I can't do it becouse of the formik.
here onChange={handleChange} I can pass only handleChange and can't run my function or I can run my function like this onChange={myFunc()} but than its seems impossible to pass handleChange to Formik. (I dont need call it like this handleChange(e) its not work! i need somehow pass it to Formik) I'm total stuck. If you have knowledge in react plz help.
And if you know some examples how to show server side validation messages in react form, links to this examples might be helpful. ty.
The problem arised when I was following this React tutorial.
The goal is to create a simple social media web app with Firebase, React, MaterialUI, etc.
I am around the 5:40:00 mark and I've managed to debug every problem I've encountered up until this point, but right now I've been stuck for more than an hour on something that seems like a stupid mistake. The segment that I'm on is creating the login page. I've successfully created the login form and it functions (meaning that it will redirect the user to the home page if the right user credentials are submitted). What doesn't function thought is the helperText of the input fields. The way it's supposed to work is to display an error and give Helper text with the value of an error object, which is generated by this basic LoginValidaton function in the back-end:
exports.validateLoginData = (userData) => {
let errors = {};
if (isEmpty(userData.email)) {
errors.email = "Must not be empty";
}
if (isEmpty(userData.password)) {
errors.password = "Must not be empty";
}
return {
errors,
valid: Object.entries(errors).length === 0 ? true : false,
};
};
Which yields the following response when called through Postman: linkForPicture
And should look like this: linkForPicture
However, in reality, it gives a bad request error in the console and logs the errors object. linkForPicture
Here is the code for my Login Page without the imports and styling parts:
class login extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
loading: false,
errors: {},
};
}
handleChange = (event) => {
this.setState({
[event.target.name]: event.target.value,
});
};
handleSubmit = (event) => {
event.preventDefault();
this.setState({
loading: true,
});
const userData = {
email: this.state.email,
password: this.state.password,
};
axios
.post("/login", userData)
.then((res) => {
console.log(res.data);
this.setState({ loading: false });
this.props.history.push("/");
})
.catch((err) => {
this.setState({
errors: err.response.data,
loading: false,
});
});
};
render() {
const { classes } = this.props;
const { errors, loading } = this.state;
console.log(errors);
return (
<Grid container className={classes.form}>
<Grid item sm />
<Grid item sm>
<img className={classes.logo} src={AppLogo} alt="no-nudes"></img>
<Typography variant="h4" className={classes.pageTitle}>
Login
</Typography>
<form noValidate onSubmit={this.handleSubmit}>
<TextField
id="email"
name="email"
type="email"
label="E-mail"
className={classes.textField}
helperText={errors.email}
error={errors.email ? true : false}
value={this.state.email}
onChange={this.handleChange}
fullWidth
/>
<TextField
id="password"
name="password"
type="password"
label="Password"
className={classes.textField}
helperText={errors.password}
error={errors.password ? true : false}
value={this.state.password}
onChange={this.handleChange}
fullWidth
/>
<Button
type="submit"
variant="contained"
color="primary"
className={classes.button}
>
Sign-in
</Button>
</form>
<Typography>{errors.email}</Typography>
</Grid>
<Grid item sm />
</Grid>
);
}
}
Although I am not sure, I believe the current code creates an object 'errors' in the state with the following properties when the form is submitted with empty text fields
errors: {
"errors": {
"email": "Must not be empty",
"password": "Must not be empty"
}
}
and so I tried to access it in the helperText with something like {errors.errors.email}, but this results in the following error: linkForPicture
I would be extremely grateful if I received some sort of guidance to what I'm doing wrong.
I think the problem is both with your .catch() { ...setState() and what your API is returning.
You're setting a new object that looks like this:
{
errors: err.response.data,
loading: false
}
but what's inside your err.response.data ie what's being returned by your API?
{
errors: {
email: "Must not be empty",
password": "Must not be empty",
}
}
ANOTHER object that already has errors as the property name.
That's why your state ends up with:
{
errors: {
errors: {
email: "Must not be empty",
password": "Must not be empty",
}
},
loading: false
}
you can either get your API to return just the error object:
{
email: "Must not be empty",
password: "Must not be empty",
}
or in your setState(), set the content of the new piece of state to the errors property your API is returning:
.catch((err) => {
this.setState({
errors: err.response.data.errors, //here
loading: false,
});
Update: Why is helperText={errors.errors.email} giving undefined?
Have a look at how you initialised your state object in your constructor function. Your errors property is an empty object {}.
So in the initial render, before you've clicked submit, your helperText prop in your <TextField> is trying to access an undefined property in your initially declared error object. There's nothing there atm! You only get something there after you click the Submit button.
If you want it to not show undefined, you could always fill it with an empty string:
this.state = {
email: "",
password: "",
loading: false,
errors: {
email: "",
password: ""
},
};
Having said that, this is probably not a very good way to manage error messages. Look into form validation for React. There's alotta libraries out there!
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);
}
}
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...