Reputation: 1482
I've got this simple component Login:
function Login() {
const [isFormValidState, setIsFormValidState] = React.useState(false);
const [credentialState, setCredentialState] = React.useState();
function getFormErrors(errors: any, dirty: boolean) {
setIsFormValidState(!Object.keys(errors).length && dirty);
}
function getFormValues(values: any) {
setCredentialState(values);
}
function doAction() {
//credentialState rest call...
}
return (
<View>
<Text>Login</Text>
<UserCredentialForm getFormValues={getFormValues} getFormErrors={getFormErrors}/>
<Button title='Entra' disabled={!isFormValidState} onPress={doAction}/>
</View>
);
}
Which calls UserCredentialForm:
export default function UserCredentialForm({ getFormValues, getFormErrors }) {
[...]
return (
<Formik innerRef={formRef} validationSchema={formSchema} initialValues={state.form} onSubmit={() => { }}>
{({ handleChange, values, touched, errors, dirty }) => {
getFormValues(values);
getFormErrors(errors, dirty);
return <React.Fragment>
// <TextInput/>....
</React.Fragment>
}}
</Formik>
);
[...]
}
While navigating in my app I've got this error:
react native cannot update a component Login while rendering a different component Formik.
Then it points me to the error in the setCredentialState
inside getFormValues
handler in Login component.
I've resolved this using a ref instead of a state, but the problem itself is unsolved to me.
What if I need to update my parent component view after a child event?
Upvotes: 5
Views: 2545
Reputation: 91
I got a similar error while using reactNativeNavigation,
I had a TouchbleOpacity with an onPress event handler that navigates to a different screen
however I had it written like this ...
<TouchableOpacity onPress={navigation.navigate("OtherScreen")}>...
which obviously calls the function to execute even though it hadn't been Touched yet
So I corrected it this way...
<TouchableOpacity onPress={()=> navigation.navigate("OtherScreen")}>...
which doesn't call the function.
This answer is for people who may experience similar errors, even though the code has nothing to do with settingState
Upvotes: 0
Reputation: 2905
You are calling getFormValues
and getFormErrors
inside a callback provided by Formik, that means you cannot wrap them inside an effect hook to suppress this warning or it will violate rules of hooks.
I faced the same issue in React JS and got rid of it by using the following: approach.
I used useFormik hook as an alternate.
and afterwards I refactored Formik form into a new component from where I made state changes to parent component. This way I neither violated rules of hooks nor got this warning.
Also in the that newly refactored component you might need useFormikContext and useField
Upvotes: 0
Reputation: 166
The reason for that error is because you call setState
inside render()
. The call getFormValues(values)
, which set the state of credentialState
is called inside the render.
When the state is set, the Login
component get rerendered, thus recreating a new function of getFormValues
. As this is used as the prop of UserCredentialForm
, it also causes that component to rerender, which causes the render prop inside Formik
to calls again, which calls getFormValues
causing the state change, causing an infinite loop.
One solution you can try is to add useCallback to the two functions, which prevent them to have new identities after the state changes and consequently change the props, thus creating infinite rerender.
function Login() {
const [isFormValidState, setIsFormValidState] = React.useState(false);
const [credentialState, setCredentialState] = React.useState();
const getFormErrors = useCallback(function getFormErrors(errors: any, dirty: boolean) {
setIsFormValidState(!Object.keys(errors).length && dirty);
}, []);
const getFormValues = useCallback(function getFormValues(values: any) {
setCredentialState(values);
}, []);
function doAction() {
//credentialState rest call...
}
return (
<View>
<Text>Login</Text>
<UserCredentialForm getFormValues={getFormValues} getFormErrors={getFormErrors}/>
<Button title='Entra' disabled={!isFormValidState} onPress={doAction}/>
</View>
);
}
However, there is still an issue and that is the identity of values
may not be stable and by setting it to state, it will keep causing rerender. What you want to do is to tell UserCredentialForm
not to rerender even when that state changes, and since the state is not used as a prop in UserCredentialForm
, you can do that with React.memo.
export default React.memo(function UserCredentialForm({ getFormValues, getFormErrors }) {
[...]
return (
<Formik innerRef={formRef} validationSchema={formSchema} initialValues={state.form} onSubmit={() => { }}>
{({ handleChange, values, touched, errors, dirty }) => {
getFormValues(values);
getFormErrors(errors, dirty);
return <React.Fragment>
// <TextInput/>....
</React.Fragment>
}}
</Formik>
);
[...]
})
Upvotes: 2
Reputation: 6967
Simple example can be like
UserCredentialForm:
// Formik x React Native example
import React from 'react';
import { Button, TextInput, View } from 'react-native';
import { Formik } from 'formik';
export const MyReactNativeForm = ({onSubmit}) => (
<Formik
initialValues={{ email: '' }}
onSubmit={values => onSubmit(values)}
>
{({ handleChange, handleBlur, handleSubmit, values }) => (
<View>
<TextInput
onChangeText={handleChange('email')}
onBlur={handleBlur('email')}
value={values.email}
/>
<Button onPress={handleSubmit} title="Submit" />
</View>
)}
</Formik>
);
Usage like
function Login() {
function doAction(values) {
console.log(values);
//credentialState rest call...
}
return (
<View>
....
<UserCredentialForm onSubmit={doAction} />
....
</View>
);
}
Upvotes: -1
Reputation: 180
I think you got an unlimited loop of rendering,
you setState by getFormValues and the Login component re-render make UserCredentialForm re-render too, so it call getFormValues again and again
You can call getFormValues(values) in a useEffect hook after values of formik update
Upvotes: 1