Formik Initial Values out of Sync with Redux - javascript

This form is used to edit a user profile. It should load with up to date user data for the initial values. To handle this, I've added a redux fetchUser action in useEffect and set the initial values of the form to the currentUser in the redux store. fetchUser hits a database endpoint and updates the currentUser in the redux store.
However, if I submit the form and reload the page, the initial data is not updated.
I'm not sure why my initial values/form is not up to date with my redux store. The form will update with the right data if I navigate away from it and navigate back to it twice.
My assumption is that the form is not resetting the initial value each time the component is rendering. It looks like that data is persisting. I have tried passing in an object to resetForm() to force the initial values to update, but this results in the same issue. If
Here is the full react component:
import { Formik, Form, Field, ErrorMessage } from "formik"
//redux action that fetches current user
import { fetchUser } from "../../actions"
import { connect } from "react-redux"
import profileSchema from "./yupSchema"
import updateProfile from "./updateAxiosCall"
const submitData = (data, resetForm, tagNames) => {
const newProfile = { ...data}
//end point call - working as intended
updateProfile(newProfile)
resetForm()
}
const ProfileForm = ({ currentUser, fetchUser }) => {
const [formData, setFormData] = useState(null)
useEffect(() => {
//grab user on component render
fetchUser()
setFormData({
linkedin: currentUser.linkedin,
github: currentUser.github,
instagram: currentUser.instagram,
facebook: currentUser.facebook,
twitter: currentUser.twitter,
header: currentUser.header,
bio: currentUser.bio,
})
}, [])
if (formData === null) {
return <div>Not ready</div>
} else {
return (
<Formik
initialValues={formData}
validationSchema={profileSchema}
onSubmit={(values, { resetForm }) => {
submitData(values, resetForm, tagNames)
}}
>
{({ errors, resetForm, handleChange, touched, values, onSubmit }) => (
<Form
style={{
display: "flex",
flexDirection: "column",
width: "100%",
}}
autoComplete="off"
>
//This is up to date
<h1>{currentUser.header}</h1>
<TextField
error={errors.header && touched.header}
onChange={handleChange}
name="header"
variant="outlined"
value={values.header || ""}
id="header"
margin="normal"
label="header"
helperText={
errors.header && touched.header ? errors.header : null
}
/>
{/* ...additional <TextField> components */}
<Button
style={{ margin: "10px 0px" }}
size="large"
margin="normal"
type="submit"
variant="contained"
color="primary"
>
Save
</Button>
</Form>
)}
</Formik>
)
}
}
function mapStateToProps(state) {
return {
currentUser: state.currentUser,
}
}
export default connect(mapStateToProps, { fetchUser })(ProfileForm)

put enableReinitialize={true} as a Formik property.
so this:
<Formik
enableReinitialize={true}
initialValues={formData}
...

#Jared answer works for me by using enableReinitialize:true hook
const formik = useFormik({
initialValues: {
title: "",
text: "",
authorid: `${user.id}`,
cat_id: '1',
},
validateOnBlur: true,
onSubmit,
enableReinitialize: true,
});

You might need to separate your useEffect hook into 2 useEffects. One hook for the "componentDidMount" cycle (e.g. []) and another watching changes to currentUser.
Example:
const [formData, setFormData] = useState(null)
useEffect(() => {
//grab user on component render
fetchUser()
}, [])
useEffect(() => {
setFormData({
linkedin: currentUser.linkedin,
github: currentUser.github,
instagram: currentUser.instagram,
facebook: currentUser.facebook,
twitter: currentUser.twitter,
header: currentUser.header,
bio: currentUser.bio,
})
}, [ currentUser ])

Related

Giving formik reference to custom hook

I'm trying to separate formik validation to a custom hook. The hook returns yup falidation schema, but it needs functions from formik bag to build that schema. How could I get the reference to formik in the component root, and pass it down to custom hook? The component looks like this:
export const MyView = () => {
let formikRef: FormikProps<FormData> | undefined = undefined;
const { validationSchema } = useValidationSchema(formikRef);
...
return (
<Formik
initialValues={initialValues}
onSubmit={submitForm}
enableReinitialize={true}
validateOnChange={false}
validateOnBlur={false}
validationSchema={validationSchema}
>
{(formik) => {
formikRef = formik;
return (
<>
...
</>
);
}}
</Formik>
);
}
This method of getting the ref does not work. The ref actually receives the correct value, but the custom hook only receives initial value (undefined). I also tried storing the ref to state, but that leads to infinite render loop. Additionally I tried using innerRef, but again, custom hook always receives undefined reference.
EDIT:
Managed to get it working by using useFormik and FormikProvider. Now there's a problem with reference order: formik uses validationSchema and other way around. I solved this by adding a function to custom hook:
const { validationSchema, setFormik } = useValidationSchema();
const formik = useFormik<FormData>({
initialValues: initialValues,
onSubmit: submitForm,
enableReinitialize: true,
validateOnChange: false,
validateOnBlur: false,
validationSchema: validationSchema,
});
setFormik(formik);
Is there a more elegant solution?
Ended up with
export const MyView = () => {
const { validationSchema, setFormikForValidation } = useValidationSchema();
const formik = useFormik<FormData>({
initialValues: initialValues,
onSubmit: submitForm,
enableReinitialize: true,
validateOnChange: false,
validateOnBlur: false,
validationSchema: validationSchema,
});
setFormikForValidation(formik);
return (
<FormikProvider formik={formik}>
...
</FormikProvider>
);
}
You can you useFormikContext hook. This is a hook provided by formik and you can use it in any of the children of a formik instance.
Your form:
import Formik from 'formik';
const Form = (props) => {
return (
<Formik {...props}>
<FormBody/>
</Formik>
);
}
your form body:
import {useFormikContext} from 'formik';
const FormBody = () => {
const {values, ...etc} = useFormikContext();
return (
...
);
}

get value when updated,outside of formik using useRef()

I'm trying to get the value of an autocomplete field outside of my formik component everytime it changes, and i did something like this:
const formRef=useRef<any>(null);
useEffect(() => {
console.log("eee!!",formRef.current?.values?.flagTR)
},[formRef.current?.values]);
return (
<Formik
initialValues={initialValues}
onSubmit={handleSumbit}
enableReinitialize={true}
validateOnChange={true}
validationSchema={ProduitSchema}
innerRef={formRef}
>
the useEffect is triggered the first time the component renders,but then when i change the value of the autocomplete the useEffect doesn't get triggered.
what should i do in order to trgger useEffect everytime a value from formik changes?
Hey so I don't think the approach you are going with here is going to work, it's really not what the useRef hook is for.
The correct approach for what it looks like you want to do is to properly use Formik's context to mapValuesToProps or get access to the values, errors, and validation states.
You can use withFormik() to set up your initial form values and mapValuesToProps. Then you can use within the form component formik's useFormikContext() which will give you access to values, errors, setValues, etc
export default withFormik({
handleSubmit: () => {},
validateOnChange: true,
validateOnBlur: true,
validateOnMount: true,
mapPropsToValues: () => ({ name: '', email: '' }),
initialValues: { name: '', email: '' }
})(MyFormComponent)
In the MyFormComponent you can then call useFormikContext() and do whatever you want when the values change.
const { setValues, values, isValid, validateForm } = useFormikContext()
If for some reason this is not what you want to do, or it does not solve your problem, the only way to achieve what you want in React alone is to useState and and setState each time onChange eg
const MyFormComponent = () => {
const [nameField, nameMeta] = useField(name)
const [emailField, emailMeta] = useField(email)
const [formState, setFormState] = useState({name: '', email: ''})
return (
<Formik
enableReinitialize
validateOnBlur={false}
initialValues={formState}
onSubmit={() => {}}
>
{props => {
const onChange = e => {
const targetEl = e.target
const fieldName = targetEl.name
setFormState({
...formState,
[fieldName]: targetEl.value
})
return props.handleChange(e)
}
return (
<>
<input {...nameField} onChange={onChange}>
<input {...emailField} onChange={onChange}>
</>
)
</Formik>
)

How to dynamically set defaultValue in react-hook-form

I have a page to edit Product, props value are taken from an API and it's coming from parent component. How do I set this value to my Datepicker input? Because defaultValue from react-hook-form is cached at the first render within the custom hook, so I'm getting null.
const ProductEdit = (props) => {
const { product } = props;
const { control, register, handleSubmit, formState: { errors } } = useForm();
........
<Controller
control={control}
name='dateLaunch'
defaultValue={() => {
console.log(product); // return null
}}
render={({ field }) => (
<DatePicker
selected={field.value}
onChange={(date) => field.onChange(date)}
/>
)}
/>
}
defaultValues is cached at the first render within the custom hook
You have to use reset here and call it when product changed via useEffect. I assume, that product has a property named dateLaunch.
const ProductEdit = (props) => {
const { product } = props;
const { control, register, handleSubmit, reset, formState: { errors } } = useForm();
useEffect(() => {
reset(product)
}, [product]);
........
<Controller
control={control}
name='dateLaunch'
defaultValue={null}
render={({ field }) => (
<DatePicker
selected={field.value}
onChange={(date) => field.onChange(date)}
/>
)}
/>
you can use register
const ProductEdit = (props) => {
const { product } = props;
const { control, register, handleSubmit, formState: { errors } } = useForm({
defaultValues: {
inputOneName: props.name,
inputTwoName: props.lastname
}
});

cant get the updated profile info with firebase after signIn with email

I am currently developping an app using react native and i could manage to have a login and sign up page but my problem is the following.
After create user with email and password using firebase method, i would like to update the profile info (displayName) in order to display it in the home page.
But it seems that the page redirect to the home page after sign up without the update done
I have set a listener on my navigator stack to display login/signup pages or Home page regarding the value of user.
the Update is correctly set if i close the app and reload it after the first sign up success.
my code is the following
the main stack code
function onAuthStateChange(user) {
setUser(user);
if (initializing) setInitializing(false);
}
useEffect(() => {
const subscriber = auth().onAuthStateChanged(onAuthStateChange);
return subscriber;
}, [];
if (initializing) {
return <LoadingScreen />
if (!user) {
return (
<NavigationCOntainer>
<AuthStackScreen />
</NavigationCOntainer>
);
}
return (
<NavigationCOntainer>
<DrawerStackScreen />
</NavigationCOntainer>
the code to handle the sign up method :
function handgleRegister() {
auth()
.createUserWithEmailAndPassword(email, password)
.then(userCred => {
return userCred.user.updateProfile({
displayName: name,
});
})
.catch(e => {
...
...
});
}
the code in the Home page, i also set a listener of user.
I also have tried by calling the method : auth().currentUser;
But i got the same problem, the displayName at the first display was set to null and i had to close and reload the app to be able to display it.
function onAuthStateChange(user) {
console.log(user);
}
useEffect(() => {
const subscriber = auth().onAuthStateChanged(onAuthStateChange);
return subscriber;
}, [];
the result of the log shows that displayName is null so i assume the sign up process redirect to the home before updating the value.
Does someone has an idea how i can manage to get the update value on the Home page screen?
Thanks in advance
The user's profile information is gotten from their ID token, which Firebase gets then the user signs in (or is created) and then automatically refreshes every hour. If the user's profile is updated within that hour, the client may be showing outdated profile information until it refreshes the token.
You can force the refresh of a token by:
Signing the user out and in again.
Calling User.reload or User.getIdToken(true).
Both of these cases force the client to refresh the ID token, and thus get the latest profile from the servers.
try this.
function handgleRegister() {
auth()
.createUserWithEmailAndPassword(email, password)
.then(userCred => {
userCred.user.updateProfile({
displayName: name,
}).then(()=>{
setUser(auth().currentUser());
};
})
.catch(e => {
...
...
});
}
here's my signup code. I'm just update the profile with the email address right now since i don't have another input for display name. I'm also using redux toolkit.
import React, { Component, useEffect } from "react";
import { View } from "react-native";
import { Button, Input } from "react-native-elements";
import Icon from "react-native-vector-icons/FontAwesome";
import { Dimensions } from "react-native";
import auth from "#react-native-firebase/auth";
import { useDispatch, useSelector } from "react-redux";
import {
clearLoginInfo,
updateLoginInfo,
signInWithEmailAndPassword,
} from "../login/LoginSlice";
export default function SignupScreen(props) {
const { navigation } = props;
const [username, setUsername] = React.useState("");
const [password, setPassword] = React.useState("");
const [passwordErrorMsg, setPasswordErrorMsg] = React.useState("");
const [usernameErrorMsg, setUsernameErrorMsg] = React.useState("");
const [securedText, setSecuredTtext] = React.useState(true);
const dispatch = useDispatch();
function onAuthStateChanged(user) {
console.log("on user change", user);
}
useEffect(() => {
const subscriber = auth().onAuthStateChanged(onAuthStateChanged);
return subscriber; // unsubscribe on unmount
}, []);
const signIn = (e) => {
auth()
.createUserWithEmailAndPassword(e.username, e.password)
.then((user) => {
console.log("user = ", user);
user.user
.updateProfile({
displayName: e.username,
photoURL: "abc.com",
})
.then(() => {
const currentUser = auth().currentUser;
console.log("current user ", auth().currentUser);
dispatch(
updateLoginInfo({
displayName: currentUser.displayName,
photoURL: currentUser.photoURL,
})
);
});
})
.catch((error) => {
if (error.code === "auth/email-already-in-use") {
setUsernameErrorMsg("That email address is already in use!");
}
if (error.code === "auth/invalid-email") {
setUsernameErrorMsg("That email address is invalid!");
}
if (error.code === "auth/weak-password") {
setPasswordErrorMsg("please choose a stronger password");
}
console.error(error);
});
};
const handlePasswordChange = (val) => {
setPassword(val);
};
const handleUserNameChange = (val) => {
setUsername(val);
};
const doSignin = () => {
signIn({ username, password });
};
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignContent: "center",
alignItems: "center",
}}
>
<Input
placeholder="Email"
errorMessage={usernameErrorMsg}
value={username}
leftIcon={{ type: "font-awesome", name: "envelope" }}
onChangeText={handleUserNameChange}
/>
<Input
placeholder="Password"
errorMessage={passwordErrorMsg}
errorStyle={{ color: "red" }}
value={password}
leftIcon={<Icon name="lock" size={40} color="black" />}
rightIcon={
securedText ? (
<Icon
name="eye"
size={32}
onPress={() => setSecuredTtext(!securedText)}
color="black"
/>
) : (
<Icon
name="eye-slash"
size={32}
onPress={() => setSecuredTtext(!securedText)}
color="black"
/>
)
}
onChangeText={handlePasswordChange}
secureTextEntry={securedText}
/>
<Button
title="Sign up"
onPress={() => doSignin()}
buttonStyle={{ backgroundColor: "#eda621" }}
style={{ width: Dimensions.get("window").width * 0.5 }}
/>
<Button
title="Back to Sign in"
style={{ width: Dimensions.get("window").width * 0.5, marginTop: 10 }}
onPress={() => navigation.navigate("Signin")}
/>
</View>
);
}
listener:
useEffect(()=>{
if (user != null && user.displayName != '') {
navigation.navigate("Home");
},
[user]});

Get null for innerRef of Formik

When I try to get reference on formik, I get null in current field of ref object.
const formikRef = useRef();
...
<Formik
innerRef={formikRef}
initialValues={{
title: '',
}}
onSubmit={(values) => console.log('submit')}
>
And next:
useEffect(() => {
console.log(formikRef);
}, []);
Get:
Object {
"current": Object {
"current": null,
},
}
How can I fix this problem?
Help please.
If you want to call submit function outside Formik, you can use useImperativeHandle. Document
// Children Component
const Form = forwardRef((props, ref) => {
const formik = useFormik({
initialValues,
validationSchema,
onSubmit,
...rest // other props
})
useImperativeHandle(ref, () => ({
...formik
}))
return ** Your form here **
})
and using:
// Parent Component
const Parent = () => {
const formRef = useRef(null)
const handleSubmitForm = (values, actions) => {
// handle logic submit form
}
const onSubmit = () => {
formRef.current.submitForm()
}
return (<>
<Form ref={formRef} onSubmit={handleSubmitForm} />
<button type="button" onClick={onSubmit}>Submit</button>
</>)
}
Read the ref only when it has value, and add the dependency in useEffect on the ref.
useEffect(() => {
if (formikRef.current) {
console.log(formikRef);
}
}, [formikRef]);
Remember, that refs handle it's actual value in .current property.
What worked for me was adding variables inside useEffect's [].
For my case, it was [ref.current, show].
Add an if(ref.current) {...} before any ref.current.setFieldValue in useEffect body as well or ref.current?.setFieldValue.
This cost me several hours, I hope you're better off.

Categories

Resources