Handling routes after submitting form - reactjs

Hello my great teachers of StackOverflow. I'm going through ben awads fullstack tutorial and am trying to add an image upload feature to create post. Looks like everything works well, inserts posts (including image) into db. However, after submitting my form, it wont send me to the home page (stays on the same page with current values inserted). It is set so that if there are no errors, route me to the homepage. Im assumming i have no errors cause the post inserts into database. Any help will be greatly appreciated.
const CreatePost: React.FC<{}> = ({}) => {
const router = useRouter();
const [createPost] = useCreatePostMutation();
return (
<Layout>
<Formik
initialValues={{ title: "", text: "", file: null }}
onSubmit={async (values) => {
console.log(values);
const { errors } = await createPost({
variables: values,
});
if (!errors) {
router.push("/");
}
}}
>
{({ isSubmitting, setFieldValue }) => (
<Form>
<InputField name="title" placeholder="title" label="Title" />
<Box mt={4}>
<InputField name="text" placeholder="text..." label="Body" />
</Box>
<Input
mt={4}
required
type="file"
name="file"
id="file"
onChange={(event) => {
setFieldValue("file", event.currentTarget.files[0]);
}}
/>
<Button mt={5} type="submit" isLoading={isSubmitting}>
create post
</Button>
</Form>
)}
</Formik>
</Layout>
);
};

You can use router.push hook.
You need to pass validate function as props to Fomik to prevent submitting only with 2 fields. I have added a basic validate object. But you can use Yup package also. Formik will return an error object inside the form and you need to make sure form does not get submitted in error state. For that get the errors object and as you are using custom Button component pass true/false by checking if any errors exist. I added the code.
You can get more details here.
router.push("/home");
.....
const validate={values => {
const errors = {};
if (!values.title) {
errors.email = 'Required';
}
if (!values.text) {
errors.text= 'Required';
}
if (!values.file) {
errors.file= 'Required';
}
return errors;
}}
<Formik
initialValues={{ title: "", text: "", file: null }}
validate
onSubmit={async (values) => {
.......
{({ isSubmitting, setFieldValue, errors }) => (
..........
<Button mt={5} type="submit" isLoading={isSubmitting} isFormValid={!Object.values(errors).find(e => e)}>
create post
</Button>

Related

Antd TypeScript : Log data when user clicks enter

In the project I'm working, I have wrapped the input around a form, and the idea is when the user types something and clicks enter, the data is logged. But the issue is I'm not getting such logs.
Here is my code :
Index.tsx
const onFinish = (values: any) => {
console.log("yoyo");
console.log(values);
};
<Form name="control-ref" onMouseEnter={onFinish}>
<Form.Item name="note" rules={[{ required: true }]}>
<InputField
variant="search"
placeholder="Search"
addonAfter={selectAfter}
className="search-field"
/>
</Form.Item>
</Form>
The InputField with the specified variant returns this components
const SearchField: React.FC<IInputFieldProps> = (props) => {
const { placeholder } = props;
return (
<Input
className="search-field"
placeholder={placeholder || 'Search'}
{...props}
/>
);
};
Please help
Log the data when the form is submitted. Forms can be submitted by pressing enter.
<Form name="control-ref" onFinish={(values: any) => {
console.log("yoyo");
console.log(values);
}}>
{...}
</Form>
Edit: updated submission callback according to ant design react docs

Formik validate initial values on page load

Code below validate when submit and onFocusOut of textbox fine, What I expect it to trigger validation first reload of the page with initial values.
Tried validateOnMount, as well as others but not worked.
Whats missing here?
const RoleValidationSchema = Yup.object().shape({
Adi: Yup.string()
.min(2, "En az 2 karakter olmalıdır")
.max(30, "En fazla 30 karakter olmalıdır")
.required("Gerekli!")
})
const Role = (props) => {
return (
<div>
<Formik
onSubmit={(values, { validate }) => {
debugger
validate(values);
alert("Submietted!")
props.handleFormSubmit()
}}
initialValues={{
Adi: "d"
}}
validationSchema={RoleValidationSchema}
validateOnMount={true}
validateOnChange={true}
validateOnBlur={true}
render={({ errors, touched, setFieldValue, ...rest }) => {
debugger
return (
<Form>
<Row>
<Col sm="12">
<Label for="Adi">Rol Adi</Label>
<FormGroup className="position-relative">
<Field
autoComplete="off"
id="Adi"
className="form-control"
name="Adi"
type="text"
/>
<ErrorMessage name="Adi">
{(msg) => (
<div className="field-error text-danger">{msg}</div>
)}
</ErrorMessage>
</FormGroup>
</Col>
<Tree dataSource={treeData} targetKeys={targetKeys} expandKeys={[]} onChange={onChange} />
</Row>
<button type="submit" className="btn btn-success" > Kaydet</button>
</Form>
)
}}
/>
</div>
)
}
This worked for me to show validation error on form load.
useEffect(() => {
if (formRef.current) {
formRef.current.validateForm()
}
}, [])
Try to add enableReinitialize Prop to your Formik Component
<Formik
enableReinitialize
......
/>
I had a bit specific case with dynamic Yup schema. Schema was generated based on API data. And validateOnMount was failing as when component was mounted there was no schema yet (API response not received yet).
I end up with something like this.
Inside component I used useEffect. Notice: I used useRef to be able to reference Formik outside of form.
useEffect(() => {
if (formRef.current) {
// check that schema was generated based on API response
if(Object.keys(dynamicValidationSchema).length != 0){
formRef.current.validateForm()
}
}
}, [initialData, dynamicValidationSchema])
Formik:
<Formik
innerRef={formRef}
enableReinitialize
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={props.handleSubmit}
>
It's possible to provide a initial value for validation in the "initialIsValid" prop.
const validationSchema= Yup.object()
const initialValues = { ... }
const initialIsValid = schema.isValidSync(initialValues)
return <Formik
initialValues={initialValues}
validationSchema={validationSchema}
initialIsValid={initialIsValid }
...
>
...
</Formik>
The only solution I found is to explicitly add initialErrors={{..}} to Formik.
In the below example, it's added depending on some condition. Unfortunately that duplicates the Yup validation/message which I also have in the schema. Nothing else worked, no validateOnMount or anything else.
<Formik
enableReinitialize
validationSchema={schema}
onSubmit={ (values) => {
submitForm(values);
}}
initialValues={initialFormValues}
/* Unfortunately a duplication of Yup Schema validation on this field,
but can't force Formik/Yup to validate initially */
initialErrors={!isEmptyObject(initialFormValues) &&
initialFormValues.inactiveApproverCondition &&
{'approverId': 'Selected Approver is no longer eligible. Please choose a different Approver to continue.'}}
>
You should try updating your code follow this
render={({ errors, touched, setFieldValue, isValid, ...rest })
and
<button type="submit" className="btn btn-success" disabled={!isValid}> Kaydet</button>

How to reset a certains fields after submit in Formik

I am using Formik and I need to make this logic.
I am have a big form, and after submission I need to reset certain fields in this form
There is 2 buttton: Save and Save & New
save will save the data and redirect.
Save & New will save the data and reset the first two fields.
I tried to use:
resetForm but it resets all the form
setValues does not work
setFieldValues prevents the api request
set values manually also does not work
Here is the code
const initialValues = { value1: "", value2: "", value3: "", value4: "" };
<Formik
initialValues={initialValues}
enableReinitialize
validationSchema={Schema}
validateOnChange={false}
validateOnBlur={false}
onSubmit={(values, { setSubmitting, resetForm }) => {
props.addData(values); // api request
setSubmitting(false);
// resetForm(initialValues); // this will reset all the form fields
// values.value1 = ""; // does not work
}}
>
{({ values, handleChange, handleSubmit, setFieldValue, errors }) => (
<Form>
<Row>
<Field type="text" name="value1" />
<Field type="text" name="value2" />
<Field type="text" name="value3" />
<Field type="text" name="value4" />
</Row>
<StyledRow>
<Button
variant="outline-primary"
onClick={() => {
handleSubmit();
history.push("/tableOverview");
}}
>
Save
</Button>
<Button
variant="outline-primary"
onClick={() => {
handleSubmit();
// this prevents the api request, because of async code I guess
// setFieldValue("value1", "");
// setFieldValue("value2", "");
}
>
Save & New
</Button>
</StyledRow>
</Form>
)}
</Formik>
On submitting the form, you can set whatever the values you dont want to peresist using setFieldValue and later setSubmitting to false, So that the values you want will be persisted and you can submit the form again
Please find the example codesandbox here

Displaying Value of setFieldErrors Using ErrorMessage Component in Formik

See Update Below
I have a Login Form Component that I built using Formik that syncs with Firebase Authentication. I have set it up so that I can display errors from Firebase using the setFieldError prop. Here is the relevant sections of the code:
export const LoginForm = () => {
async function authenticateUser(values, setFieldError) {
const { email, password } = values
try {
await firebase.login(email, password)
navigate('/', { replace: true })
} catch (error) {
console.log('Authentication Error: ', error)
await setFieldError('firebaseErrorMessage', error.message)
}
}
return (
<>
<h1>Form</h1>
<Formik
render={props => <RenderForm {...props} />}
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={async (
values,
{ setFieldError, setSubmitting, resetForm }
) => {
setSubmitting(true)
authenticateUser(values, setFieldError)
setSubmitting(false)
resetForm()
}}
/>
</>
)
}
const RenderForm = ({ errors, isSubmitting, isValid }) => (
<Form>
<h3>Sign Up</h3>
<Email name="email" />
<Password name="password" />
<Button disabled={!isValid || isSubmitting} type="submit">
Submit
</Button>
{errors.firebaseErrorMessage && <p>{errors.firebaseErrorMessage}</p>}
</Form>
)
Now, this works just fine. However, if I try to display the error message using Formik's ErrorMessage component, then the message does not show up.
In other words, this works:
{errors.firebaseErrorMessage && <p>{errors.firebaseErrorMessage}</p>}
This does not work:
<ErrorMessage name="firebaseErrorMessage" />
Any idea why this doesn't work and how to get it to work?
Thanks.
UPDATE
Here are my initial values:
const initialValues = {
email: '',
password: '',
}
I don't think that you should use Formik's error for your Firebase error. Formik's errors are meant for validating form inputs.
To store and reference an API error, you can use Formik's status object. Handling API errors is the example he gives for status.
I think the issue is that, in Formik, name is meant to refer to the name of an input. Instead, you're imperatively adding a new name property to the errors object using setFieldError, but firebaseErrorMessage isn't a field in your form. (Share your initialValues object to verify this.)
One annoying part of this is that there is probably some styling associated with <ErrorMessage> that you can't leverage directly. But, in my opinion, it's probably more important to have your system structured correctly, and then you can mimic styles as-needed.
Here's my code suggestion:
const RenderForm = ({ isSubmitting, isValid, status }) => (
<Form>
<h3>Sign Up</h3>
<Email name="email" />
<Password name="password" />
<Button disabled={!isValid || isSubmitting} type="submit">
Submit
</Button>
{status.firebaseErrorMessage && <p>{status.firebaseErrorMessage}</p>}
</Form>
);
export const LoginForm = () => {
async function authenticateUser(values, setStatus, setSubmitting) {
const { email, password } = values;
setSubmitting(true);
try {
await firebase.login(email, password);
navigate("/", { replace: true });
} catch (error) {
console.log("Authentication Error: ", error);
setStatus({
firebaseErrorMessage: error.message
});
} finally {
setSubmitting(false);
}
}
return (
<>
<h1>Form</h1>
<Formik
render={props => <RenderForm {...props} />}
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={async (values, { setStatus, setSubmitting, resetForm }) => {
await authenticateUser(values, setStatus, setSubmitting);
resetForm();
}}
/>
</>
);
};

understanding Formik and React

This is probably not the best place to post this question but where, then?
The code below is taken from Formik's overview page and I'm very confused about the onSubmit handlers:
The form element has an onSubmit property that refers to handleSubmit which is passed on that anonymous function : <form onSubmit={handleSubmit}>. Where does that come from?
The Formik component has an onSubmit property as well:
onSubmit={(values, { setSubmitting }) => { ... }
How do these relate to each other? What is going on?
import React from 'react';
import { Formik } from 'formik';
const Basic = () => (
<div>
<h1>Anywhere in your app!</h1>
<Formik
initialValues={{ email: '', password: '' }}
validate={values => {
let errors = {};
if (!values.email) {
errors.email = 'Required';
} else if (
!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
) {
errors.email = 'Invalid email address';
}
return errors;
}}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
{({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
/* and other goodies */
}) => (
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
onChange={handleChange}
onBlur={handleBlur}
value={values.email}
/>
{errors.email && touched.email && errors.email}
<input
type="password"
name="password"
onChange={handleChange}
onBlur={handleBlur}
value={values.password}
/>
{errors.password && touched.password && errors.password}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</form>
)}
</Formik>
</div>
);
export default Basic;
The component takes onSubmit as a prop where you can execute code you want to perform when you submit your form. This prop is also given some arguments such as values (values of the form) for you to use in your onSubmit function.
The handleSubmit form is auto generated from the Formik library that automates some common form logics explained here. The handleSubmit will automatically execute onSubmit function mentioned above as part of its phases (pre-submit, validation, submission). Hope that answers your question!

Resources