I have the following Form:
const MyForm = () => {
return (
<>
<Formik
validateOnChange={true}
initialValues={{ plan: "", email: "", name: "" }}
validate={values => {
console.log(values)
const errors = {}
if (values.plan !== "123" && values.plan !== "456") {
errors.plan = "Not valid"
} else if (values.plan === "") {
errors.plan = "Please enter something"
}
if (!values.email) {
errors.email = "Please provide an e-mail address."
} else if (
!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
) {
errors.email = "Please provide a valid e-mail address."
}
return errors
}}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
setSubmitting(false)
}, 400)
}}
>
{({ isSubmitting, errors }) => (
<Form>
<FieldWrapper>
<InputField type="text" name="plan" label="plan" />
<StyledErrorMessage name="plan" component="div" />
</FieldWrapper>
<Button
disabled={errors.plan}
>
Continue
</Button>
</Form>
)}
</Formik>
</>
)
}
I have a Continue Button and I want it to be disabled if there are any errors. I am doing <Button disabled={errors.plan}> and this works.
However: it does not disable to Button when the user just doesn't touch the field at all - since then, the validation isn't called and consequently, there won't be any errors in the error object. So initially, the button is not disabled.
How can I circumvent this?
I'm not too familiar with Formik, but could you add a state for completed status of the form, that is initially set to false, and when completed setState(true). Then your conditional for <Button> can check both errors.plan && completedState.
Related
I am building form using react-form-hook. I am stuck on the validation part.
I don't want to use client side validation to show errors. I want to validate the touched fields server side and show the returned errors on the client side.
The form got submitted on the onBlur and i am able to show the errors. But i couldn't track changed fields and only submit the form. Any help would be appreciated.
Here is the code snippet that I have come up with.
const {
register,
handleSubmit,
watch,
setError,
setValue,
formState: { isLoading, errors, touchedFields },
} = useForm({
defaultValues: { ...form },
});
async function handleFocusOut(formData) {
console.log(formData, touchedFields);
try {
const { data } = await axios.post(`/api/ads/publish`, formData);
console.log(data);
} catch (error) {
const {data, status} = error.response;
if(status === 422) {
const errs = {...data.errors};
Object.keys(errs).filter(field => !touchedFields[field]).filter(field => delete errs[field]);
Object.keys(errs).map(
(field) => setError(field, {message: errs[field][0], type: 'custom'})
);
console.log(errors);
}
toast(error.message);
console.log(error);
}
return;
}
return (
<form onBlur={handleSubmit(handleFocusOut)}>
{fields.map((field) => (
<Fragment key={field.key}>
{field.type === "text" && (
<div className="mb-4">
<TextInput
register={register}
id={field.key}
label={field.label}
name={field.name}
settings={field.settings}
/>
{errors[field.name] && <p className="text-xs text-red-500">{errors[field.name].message}</p>}
</div>
)}
{field.type === "textarea" && (
<div className="mb-4">
<TextareaInput
register={register}
id={field.key}
label={field.label}
name={field.name}
settings={field.settings}
/>
{errors[field.name] && <p className="text-xs text-red-500">{errors[field.name].message}</p>}
</div>
)}
</Fragment>
))}
</form>
)
I am trying to render an error message under an Antd input on form submit if some conditions are not satisfied. In my specific case, I want to render an error message if !this.isNew && this.state.hasTypedLocation && !this.state.hasChosenLocation. Right now I'm just console logging an error in the console. Does Antd offer some sort of similar functionality?
I have the following input:
<Form.Item
name="searchBox"
rules={[
{
required: true,
message: <IntlMessages id="common.error_address" />,
},
]}
>
<Input
type="text"
placeholder={intl.formatMessage({ id: 'common.error_address' })}
onChange={onChangeHandlerAddress}
value={facilityData.address1}
/>
</Form.Item>
And I have the following function which saves my entity:
saveFacility(event) {
event.persist();
event.preventDefault();
this.formRef.current
.validateFields(['name', 'organization', 'customer', 'searchBox'])
.then((values) => {
const { actions } = this.props;
const { facility } = this.state;
if (!this.isNew && this.state.hasTypedLocation && !this.state.hasChosenLocation ) {
console.log('error')
} else {
actions.saveFacility(facility);
}
})
.catch((error) => {
console.log(error);
});
}
You can pass a validator function that triggers the custom error message as a rule to Form.Item. The validator should return a Promise. Example:
<Form.Item
name="searchBox"
rules={[
{
required: true,
message: <IntlMessages id="common.error_address" />,
},{
// Validator function example
validator: (rule, value) => {
if (!this.isNew && this.state.hasTypedLocation && !this.state.hasChosenLocation ) {
return Promise.reject("Custom Error Message!!");
} else {
return Promise.resolve();
}
},
]}
>
<Input
type="text"
placeholder={intl.formatMessage({ id: 'common.error_address' })}
onChange={onChangeHandlerAddress}
value={facilityData.address1}
/>
</Form.Item>
onCompleted() is called in useMutation because i want to open=true (open is a flag) when mutation is done than this flag value should pass to const App() so that open={open} and alert is shown. but the value of open is false throughout process.
(2). If i set open={true}, than Welcome popUp shown on screen with login button, but when i press login button and onclick() triggers it gives error "cannot read property of 'setNewUser'", where setNewUser is const define in index file.
Register.js
const App = ({ values, errors, touched, isSubmitting, dirty , isValid, setNewUser }) => {
const styles= useStyles();
const [open, setOpen]= useState(false) ; //On new user dialog will open
// console.log(open)
return (
<div className={styles.root}>
<Paper className={styles.paper}>
<Form>
<Field type="username" name="username" placeholder="Username" />
<div>
{touched.email && errors.email && <p>Invalid Email </p>}
<Field type="email" name="email" placeholder="Email" />
</div>
<div>
{touched.password && errors.password && <p>Invalid password </p>}
<Field type="password" name="password" placeholder="Password" />
</div>
<button disabled={isSubmitting || !isValid || !dirty }>
Register
</button>
</Form>
</Paper>
<Dialog
// Here is the flag, its not changing to true
open={open}
disableBackDropClick={true}>
<DialogTitle>
<VerifiedUserTwoTone className={styles.icon}/> New Account created
</DialogTitle>
<DialogContent>
<DialogContentText> Welcome {values.username} </DialogContentText>
</DialogContent>
<DialogActions>
<button onClick={()=>setNewUser(false)}>Login</button>
</DialogActions>
</Dialog>
</div>
);
};
const FormikApp = withFormik({
// enableReinitialize: true,
mapPropsToValues({ email, password, username }) {
return {
username: username || " ",
password: password || " ",
email: email || " ",
};
},
validationSchema: Yup.object().shape({
email: Yup.string()
.email("Invalid email account")
.required("field missing"),
password: Yup.string()
.min(8, "password is weak")
.required()
}),
handleSubmit(values, {setStatus, props, setSubmitting }) {
//event.preventDefault();
props.createUser({
variables: values,
}, );
setTimeout(()=>{
setSubmitting(false)
setStatus("sent");
console.log("Thanks!");
},1000);
}
})(App);
const Register = () => {
const [createUser,{loading,error}] = useMutation(REGISTER_MUTATION ,
{
onCompleted(){
// setOpen(true)
}}
);
if (loading) return <p>loading...</p>;
if (error) return <p>An error occurred</p>;
return <FormikApp createUser={createUser} />;
};
export default Register;
index.js (local index for forms)
import React,{useState} from "react";
import withRoot from "../withRoot";
import Login from "./Login"
import Register from "./Register"
export default withRoot(() => {
const [newUser, setNewUser]= useState(true)
return newUser? //if user is new than goto Register otherwise Login page
<Register setNewUser={setNewUser}/>
) : (
<Login/>
)
});
Login page is replica of Register (excluding mutation). I am stuck on this point, any contribution is welcomed. Thanks
You didn't passed setNewUser down to the <Register/>:
return <FormikApp createUser={createUser} setNewUser={props.setNewUser} />;
You cannot use setOpen in onCompleted as it's defined inside child. You need to define it higher (in <Register />) and pass value (open) and setter (setOpen) as props.
I need help with state management in my Chat-app.
Description:
I am attempting to display the username entered from the UserModal component to my ChatScreen component. I am using the onChange function inside UserModal and I am using a switch case statement to ensure form validation. Then setting the state with name as an array and assigning the value to the name like so:
UserModal.js
onChange = e => {
e.preventDefault();
const { name, value } = e.target;
let formError = this.state.formError;
switch (name) {
case 'userName':
formError.userName =
value.length < 3 ? 'minimum 3 characters required' : '';
break;
default:
}
this.setState({ formError, [name]: value }, () =>
console.log(this.state)
);
};
onSubmit function
onSubmit = (e) => {
e.preventDefault();
if (formValid(this.state)) {
Axios.post('http://localhost:5000/api/authenticate').then(
(res) => {
if (res.data.isSuccessful === true) {
alert('Your username is: ' + this.input.value);
this.close();
} else {
alert('Error Message: ' + res.data.ErrorMessage);
}
}
);
} else {
alert(
'Error message: Please enter a username with a minimum of 3 characters'
);
}
};
Form
<form action="Main.js" className="form-inside-input" onSubmit={this.onSubmit} noValidate>
<label>Username:</label>
<input
className={formError.userName.length > 0 ? "error" : null}
type="text"
placeholder="Enter username"
name="userName"
onChange={this.onChange}
ref={(input) => (this.input = input)}
noValidate
></input>
{formError.userName.length > 0 && <span>{formError.userName}</span>}
<Button
type="submit"
positive
icon="checkmark"
labelPosition="right"
content="Let me in!"
onClick={() => {
this.onSearch();
}}
/>
</form>;
Goal:
My goal is to take what the user puts in the username field in the modal and display "{userName} has joined the chat." where userName is replaced with the input of the user, in the ChatScreen.js component within the Semantic UI fragments.
Github Link: https://github.com/George-Jindo/chat-app
I have a React application where I have a form with an email and phone number fields. The textBoxes validates itself onBlur. When I have an error for email and it appears on screen, when I fix it and then click the submit button, the onBlur fires and clears the error but submit is not fired. I want the error to be cleared and submit to be fired after that since the submit button has been clicked
function SaveContactInfo () {
const dispatch = useDispatch()
const { email, phoneNumber } = useSelector(getContactInfo)
const [emailValue, setEmail] = useInput(email)
const [phone, setPhone] = useInput(phoneNumber)
const [emailError, setEmailError] = useState(null)
const [phoneError, setPhoneError] = useState(null)
const handlePhoneOnBlur = useCallback((target) => {
if (target.value) {
let number = phoneUtil.parse(target.value, 'US')
let valid = phoneUtil.isValidNumber(number)
if (!valid) {
if (!phoneError) {
setPhoneError(true)
target.focus()
}
} else {
setPhoneError(false)
}
} else {
setPhoneError(false)
}
}, [setPhoneError, phoneUtil, phoneError])
const handleEmailOnBlur = useCallback((target) => {
if (target.value) {
if (!target.checkValidity()) {
if (!emailError) {
setEmailError(true)
target.focus()
}
} else {
setEmailError(false)
}
} else {
setEmailError(false)
}
}, [setEmailError, emailError])
const handleSaveContactInfo = useCallback((e) => {
if (!emailError && !phoneError) {
dispatch(updateContactInfo(phone, emailValue))
}
}, [dispatch, emailValue, phone, phoneUtil, emailError, phoneError])
return (
<form className={classes.preferences} noValidate onSubmit={handleSaveContactInfo}>
<FormControl fullWidth className={classes.preference}>
<EmailInfo />
<TextInputField value={emailValue || ''} error={emailError} FormHelperTextProps={{ 'data-testid': 'emailError' }} helperText={emailError && 'Enter email address in format: yourname#example.com' variant='outlined' type='email' maxLength={100} onChange={setEmail} onBlur={handleEmailOnBlur} label='Email' />
</FormControl>
<FormControl fullWidth className={classes.preference}>
<PhoneInfo />
<TextInputField value={phone || ''} error={phoneError} helperText={phoneError && 'Enter phone number with area code and in format: 123-456-7890' variant='outlined' type='tel' onChange={setPhone} onBlur={handlePhoneOnBlur} label='Phone Number' />
</FormControl>
<div>
<PrimaryButton type='submit'>Submit</PrimaryButton>
</div>
</form>
)
}
export default SaveContactInfo
Update: I found that there is a known react issue https://github.com/facebook/react/issues/4210 where when an onBlur caused a shift in position of the submit button as there is a DOM rerender, the click is not registered and hence it fails. IN fact all buttons that shift position, their click is not registered. Any solution to that?