This is the first time I'm using Formik and I'm facing the following issues:
I created this form using a typescript starter provided in the Formik documentation, and it works, but I'd like to show a success message and remove the form once axios returns with status 200.
So,
1. how do I target the form reference inside of the axios call? normally that is as simple as e.target but the event seems not to be available in Formik.
2. how do I access the state of the form in Formik? to toggle the success message.
The full code is available here: https://codesandbox.io/s/throbbing-water-ffl2w
Thanks a lot in advance.
<Formik
initialValues={{
firstName: "",
lastName: "",
email: ""
}}
// initialStatus={{ // resetForm(); resets this
// sent: "nope"
// }}
onSubmit={(
values: Values,
{ setStatus, setSubmitting, resetForm }: FormikActions<Values>
) => {
axios({
method: "post",
url: "https://getform.io/f/15faef97-5703-4799-930d-c3e698c99967",
data: { email: values.email, values }
}).then(r => {
setSubmitting(false);
setStatus("sent");
//resetForm();
console.log("Thanks!");
});
}}
render={() => (
<Form>
<label htmlFor="firstName">First Name</label>
<Field
id="firstName"
name="firstName"
placeholder="John"
type="text"
/>
<label htmlFor="lastName">Last Name</label>
<Field id="lastName" name="lastName" placeholder="Doe" type="text" />
<label htmlFor="email">Email</label>
<Field
id="email"
name="email"
placeholder="john#acme.com"
type="email"
/>
<button type="submit" style={{ display: "block" }}>
Submit
</button>
</Form>
)}
/>
What I recommend is using state to control what to show in your component (for some reason I cannot save the codesandbox):
const BasicForm: React.FC<{}> = () => {
const [isSent, setIsSent] = React.useState(false);
then, the fetch callback:
.then(r =>
...
setIsSent(true);
Finally in your render function
render={({ isSubmitting, status }) =>
!isSent ?
<Form> ... </Form>:
<div>Success</div>
render is a function that gets props. I see that you use setStatus, so you can get status from props and make changes in Form Component
This is an outdated version of Formik v1.1.2 and I wouldn't recommend to use it as there are some Breaking Changes such as the render method has been deprecated and will be removed in future versions. You may want to use the current version which is v2.1.4
how do I target the form reference inside of the axios call?
Formik passes the values object along with other methods (called FormikBag) inside the onSubmit prop. You can pass these value directly to axios without the need to have your own onSubmit or onChange methods. Please note that <Formik> component has other props. that will give you pretty much full control/access for your needs. That said, I'd recommend to only use Formik state/methods to avoid any side effects or bugs of having the multiple states or handlers.
v2 General Syntax:
<Formik
initialValues={initialValues}
// Other Formik props...
onSubmit = {(Object: form values, Object: Formik Bag ) => {
// access form values...
}}
>
// Access render methods and props (children props)
{(props) => {
return (
<Form>
<Field> ...
</Form>
)
}
}
</Formik>
axios Example:
<Formik
initialValues={initialValues}
onSubmit={(values) => {
console.log(values) // Object holds your form values
axios({
method: "post",
url: "url",
data: { values }
})
})
/>
how do I access the state of the form in Formik? to toggle the success message.
You can use Formik setStatus method from FormikBag inside your onSubmit to pass your server response status, then you can access that status via children props Here is an example:
<Formik
initialValues={initialValues}
onSubmit={(values, setStatus) => {
axios({
method: "post",
url: "url",
data: { values }
})
.then(res => {
if (res.status === 200) {
// 200 means POST method response with success
// Pass your server response to Formik
setStatus({
sent: true,
msg: "Message has been sent! Thanks!"
// Pass more if you need
})
}
})
.catch(err => {
// Something went wrong
setStatus({
sent: false,
msg: `Error! ${err}. Please try again later.`
})
})
})
>
// Later in your code destructuring the children props and use it like so:
{({ status }) => (
<Form>
<Field ... />
{status && status.msg && (
<p className={`alert ${ status.sent ? "alert-success" : "alert-error"}`}>
{status.msg}
</p>
)}
<button>Submit</button>
</Form>
)}
</Formik>
I did fork your codesanbox and updated the dependencies versions/syntax in this codeSandbox Example. Please note that I'm no typescript expert.
Related
But it does work when I disable React Hook Form and vice versa but not both together. I just want to focus on the title input field on first render. I minified it a little and imports/exports are not included in the snippet because it's usual boilerplate.
When I comment out ref={refFocus} RHF works but when I take it in again it won't function. I don't know if this is a conflict with the DOM reference since RHF is working a lot with it too. Might be a version mismatch, typescript error or something's deprecated or incompatible - I have no clue. I'd appreciate any idea.
I already browsed through the questions and found a similar issue but it lacks contents/code example and there's no answer: Why onchange, onblur and useRef doesn't work with react hook form?
And this one is not very alike, I tried that: How to set focus when using React hook form Controller component
For context, It's a simple UI where I can add tiles by filling out a form that's inside a header:
const Header = () : JSX.Element => {
const
{setCtxAdded} = useContext(TileListCtx),
addSchema = z.object({
image: z.string().url({ message: 'Value must contain a valid URL address.' }),
title: z.string(),
text: z.string().optional()
}),
refFocus = useRef<HTMLInputElement | null>(null)
useEffect(()=> refFocus.current?.focus(), [])
type AddFormValidation = z.infer<typeof addSchema>
const
{register, handleSubmit, formState:{errors}} = useForm<AddFormValidation>({
resolver: zodResolver(addSchema)
}),
addTile = async (data: Array<string> | unknown) : Promise<void> => {
const add: Response = await fetch('http://localhost:3000/api/add', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
const res: Array<JSON> | any = await add.json()
setCtxAdded(add.ok)
if (res.error.code == 'P2002') console.log('Title already exists.')
}
return (
<header>
<h1>X-5</h1>
<form onSubmit={handleSubmit(addTile)}>
<input {...register("image")} type='text' placeholder='Image' />
<input {...register("title")} ref={refFocus} type='text' placeholder='Title' />
<input {...register("text")} type='text' placeholder='Text' />
<button>Add Tile</button>
</form>
</header>
)
}
React hook form uses refs, but you replaced his ref with your ref.
RHF has setFocus method. Use it instead of your own ref:
const {
register,
handleSubmit,
formState: { errors },
setFocus
} = useForm<AddFormValidation>(),
useEffect(() => {
setFocus("title");
}, [setFocus]);
<form onSubmit={handleSubmit(addTile)}>
<input {...register("image")} type="text" placeholder="Image" />
<input {...register("title")} type="text" placeholder="Title" />
<input {...register("text")} type="text" placeholder="Text" />
<button>Add Tile</button>
</form>
https://codesandbox.io/s/loving-booth-2yg5ve?file=/src/Header.tsx
I am working on an address book and I have fetched all the data from this API url:
https://jsonplaceholder.typicode.com/users
The API cannot really be modified, but it should behave "like if" according to this message in the documentation: "resource will not be really updated on the server but it will be faked as if."
I have set up the react hook form but when I submit my form this is what I get in the dev tools tab network? Shouldn't be showing the user inputs at this point instead of empty string for all fields? The id is the only thing that gets updated.
Is there anything wrong with my Submit function or the actual fetch? Is it ok I have the POST fetch in this component where I have my form as well or should be in the same component where I have the GET request?
Would this one be a good way to approach the POST request?
const NewUserForm = () => {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = () => {
fetch(URL, {
method: 'POST',
body: JSON.stringify({
id:'',
name: '',
email: '',
address1:'',
address2:'',
city:'',
phone:''
}),
headers: {
'Content-type': 'application/json; charset=UTF-8'
},
})
.then((response) => response.json())
.then((json) => console.log(json));
}
return (
<>
<Header>New user</Header>
<FormContainer>
<Form onSubmit={handleSubmit(onSubmit)}>
<input type="text" placeholder="name" {...register("name", { required: true })} />
{errors.name && <span>This field is required</span>}
<input type="text" placeholder="email" {...register("email", { required: true })} />
{errors.email && <span>This field is required</span>}
<input type="text" placeholder="address1"{...register("address1", { required: true })} />
{errors.address1 && <span>This field is required</span>}
<input type="text" placeholder="address2"{...register("address2", { required: true })} />
{errors.address2 && <span>This field is required</span>}
<input type="text" placeholder="city"{...register("city", { required: true })} />
{errors.city && <span>This field is required</span>}
<input type="text" placeholder="phone"{...register("phone", { required: true })} />
{errors.phone && <span>This field is required</span>}
<input type="submit" />
</Form>
</FormContainer>
</>
);
}
Okay, after checking the react-hook-form docs, here is a possible solution:
In the docs, it says that your onSubmit will have a data param:
const onSubmit = (data) => alert(JSON.stringify(data));
Which means that you can use that in your onSubmit too.
Try changing your onSubmit to use the data parameter:
const onSubmit = (data) => {
fetch(URL, {
method: 'POST',
body: JSON.stringify(data),
And revert the change I suggested earlier regarding handleSubmit. This is correct:
<Form onSubmit={handleSubmit(onSubmit)}>
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>
item at form to show up form fields here is my code
import { Form, Input, Button, Select, } from 'antd';
import axios from "axios";
render() {
const isLoading = this.state.isLoading;
return (
<>
<Form className={'auth-form'} onSubmit={(e) => { this.onSubmit(e) }}>
<h3 className={'mb-5 text-center'}>Candidate Sign Up</h3>
<Form.Item
label=""
name="email"
rules={[{ required: true, type: 'email', message: 'Please enter email address'}]}
>
<Input className={'ks-form-control'} placeholder={'Enter Email'} onChange={this.onChangehandler} />
</Form.Item>
<Form.Item
label=""
name="password"
rules={[{ required: true, message: 'Please input your password!' }]}
>
<Input.Password className={'ks-form-control'} placeholder={'Password'} />
</Form.Item>
<Form.Item
label=""
name="confirmPassword"
rules={[{ required: true, message: 'Please confirm your password!' }]}
>
<Input.Password className={'ks-form-control'} placeholder={'Confirm Password'} />
</Form.Item>
<Form.Item >
<Button className={'btn-custom px-4 py-2 d-block w-100'} type="primary" htmlType="submit">
Create an account
</Button>
</Form.Item>
</Form>
</>
)
}
Here is the code for submithandler. I want to show message comming from api and custom message with below code at rules={[{}]}
msg: response.data.message
On submit handler
onSubmitHandler = (e) => {
e.preventDefault();
this.setState({ isLoading: true });
axios
.post("http://127.0.0.1:8000/api/user-signup", this.state.signupData)
.then((response) => {
this.setState({ isLoading: false });
if (response.data.status === 200) {
this.setState({
msg: response.data.message // message comming from api
});
}
if (response.data.status === "failed") {
this.setState({ msg: response.data.message }); // message comming from api
}
});
}
The valiations for all fields working fine with rules={[]} . But I want show error based on api response like if a email already registered then this will show message 'email already exists'
Please let me know how can i do this
It would be helpful to know what Form.Item even is, which UI library you are using here. If you can set rules on the component you can probably also set an "error"-attribute. My best guess to what you have provided would be:
const [emailError, setEmailError] = useState(false)
const = onSubmit = (formvalues) => {
const { email } = formvalues
validateEmailViaAPI(email).then((response) => response.isValid ? doWhatEver(formvalues) : setEmailError(true))
}
const onChangeEmail = () => setEmailError(false)
Inside your Input Component you can specifically set the error:
<Form.Input error={emailError}> </Form.Input>
This should then show the error message if you set the error yourself. This probably happened behind the "rules"-attribute that the component used.
Dont forget to clear the error at a useful point. It would make sense to put that validation inside a custom hook if you use it more than once.
If this wasnt helpful please provide more input about the Input-Component that you are using.
You have not provided the onSubmit function but if you are using axios you can do something like
const [error, setError] = useState(null)
const onSubmit = (e) => {
e.preventDefault();
axios.post('linkToApi').then((res) =>
{
`res is the response of the succesful api call`
`do Whatever you want on successful api call`
}).catch((err) => {
setError(err.response.data.message)
})
}
Here err.response.data is the response of your api on error.
You have to set your backend to return http status codes which will determine the success or failure of the api call. For eg:- 200 means successful api call and 401 means unauthorized.
See this answer to do this in express
https://stackoverflow.com/a/28547843/12200445
In your form you can create a div to show the error
{error && <div>{error}</div>} //This div will only show when there is some value defined for `error`
EDIT: I didn't realize you were using class components but I hope you get the idea.
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();
}}
/>
</>
);
};