Clearing Formik errors and form data - React Native - react-native

I'm using Formik and was wondering how I go about clearing the errors and form values when leaving a screen.
For example, a user tries to submit the form with no values and the errors are displayed:
When the user then navigates to a different screen and then comes back those errors are still present. Is there a way to clear these? Can I access Formik methods within a useEffect hook as an example?
This is my implementation so far:
export const SignIn = ({route, navigation}) => {
const formValidationSchema = Yup.object().shape({
signInEmail: Yup.string()
.required('Email address is required')
.email('Please provide a valid email address')
.label('Email'),
signInPassword: Yup.string()
.required('Password is required')
.label('Password'),
});
const initialFormValues = {
signInEmail: '',
signInPassword: '',
};
return (
<Formik
initialValues={initialFormValues}
validationSchema={formValidationSchema}
onSubmit={(values, formikActions) => {
handleFormSubmit(values);
}}>
{({handleChange, handleSubmit, errors}) => (
<>
<SignInForm
messages={errors}
navigation={navigation}
handleFormSubmit={handleSubmit}
/>
</>
)}
</Formik>
)
}

The problem here is that a screen does not get unmounted if we navigate to a different screen and the initial values of a Formik form will only be set on screen mount.
I have created a minimal example with one field and I navigate whenever the submit button is executed to a different Screen in a Stack.Navigator.
Notice that the onSubmit function is usually not fired if there are errors in your form fields. However, since I wanted to provide a quick test, I navigate by hand by calling the function directly.
If we navigate back by pressing the onBack button of the navigator, the form fields will be reseted to the default values and all errors will be reseted automatically.
We can trigger this by hand using the Formik innerRef prop and a focus listener.
For testing this, you should do the following.
Type something, and remove it. Notice the error message that is rendered below the input field.
Navigate to the screen using the submit button.
Go back.
Expected result: no error message.
Type something. Expected result: no error message.
Navigate on submit.
Go back.
Expected result: no error message, no content in field.
In principal, this will work with every navigator, e.g. changing a Tab in a Tab.Navigator and it will reset both, the errors and the field's content.
The key part is given by the following code snippet.
const ref = useRef(null)
const initialFormValues = {
signInEmail: '',
signInPassword: '',
};
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
if (ref?.current) {
ref.current.values = initialFormValues
ref.current.setErrors({})
console.log(ref.current)
}
});
return unsubscribe;
}, [navigation, initialFormValues]);
return (
<Formik
innerRef={ref}
isInitialValid={true}
initialValues={initialFormValues}
validationSchema={formValidationSchema}
onSubmit={(values) => {
console.log("whatever")
}}>
...
The full code is given as follows.
import React, { useRef} from 'react';
import { StyleSheet, Text, View,TextInput, Button } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import { Formik } from 'formik';
import * as Yup from 'yup';
export const SignIn = ({route, navigation}) => {
const formValidationSchema = Yup.object().shape({
signInEmail: Yup.string()
.required('Email address is required')
.label('Email'),
});
const ref = useRef(null)
const initialFormValues = {
signInEmail: '',
signInPassword: '',
};
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
if (ref?.current) {
ref.current.values = initialFormValues
ref.current.setErrors({})
console.log(ref.current)
}
});
return unsubscribe;
}, [navigation, initialFormValues]);
function handleFormSubmit(values) {
navigation.navigate("SomeScreen")
}
return (
<Formik
innerRef={ref}
isInitialValid={true}
initialValues={initialFormValues}
validationSchema={formValidationSchema}
onSubmit={(values) => {
console.log("whatever")
}}>
{({handleChange, handleSubmit, errors, values}) => (
<>
<View>
<TextInput
style={{height: 30}}
placeholder={"Placeholder mail"}
onChangeText={handleChange('signInEmail')}
value={values.signInEmail}
/>
{errors.signInEmail ?
<Text style={{ fontSize: 10, color: 'red' }}>{errors.signInEmail}</Text> : null
}
<Button onPress={() => {
handleSubmit()
handleFormSubmit()}} title="Submit" />
</View>
</>
)}
</Formik>
)
}
export function SomeOtherScreen(props) {
return <></>
}
const Stack = createNativeStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Form" component={SignIn} />
<Stack.Screen name="SomeScreen" component={SomeOtherScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});

This should be possible with the useFocusEffect hook from react navigation. It gets triggered on first time and every time the screen comes in focus.
useFocusEffect(
React.useCallback(() => {
// set Formik Errors to empty object
setErrors({});
// Since your clearing errors, you should also reset the formik values
setSignInEmail('');
setSignInPassword('');
return () => {}; // gets called on unfocus. I don't think you need this
}, [])
);

Related

stack from NativeStackNavigator (nested in BottomTabNavigator) resets everytime the BottomTabNavigator changes tabs

Situation:
The react native app has a BottomTabNavigator (react-navigation/material-bottom-tabs) and one of the tabs has a NativeStackNavigator (react-navigation/native-stack).
BottomTabNavigator:
tab1
tab2
tab3:
NativeStackNavigator:
screen1
screen2
Problem:
When I press tab3 (from the BottomTabNavigator) I see screen 1.
If I press a button on screen 1 then it will navigate from screen 1 to screen 2.
When I press tab1 or tab2 and then I press tab3 again then I see screen 1.
I want to see screen 2 (because that's what should be at the top of the stack from the NativeStackNavigator right?).
Did the stack from NativeStackNavigator reset?
Did the whole NativeStackNavigator render again?
What causes this behavior?
Code BottomTabNavigator:
export type MainNavigationBarParam = {
tab1: undefined;
tab2: undefined;
tab3: undefined;
};
const Tab = getMainNavigationBarTabNavigator();
export const tab3Stack = createNativeStackNavigator();
export function MainNavigationBar() {
const sizeToUse = 25;
return (
<Tab.Navigator
screenOptions={defaultScreenOptions()}
barStyle={{backgroundColor: theme.colors.primary}}>
<Tab.Screen
name="tab1"
component={Component1}
/>
<Tab.Screen
name="tab2"
component={Component2}
/>
<Tab.Screen
name="tab3"
component={Component3}
/>
</Tab.Navigator>
);
}
function defaultScreenOptions() {
const screenOptions: any = {
headerShown: false,
tabBarHideOnKeyboard: true,
};
if (Platform.OS === 'web') {
screenOptions.swipeEnabled = false;
}
return screenOptions;
}
Code Component3:
export type Tab3Stack ParamList = {
Screen1: undefined;
Screen2: {id: string; name: string};
};
export default function Component3() {
const [topComponentHeight, setTopComponentHeight] = useState(0);
function onLayout(event: LayoutChangeEvent) {
if ('top' in event.nativeEvent.layout) {
const withTop = event.nativeEvent.layout as unknown as {top: number};
setTopComponentHeight(withTop.top);
}
event;
}
return (
<View style={navContainerStyle(topComponentHeight).navContainer} onLayout={onLayout}>
<tab3Stack.Navigator screenOptions={{headerShown: false}}>
<tab3Stack.Screen
name={'Screen1'}
component={Screen1}
/>
<tab3Stack.Screen
name={'Screen2'}
component={Screen2}
/>
</tab3Stack.Navigator>
</View>
);
}
Update 1
Reproduced the code above for 2 tabs: https://snack.expo.dev/#jacobdel/182747
The problem does not occur on snack, only in my app.
Console in chrome:
No errors or warnings.
Sometimes this is shown, but after disabling the quill editor it doesn't appear anymore
Update 2
Cause is found: getMainNavigationBarTabNavigator(); from BottomTabNavigator
File MainNavigationBarTabNavigator.ts looks like this:
import {createMaterialBottomTabNavigator} from '#react-navigation/material-bottom-tabs';
import {MainNavigationBarParam} from './MainNavigationTabs';
export function getMainNavigationBarTabNavigator() {
return createMaterialBottomTabNavigator<MainNavigationBarParam>();
}
While MainNavigationBarTabNavigator.web.ts looks like this:
import {createMaterialTopTabNavigator} from '#react-navigation/material-top-tabs';
import {MainNavigationBarParam} from './MainNavigationTabs';
export function getMainNavigationBarTabNavigator() {
return createMaterialTopTabNavigator<MainNavigationBarParam>();
}
Snack only works as intended when using MainNavigationBarTabNavigator.ts
Update 3
Stuck..
Replacing the code from MainNavigationBarTabNavigator.web.ts with the code from MainNavigationBarTabNavigator.ts does not show the intended behavior as shown in the Snack example.
There seems to be an issue with the navigation event handling of #react-navigation/material-top-tabs.
If we handle this on our own, then the nested stack is not reset. We can prevent the default navigation action by using a tabPress event listener and calling e.preventDefault.
In your case, this is done as follows for screen2 (which is the nested stack).
<Tab.Screen
name="screen2"
component={Screen2}
listeners={({ navigation, route }) => ({
tabPress: (e) => {
e.preventDefault();
navigation.navigate({ name: route.name, merge: true });
},
})}
/>
Notice that this works fine on the phone, but has some issues on the web. If we navigate fast between screen2 and screen1 multiple times, then the navigation does not work at all. I haven't found the root cause for this issue.
However, we can make this work on the web as well as on the phone by providing a custom top navigation bar using the tabBar prop of the tab navigator. We override the default behavior of the tabPress function.
The original component is exported and is named MaterialTopTabBar. Sadly, it does not provide the possibility to provide a custom onTabPress function via props.
I have forked the component and 'fixed' (without knowing what exactly is going wrong here) the onTabPress function.
import {
useTheme,
} from '#react-navigation/native';
import Color from 'color';
import * as React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { TabBar, TabBarIndicator } from 'react-native-tab-view';
export default function FixedTabBarTop({
state,
navigation,
descriptors,
...rest
}) {
const { colors } = useTheme();
const focusedOptions = descriptors[state.routes[state.index].key].options;
const activeColor = focusedOptions.tabBarActiveTintColor ?? colors.text;
const inactiveColor =
focusedOptions.tabBarInactiveTintColor ??
new Color(activeColor).alpha(0.5).rgb().string();
return (
<TabBar
{...rest}
navigationState={state}
scrollEnabled={focusedOptions.tabBarScrollEnabled}
bounces={focusedOptions.tabBarBounces}
activeColor={activeColor}
inactiveColor={inactiveColor}
pressColor={focusedOptions.tabBarPressColor}
pressOpacity={focusedOptions.tabBarPressOpacity}
tabStyle={focusedOptions.tabBarItemStyle}
indicatorStyle={[
{ backgroundColor: colors.primary },
focusedOptions.tabBarIndicatorStyle,
]}
indicatorContainerStyle={focusedOptions.tabBarIndicatorContainerStyle}
contentContainerStyle={focusedOptions.tabBarContentContainerStyle}
style={[{ backgroundColor: colors.card }, focusedOptions.tabBarStyle]}
getAccessibilityLabel={({ route }) =>
descriptors[route.key].options.tabBarAccessibilityLabel
}
getTestID={({ route }) => descriptors[route.key].options.tabBarTestID}
onTabPress={({ route }) => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!event.defaultPrevented) {
navigation.navigate({ name: route.name, merge: true });
}
}}
onTabLongPress={({ route }) =>
navigation.emit({
type: 'tabLongPress',
target: route.key,
})
}
renderIcon={({ route, focused, color }) => {
const { options } = descriptors[route.key];
if (options.tabBarShowIcon === false) {
return null;
}
if (options.tabBarIcon !== undefined) {
const icon = options.tabBarIcon({ focused, color });
return (
<View style={[styles.icon, options.tabBarIconStyle]}>{icon}</View>
);
}
return null;
}}
renderLabel={({ route, focused, color }) => {
const { options } = descriptors[route.key];
if (options.tabBarShowLabel === false) {
return null;
}
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;
if (typeof label === 'string') {
return (
<Text
style={[styles.label, { color }, options.tabBarLabelStyle]}
allowFontScaling={options.tabBarAllowFontScaling}
>
{label}
</Text>
);
}
return label({ focused, color });
}}
renderBadge={({ route }) => {
const { tabBarBadge } = descriptors[route.key].options;
return tabBarBadge?.() ?? null;
}}
renderIndicator={({ navigationState: state, ...rest }) => {
return focusedOptions.tabBarIndicator ? (
focusedOptions.tabBarIndicator({
state: state,
...rest,
})
) : (
<TabBarIndicator navigationState={state} {...rest} />
);
}}
/>
);
}
const styles = StyleSheet.create({
icon: {
height: 24,
width: 24,
},
label: {
textAlign: 'center',
textTransform: 'uppercase',
fontSize: 13,
margin: 4,
backgroundColor: 'transparent',
},
});
I have used it as follows.
export function MainNavigationBar() {
return (
<Tab.Navigator
tabBar={props => <FixedTabBarTop {...props} />}
screenOptions={defaultScreenOptions()}>
<Tab.Screen
name="screen1"
component={Screen1}
/>
<Tab.Screen
name="screen2"
component={Screen2}
/>
</Tab.Navigator>
);
}
I have updated your snack. You might want to submit an issue on GitHub as well. It feels like a bug.

How can I display a modal with info from a scanned QR code?

Tech stack: Expo, React-Native
Using: expo-barcode-scanner
I am trying to create an app that will scan a QR code and then display the info from the QR code on the screen, preferably in a modal so I can display an image.
I've created an app that will do that... except that it uses an alert function to show the text. How can I change it to a modal? When I try to change it to modal, or return a modal it doesn't work.
My code:
import React, { useCallback, useEffect, useState } from 'react';
import { StyleSheet, Text, View, Image, Button, Modal } from 'react-native';
import { BarCodeScanner } from 'expo-barcode-scanner';
import {createCustomerInformation2} from './src/graphql/mutations'
import logo from './assets/icon.png';
import Scanner from './components/QRScanner';
import custModal from './components/custInfo'
export default function App() {
const [hasPermission, setHasPermission] = useState(null);
const [scanned, setScanned] = useState(false);
// const [modalVisible, setModalVisible] = useState(false);
useEffect(() => {
(async () => {
const { status } = await BarCodeScanner.requestPermissionsAsync();
setHasPermission(status === 'granted');
})();
}, []);
const handleBarCodeScanned = ({ type, data }) => {
setScanned(true);
var newData = JSON.parse(data)
// return(
// <View>
// <Modal>
// <View>
// <Text>Test</Text>
// </View>
// </Modal>
// </View>
// )
alert(`
Customer: ${newData.name}
Email: ${newData.email}
Phone: ${newData.phone}
Favorite Drink: ${newData.favoriteDrink}
`);
// createCustomerInformation2(newData)();
};
if (hasPermission === null) {
return <Text>Requesting for camera permission</Text>;
}
if (hasPermission === false) {
return <Text>No access to camera</Text>;
}
return (
<View id="view" style={styles.container}>
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={StyleSheet.absoluteFillObject}
// If you want to use the front facing or rear facing, include type={'front'} or put 'back'
/>
{scanned && <Button title={'Tap to Scan Again'} onPress={() => setScanned(false)} />}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
},
});
Instead of returning that JSX in the onBarCodeScanned handler, you'll need to include it in the returned JSX of your App. You'll want to conditionally hide the modal when no value is present. You'll then populate it by assigning the scanned value to a state variable in the event handler and displaying that value in the modal.

React Native - How to submit a Formik from Header

I'm new to Formik and React Native. Currently, my goal is to submit a form via the Navigation header. All the examples I found are to submit the form from the screen page itself. I need help figure out how to properly use the handleSubmit and onSubmit property and setParams to pass the value to the navigation header properly.
Right now, I'm stuck by not sending the values from the form to the useCallBack hook.
import React, {useState, useEffect, useCallback} from 'react';
import {StyleSheet, View, Button, Text} from 'react-native';
import {Input} from 'react-native-elements';
import {useDispatch} from 'react-redux';
import Colors from '../../constants/Colors';
import {
HeaderButtons,
HeaderButton,
Item,
} from 'react-navigation-header-buttons';
import {Ionicons} from '#expo/vector-icons';
import {CommonActions} from '#react-navigation/native';
import * as prodActions from '../../store/actions/products';
import {Formik} from 'formik';
import * as Yup from 'yup';
const AddProduct = ({navigation}) => {
const dispatch = useDispatch();
const submitHandler = useCallback(() => {
dispatch(prodActions.addProduct(value));
}, [value]);
useEffect(() => {
navigation.dispatch(CommonActions.setParams({submitForm: submitHandler}));
}, [submitHandler]);
return (
<View style={styles.screen}>
<Formik
initialValues={{title: '', description: '', imageUrl: ''}}
validationSchema={Yup.object({
title: Yup.string().required('please input your title'),
description: Yup.string().required('please input your description'),
imageUrl: Yup.string()
//.email('Please input a valid email.')
.required('Please input an email address.'),
})}
onSubmit={submitHandler}
>
{({
handleChange,
handleBlur,
handleSubmit,
values,
touched,
errors,
}) => (
<View>
<Input
label="Title"
labelStyle={{color: Colors.accent}}
onChangeText={handleChange('title')}
onBlur={handleBlur('title')}
value={values.title}
// errorMessage={errors.title}
/>
{touched.title && errors.title ? (
<Text style={styles.error}>{errors.title}</Text>
) : null}
<Input
label="Description"
labelStyle={{color: Colors.accent}}
onChangeText={handleChange('description')}
onBlur={handleBlur('description')}
value={values.description}
/>
{touched.description && errors.description ? (
<Text style={styles.error}>{errors.description}</Text>
) : null}
<Input
label="Image URL"
labelStyle={{color: Colors.accent}}
onChangeText={handleChange('imageUrl')}
onBlur={handleBlur('imageUrl')}
value={values.imageUrl}
/>
{touched.imageUrl && errors.imageUrl ? (
<Text style={styles.error}>{errors.imageUrl}</Text>
) : null}
<Button onPress={handleSubmit} title="Submit" />
</View>
)}
</Formik>
</View>
);
};
const IoniconsHeaderButton = (props) => (
<HeaderButton
IconComponent={Ionicons}
iconSize={23}
color="white"
{...props}
/>
);
export const AddProductHeaderOptions = (navData) => {
const updateForm = navData.route.params.submitForm;
return {
headerRight: () => {
return (
<HeaderButtons HeaderButtonComponent={IoniconsHeaderButton}>
<Item title="add" iconName="save-outline" onPress={updateForm} />
</HeaderButtons>
);
},
};
};
export default AddProduct;
const styles = StyleSheet.create({
screen: {
flex: 1,
marginHorizontal: 20,
marginTop: 20,
},
error: {
marginLeft: 8,
fontSize: 14,
color: 'red',
},
});
You're probably using "value" variable with an empty value. You must ref all your form data, inside your < Formik > , let's use street, for an example:
value={values.street}
error={touched.street && !isSubmitting && errors.street}
returnKeyType="next"
onSubmitEditing={() => refs.number.current?.focus()}
ref={refs.street}
So declare this ref variable first:
const refs = {
street: useRef<any>(null),
number: useRef<any>(null),
zipCode: useRef<any>(null),
//....
If you don't wanna go with REF, so at least declare the variable and try it, like that:
const [value, setValue] = useState();
Also, the VALUE name is not a good variable name because there are a lot of native things using it. But, considering that you must have taken this from an example, use useState with your form or object and you should be good.
const [formx, setFormx] = useState();
For those who use React-Navigation 5 and Formik 1.5.x+ Here is a good solution.
Declare a ref variable outside/inside of your function component to later Attach to your
let formRef;//or you can use const formRef = useRef(); inside function component
If you declare formRef outside your function component use React hook {useRef} to update the ref
formRef = useRef();
Render
<Formik innerRef={formRef} />
And finally in your header button call this
navigation.setOptions({
headerRight: () => (
<Button onPress={() => {
if (formRef.current) {
formRef.current.handleSubmit()
}
}}/>
)
});
Another solution, which I think could be better is to use the useFormik, which gives you access to all the form handlers and form meta:
const {
handleSubmit,
handleChange,
isValid,
} = useFormik({
initialValues: {...},
validationSchema: yupValidationSchema,
onSubmit: (formValues) => {
//save here...
}
});
Then you can simply pass the handleSubmit reference to your right header navigation using the useLayoutEffect as proposed in the official documentation:
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => {
return (
<HeaderButtons HeaderButtonComponent={IoniconsHeaderButton}>
<Item title="add" iconName="save-outline" onPress={handleSubmit}/>
</HeaderButtons>
);
}
});
});
This approach is clear in my opinion, since you don't need to handle all the structural disadvantages of using the renderProps approach with
<Formkik>
{({ handleSubmit }) => (
jsx here...
)}
</Formik>

React Navigation 5 headerRight button function called doesn't get updated states

In the following simplified example, a user updates the label state using the TextInput and then clicks the 'Save' button in the header. In the submit function, when the label state is requested it returns the original value '' rather than the updated value.
What changes need to be made to the navigation headerRight button to fix this issue?
Note: When the Save button is in the render view, everything works as expected, just not when it's in the header.
import React, {useState, useLayoutEffect} from 'react';
import { TouchableWithoutFeedback, View, Text, TextInput } from 'react-native';
export default function EditScreen({navigation}){
const [label, setLabel] = useState('');
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
<TouchableWithoutFeedback onPress={submit}>
<Text>Save</Text>
</TouchableWithoutFeedback>
),
});
}, [navigation]);
const submit = () => {
//label doesn't return the updated state here
const data = {label: label}
fetch(....)
}
return(
<View>
<TextInput onChangeText={(text) => setLabel(text) } value={label} />
</View>
)
}
Label should be passed as a dependency for the useLayouteffect, Which will make the hook run on changes
React.useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
<TouchableWithoutFeedback onPress={submit}>
<Text>Save</Text>
</TouchableWithoutFeedback>
),
});
}, [navigation,label]);
Guruparan's answer is correct for the question, although I wanted to make the solution more usable for screens with many TextInputs.
To achieve that, I added an additional state called saving, which is set to true when Done is clicked. This triggers the useEffect hook to be called and therefore the submit.
export default function EditScreen({navigation}){
const [label, setLabel] = useState('');
const [saving, setSaving] = useState(false);
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
<TouchableWithoutFeedback onPress={() => setSaving(true)}>
<Text>Done</Text>
</TouchableWithoutFeedback>
),
});
}, [navigation]);
useEffect(() => {
// Check if saving to avoid calling submit on screen unmounting
if(saving){
submit()
}
}, [saving]);
const submit = () => {
const data = {label: label}
fetch(....)
}
return(
<View>
<TextInput onChangeText={(text) => setLabel(text) } value={label} />
</View>
)
}

React Navigation autoFocus when using TabNavigator

In react-navigation, what is the best way to handle a tab that has a form with an autoFocus input that automatically pulls up the keyboard?
When the Navigator initializes all the screens, it automatically displays the keyboard even though the screen without the autoFocus element is showing first.
I want it to open the keyboard when I'm on the tab with the form, but close it when I leave that view.
Here is an example (and an associated Gist):
App.js
const AppNavigator = TabNavigator( {
listView: { screen: TheListView },
formView: { screen: TheFormView }
} )
TheFormView.js
const TheFormView = () => {
return (
<View style={{ marginTop: 50 }}>
<TextInput
autoFocus={ true }
keyboardType="default"
placeholder="Blah"
/>
</View>
)
}
TheListView.js
const TheListView = () => {
return (
<View style={{ marginTop: 50 }}>
<Text>ListView</Text>
</View>
)
}
You should use lazy on TabNavigator config: https://github.com/react-community/react-navigation/blob/master/docs/api/navigators/TabNavigator.md#tabnavigatorconfig
This prevents the screen from being initialised before it's viewed.
Also consider having some kind of state management or look for Custom Navigators (https://reactnavigation.org/docs/navigators/custom) for setting the autoFocus prop as true only when TheFormView is navigated to.
This answer was out of date for me as of April 2020, but this worked for me:
import { useFocusEffect, } from "#react-navigation/native"
import React, { useEffect, useState, } from "react"
...
const CreateProfileScreen = ({ navigation, }) => {
const [safeToOpenKeyboard, setSafeToOpenKeyBoard] = useState(false)
...
useFocusEffect(
React.useCallback(() => {
console.log("Navigated to CreateProfileScreen")
setSafeToOpenKeyBoard(true)
return () => {
console.log("Navigated away from CreateProfileScreen")
setSafeToOpenKeyBoard(false)
}
}, [])
)
...
return (<TextInput autoFocus={safeToOpenKeyboard}/>)
}