I am adding formik with typescript and react to my project, so i am using withFormik hook as an HOC, the issue i am facing is that i am not able to set initialvalue (defined in hoc) to the response i am getting from the api
login.tsx
import { FormikProps } from "formik";
export interface FormValues {
title: string;
}
export const Login: React.FC<FormikProps<FormValues>> = (props) => {
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((response) => response.json())
.then((json) => setTitle(json.title));
}, []);
const [title, setTitle] = useState<string | null>(null);
const { handleSubmit, handleBlur, handleChange, touched, errors, values } =
props;
return (
<Container component="main" maxWidth="xs">
<div className={classes.paper}>
<form className={classes.form} noValidate onSubmit={handleSubmit}>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="title"
label="Title"
name="title"
value={values.title}
autoFocus
autoComplete="off"
onChange={handleChange}
onBlur={handleBlur}
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Sign In
</Button>
</form>
</div>
</Container>
);
};
login.hoc.tsx
import { Form, FormikProps, FormikValues, useFormik, withFormik } from "formik";
import { FormValues, Login } from "./index";
interface MyFormikProps {
}
export const LoginView = withFormik<MyFormikProps, FormValues>({
enableReinitialize: true,
mapPropsToValues: (props) => {
return { title:"" };
},
handleSubmit: (values) => {
console.log(values);
},
})(Login);
This works perfectly fine, but my issue is that suppose i hit an api in didmount of login.tsx and then you can see i set the "title" to response what i am getting from api
Now I want to set initialValue of "title" to what i am getting as response from api
Your current setup cannot possibly work because the hierarchy is incorrect. The withFormik
HOC creates the initial value from the props
of the component. But the API response is never in the props
of Login
. It's saved to a state inside of the Login
component. An HOC, which is outside, cannot access it.
You could have a setup where withFormik
gets the correct API data in props
, but you would need to move the API fetching up to a higher level.
// component which renders the form, no more API fetch
export const Login: React.FC<FormikProps<FormValues>> = (props) => {/*...*/}
// data passed down from API
interface MyFormikProps {
title: string | null;
}
// component which sets up the form
export const LoginView = withFormik<MyFormikProps, FormValues>({
enableReinitialize: true,
mapPropsToValues: (props) => ({
title: props.title ?? "" // fallback if null
}),
handleSubmit: (values) => {
console.log(values);
}
})(Login);
// component which loads the data
export const ApiLoginForm = () => {
const [title, setTitle] = useState<string | null>(null);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((response) => response.json())
.then((json) => setTitle(json.title));
}, []);
return <LoginView title={title} />;
};
The value from your API will take time to resolve. It's tricky to use it as an initial value because the initial value must be present as soon as the form is mounted. What we are doing here is falling back to using an empty string ''
as an initial value. Then once the API response loads, the form resets with the new initial value because you've used the enableReinitialize
setting.
This will reset the whole form, and could cause a bad user experience if the API response is slow. You wouldn't want to override user inputs if the user entered text in form before the response came back. You could disable the field until the response is available. Or you could make the modification conditional by checking if touched.title
is true
or if values.title
. If you want to get more sophisticated about how you modify the form, then read on...
You can make use of the setFieldValue
prop from FormikProps
to imperatively modify any value of your field. Instead of changing the initial value, you can change the current value by calling setFieldValue
.
This approach is more in line with your current setup as it allows you to keep the API calls inside of Login
. We don't need the local title
state. Instead of calling setTitle
with the resolved response, you can call setFieldValue
.
export const Login: React.FC<FormikProps<FormValues>> = (props) => {
// access setFieldValue from props
const { setFieldValue, handleSubmit, handleBlur, handleChange, touched, errors, values } =
props;
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((response) => response.json())
// could make this conditional, or add extra logic here
.then((json) => setFieldValue("title", json.title));
}, []);
return (
/* same JSX as before */
)
};