I'm just trying this library and I get the following warning:
proxyConsole.js:56 Warning: LoginForm is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://fb.me/react-controlled-components
Am I doing anything wrong?
import React from 'react';
import { Formik, InjectedFormikProps } from 'formik';
import Yup from 'yup';
interface IValues {
email: string;
password: string;
}
interface ILoginFormProps {
onSubmit(payload: IValues): void;
}
const LoginForm: React.StatelessComponent<
InjectedFormikProps<ILoginFormProps, IValues>
> = ({ values, handleChange, handleSubmit, errors, error, isSubmitting }) =>
<form onSubmit={handleSubmit}>
<input
type="text"
name="email"
value={values.email}
onChange={handleChange}
placeholder="[email protected]"
/>
{errors.email &&
<div>
{errors.email}
</div>}
<input
type="password"
name="password"
value={values.password}
onChange={handleChange}
placeholder="password"
/>
{errors.password &&
<div>
{errors.password}
</div>}
{error &&
error.message &&
<div style={{ color: 'red' }}>
Top Level Error: {error.message}
</div>}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</form>;
export default Formik<ILoginFormProps, IValues, IValues>({
validationSchema: Yup.object().shape({
email: Yup.string().email().required(),
password: Yup.string().required()
}),
handleSubmit: (payload, { props, setError, setSubmitting }) => {
props.onSubmit(payload);
}
})(LoginForm);
You're never instantiating your values.email and values.password, so your inputs are receiving undefined values, which React interprets to mean the inputs are uncontrolled.
I would suggest implementing (in your Formik implementation) the mapPropsToValues method to ensure that email and password are initiated to empty strings instead of undefined, for example:
mapPropsToValues: (props) => {
return {
email: '',
password: '',
}
}
(Of course, if you wanted to ensure the form populated with values based on your props, you would do it there as well. I'm setting to empty strings here because your example is a login form, where you want the fields to start empty.)
@eonwhite should we add our internal mergeFields helper to Formik?
Of course I'd like to share mergeFields as it could be helpful to people, but I feel like it's a little opinionated and specific to add directly into Formik. Maybe as some kind of utility function people could optionally include?
@eonwhite my bad, thanks for the quick answer :)
I found this somewhat confusing since the documentation says "If mapPropsToValues is not specified, then Formik will map all props that are not functions to the inner component's props.values" which makes it seem like I can simply not use mapPropsToValues and go with the default vals, but that results in this "uncontrolled" warning. Or did I misunderstand the documentation?
@shacker looks like this new line has been added to the docs:
Even if your form is not receiving any props from its parent, use mapPropsToValues to initialize your forms empty state.
Is there any way for the library to reach into the inner form and create a default mapPropsToValues function when it's not passed as an option?
Is there a way to initialize all values as empty values? It's burdensome when the form is huge to write down every key-value pair in the form
@kuzdogan I鈥檝e been back and forth on this in my mind. It鈥檚 possible to do now with the changes we made to field, but things like initialValues won鈥檛 work if we do this.
@kuzdogan the other issue, come to think of it, is that this would only work with Field components and not with passing down props
by taking props.xxxx fixes the issue for me since fields get back its own value.
address: props.address || ''
Hm this behaviour is broken imo... Why should I have to initialize the values, even though there are none expected? It should work without it imo.. Otherwise you need do to a google search and read this issue here, since this behaviour is not very intuitive..
While I agree that it's a bit unintuitive, I don't think Formik should fill in initial values for you. That seems a little too magical to me. I think a warning if you mount a Field with no initial value might be good, assuming there's no valid usecase for that (I can't think of one). Maybe Field could also do with a flag like defaultInitialValue or something.
I would like to add my thoughts to this issue, with the following case:
We have forms, where there are optional number inputs. The initialValues in these cases looks something like { price: null }.
This will trigger the react warning uncontrolled/controlled, as it expected to when typing into that input.
The suggested solution above, as of { price: '' } as initialValues will work as expected on the frontend, however it will trigger a much bigger problem, as of submitting the data without touching that NON required field will send the data { price: '' } which is rejected by our backend, as empty string is not a valid number. This of course could be handled there, but shifting a frontend warning to backend type checking problem seems unnatural and odd. Also the value should be NULL by the application logic, not empty string.
I believe this problem should be handled on the frontend, as it is a react specific thing. In our previous projects, before we started using formik we had a lot of expressions on controlled components where the value was <input value={value || ''} />, while onChange and everything works as expected.
@balinabbb I totally agree. I'm surprised this hasn't been addressed with how popular this form library is. This requirement of setting a default value means that your forms have to be incredibly verbose, essentially re-defining your schema on the front-end in a format just for Formik to use. I feel like adding the inputs with the correct names should be enough. And then once you do that, it now attaches a bunch of empty values to your data that gets sent to the API. I basically have to have a method that I pass my data into which gets it ready for formik, and then a second method on submit which cleans all the garbage up. This is tedious.
As @coreysnyder expressed, this is a quite a huge problem adding extra redundant code to clean up unnecessary properties.
Imagine a case, where you have a checkout form with ability to select an existing credit card or add a new one. Unless a new one is being added the full form is hidden.
Even if a full form is hidden and won't be used I still have to provide all the initialValues for it and end up with extra properties upon submission. Weird that a form manager does not take into account that some fields can be optional. It's such a common feature.
to solve this issue, must set initial values
initialValues={{
email:""
}}
What about setting the default-values by default, and just provide an extra preventDefaultValue attribute to make it opt out, that the register call / or Component initializes correctly, even without provided defaultValues?
We're looking at ditching required initialValues in Formik v3, but it's not going to work in v2 due to API limitations. For now, one must pass all field values that will exist to initialValues, or update initialValues with newly added form fields and pass enableReinitialize to Formik.
Note reinitializing may affect isDirty, touched, errors and other things, so it may be worth following #2428
For clarity on this warning, I was running into this warning in my app and cleared it through initializing values like so
const initialFormState = {
firstName: this.state.user.firstName || "",
lastName: this.state.user.lastName || "",
email: this.state.user.email || "",
phone: this.state.user.phone || "",
business: this.state.user.business || ""
};
and enabling reinitialization like so:
<Formik
initialValues={initialFormState}
validationSchema={profileSchema}
enableReinitialize={true}
onSubmit={(values, actions) => {
this.updateDetails(values);
}}>
Most helpful comment
You're never instantiating your
values.emailandvalues.password, so yourinputs are receiving undefined values, which React interprets to mean the inputs are uncontrolled.I would suggest implementing (in your
Formikimplementation) themapPropsToValuesmethod to ensure thatemailandpasswordare initiated to empty strings instead of undefined, for example:(Of course, if you wanted to ensure the form populated with values based on your props, you would do it there as well. I'm setting to empty strings here because your example is a login form, where you want the fields to start empty.)