React Navigation V5: Navigate to different Screen - javascript

I am using React-Native-Paper Menu on the header to show two items: Logout and Profile
However, I can't seem to navigate to different screen when pressing
Logout item.
I have included snack workable example, code snippet, and screenshot below:
Snack Example
Code Snippet:
App.js
import { TextInput, Button, Menu, Divider, Provider } from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import AntDesign from 'react-native-vector-icons/AntDesign';
import Register from './Register';
import Logout from './Logout';
const Stack = createStackNavigator();
const CustomMenu = () => {
const [showMenu, setShowMenu] = React.useState(false);
return (
<View style={{}}>
<Menu
visible={showMenu}
onDismiss={() => setShowMenu(false)}
anchor={
<TouchableOpacity onPress={() => setShowMenu(true)}>
<MaterialCommunityIcons
name="earth"
size={30}
style={{ color: 'black' }}
/>
</TouchableOpacity>
}>
<Menu.Item
title="Logout"
onPress={() => {
setShowMenu(false)
/* THE FOLLOWING DOESN'T WORK */
//navigation.navigate('Logout')
}}
/>
<Divider />
<Menu.Item
onPress={() => {
setShowMenu(false)
}}
title="Profile"
/>
</Menu>
</View>
);
};
function App() {
return (
<Provider>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Register"
component={Register}
options={({ navigation, route }) => ({
headerTitle: () => (
<View style={{ flexDirection: 'row' }}>
{
/*THIS WORKS*/
<TouchableOpacity
onPress={() => navigation.navigate('Logout')}
>
<MaterialCommunityIcons name="settings" size={30} style={{
color: 'black' }} />
</TouchableOpacity>
/*THIS WORKS*/
}
<View><CustomMenu /></View>
<TouchableOpacity
onPress={() => navigation.navigate('MenuV2')}
>
<Text>
Menu
</Text>
</TouchableOpacity>
</View>
),
headerStyle: {
backgroundColor: '#2e46ff',
},
})}
/>
<Stack.Screen name="Logout" component={Logout}/>
</Stack.Navigator>
</NavigationContainer>
</Provider>
);
}
export default App;
Screenshot:

You can use the useNavigation hook inside the custom component
As its inside navigation scope it will work as expected
const CustomMenu = () => {
const [showMenu, setShowMenu] = React.useState(false);
const navigation = useNavigation();
return (
<View style={{}}>
<Menu
visible={showMenu}
onDismiss={() => setShowMenu(false)}
anchor={
<TouchableOpacity onPress={() => setShowMenu(true)}>
<MaterialCommunityIcons
name="earth"
size={30}
style={{ color: 'black' }}
/>
</TouchableOpacity>
}>
<Menu.Item
title="Logout"
onPress={() => {
setShowMenu(false);
}}
/>
<Divider />
<Menu.Item
onPress={() => {
setShowMenu(false);
navigation.navigate('Logout');
}}
title="Profile"
/>
</Menu>
</View>
);
};
Also consider using Authentication flow for login logout scenarios.

You need to pass navigation (or use the useNavigation hook) to be able to navigate from your menu:
<View><CustomMenu navigation={navigation} /></View>
...
const CustomMenu = ({ navigation }) => {
const [showMenu, setShowMenu] = React.useState(false);
return (
<View style={{}}>
<Menu
visible={showMenu}
onDismiss={() => setShowMenu(false)}
anchor={
<TouchableOpacity onPress={() => setShowMenu(true)}>
<MaterialCommunityIcons
name="earth"
size={30}
style={{ color: 'black' }}
/>
</TouchableOpacity>
}>
<Menu.Item
title="Logout"
onPress={() => {
setShowMenu(false)
navigation.navigate('Logout');
}}
/>
<Divider />
<Menu.Item
onPress={() => {
setShowMenu(false)
}}
title="Profile"
/>
</Menu>
</View>
);
};

Related

How to open a modal in drawer headerRight icon button?

I am new to react native and working on a dummy project. I use the react-navigation to set the header and headerRight and place a icon in the right side of the header. Now i want to open a modal whenever user click the icon it will show a modal. I tried many things but didn't find any proper solution. Now i am stuck and post this question. I am placing my code below.
Here is my Code:
//Navigation of my app
import * as React from "react";
import { View, TouchableOpacity, StyleSheet } from "react-native";
import { AntDesign, Fontisto } from "#expo/vector-icons";
import { createDrawerNavigator } from "#react-navigation/drawer";
import { createStackNavigator } from "#react-navigation/stack";
import AccountsScreen from "../screens/AccountsScreen";
import FavouritesScreen from "../screens/FavouritesScreen";
import HomeScreen from "../screens/HomeScreen";
import SettingsScreen from "../screens/SettingsScreen";
import TrendsScreen from "../screens/TrendsScreen";
import { createMaterialTopTabNavigator } from "#react-navigation/material-top-tabs";
import { Dimensions } from "react-native";
import Income from "../screens/Income";
import Expense from "../screens/Expense";
import FundTransferModal from "../components/Accounts/FundTransferModal";
const AppStack = () => {
return(
<Stack.Navigator mode="modal" headerMode="none">
<Stack.Screen name="Modal" component={FundTransferModal} />
</Stack.Navigator>
)
}
const AppDrawer = () => {
return (
<Drawer.Navigator
initialRouteName="Home"
screenOptions={{
headerShown: true,
}}
>
<Drawer.Screen
name="Home"
component={HomeScreen}
options={{
headerRight: () => {
return (
<TouchableOpacity onPress={() => AppStack() }>
<View style={styles.icons}>
<AntDesign name="piechart" size={30} color="#29416F" />
</View>
</TouchableOpacity>
);
},
}}
/>
<Drawer.Screen name="Accounts" component={AccountsScreen} options={{
headerRight: () => {
return (
<TouchableOpacity>
<View style={styles.icons}>
<Fontisto name="arrow-swap" size={24} color="#29416F" onPress={() =>{return <FundTransferModal />}} />
</View>
</TouchableOpacity>
);
},
}} />
<Drawer.Screen name="Categories" component={CategoriesTabScreens} />
<Drawer.Screen name="Trends" component={TrendsScreen} />
<Drawer.Screen name="Favourites" component={FavouritesScreen} />
<Drawer.Screen name="Settings" component={SettingsScreen} />
</Drawer.Navigator>
);
};
export default AppDrawer;
const styles = StyleSheet.create({
icons:{
marginRight:20,
}
})
//The Modal which i want to open
import React, { Fragment, useState } from "react";
import { globalStyles } from "../../style/Global";
import { AntDesign, Entypo } from "#expo/vector-icons";
import { MaterialCommunityIcons } from "#expo/vector-icons";
import { Formik } from "formik";
import * as yup from "yup";
import { Picker } from "#react-native-picker/picker";
import SubmitButton from "../SubmitButton";
import { ExampleUncontrolledVertical } from "../../assets/ExampleUncontrolledVertical";
import {
Modal,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
Button,
Keyboard,
ScrollView,
} from "react-native";
import DatePiker from "../DatePikers";
export default function FundTransferModal({navigation}) {
const [fundModal, setFundModal] = useState(true);
const [selectedValue, setValue] = useState(0);
showFundModal = () => {
setFundModal(true);
};
closeFundModal = () => {
setFundModal(false);
};
const validations = yup.object({
text: yup.string().required().min(3),
});
const values = ["Cash", "Bank", "Other"];
const valuesTwo = ["Cash", "Bank", "Other"]
const initialValues = { value: "", valueTwo: "" };
return (
<Modal visible={fundModal} animationType="fade">
<View style={globalStyles.modalHeader}>
<AntDesign
name="arrowleft"
size={30}
onPress={() => closeFundModal()}
/>
<Text style={styles.headerText}>Transfer</Text>
</View>
<Formik
validationSchema={validations}
initialValues={{ amount: "" , notes:"" }}
onSubmit={(values, actions) => {
actions.resetForm();
console.log(values);
}}
>
{(props) => (
<Fragment>
<ScrollView>
<View style={styles.container}>
<View style={styles.box}>
<View style={styles.picker}>
<Picker
mode="dropdown"
selectedValue={props.values.value}
onValueChange={(itemValue) => {
props.setFieldValue("value", itemValue);
setValue(itemValue);
}}
>
<Picker.Item
label="Account"
value={initialValues.value}
key={0}
color="#afb8bb"
/>
{values.map((value, index) => (
<Picker.Item label={value} value={value} key={index} />
))}
</Picker>
</View>
<View style={styles.inputValue}>
<TextInput
style={styles.inputName}
placeholder="Value"
keyboardType="numeric"
onChangeText={props.handleChange("amount")}
value={props.values.amount}
onBlur={props.handleBlur("amount")}
/>
</View>
</View>
<View style={styles.doubleArrow}>
<Entypo name="arrow-long-down" size={40} color="#afb8bb" />
<Entypo name="arrow-long-down" size={40} color="#afb8bb" />
</View>
<View style={styles.box}>
<View style={styles.picker}>
<Picker
mode="dropdown"
selectedValue={props.values.valueTwo}
onValueChange={(itemValue) => {
props.setFieldValue("valueTwo", itemValue);
setValue(itemValue);
}}
>
<Picker.Item
label="Account"
value={initialValues.valueTwo}
key={0}
color="#afb8bb"
/>
{valuesTwo.map((value, index) => (
<Picker.Item label={value} value={value} key={index} />
))}
</Picker>
</View>
<View style={styles.Calendar}><DatePiker /></View>
<View style={styles.inputValue}>
<TextInput
style={styles.inputName}
placeholder="Notes"
multiline= {true}
onChangeText={props.handleChange("notes")}
value={props.values.notes}
onBlur={props.handleBlur("notes")}
/>
</View>
</View>
<SubmitButton title="Save" onPress={props.handleSubmit} />
</View>
</ScrollView>
</Fragment>
)}
</Formik>
</Modal>
);
}
const styles = StyleSheet.create({
headerText: {
fontSize: 20,
marginLeft: 5,
},
container: {
flex: 1,
},
box: {
borderColor: "#afb8bb",
margin: 20,
flexDirection: "column",
borderWidth: 1,
borderStyle: "dashed",
borderRadius:5,
marginTop:40,
},
inputName:{
padding:10,
borderWidth:1,
borderColor:"#afb8bb",
},
inputValue:{
margin:10,
marginHorizontal:20,
},
picker:{
borderWidth:1,
borderColor:"#afb8bb",
margin:10,
marginHorizontal:20,
},
doubleArrow:{
flexDirection:'row',
alignContent:'center',
justifyContent:'center',
marginTop:10
},
Calendar: {
marginTop:-20,
borderColor: "#afb8bb",
paddingVertical: 10,
marginHorizontal:20,
fontSize: 20,
textAlign: 'center',
color: 'black',
}
});

In React Native, How Can I Use KeyboardAvoidingView with a Modal in iOS?

I am having trouble getting a KeyboardAvoidingView to work in iOS, specifically when wrapping a Modal in it. Testing in Android, the Modal avoids the keyboard correctly, but in iOS the keyboard covers the Modal.
Here is a reproducible example, using an iPhone X as my test device:
import React, {useState} from 'react';
import {StyleSheet, ScrollView, View, Modal, TextInput, KeyboardAvoidingView, Button, SafeAreaView} from 'react-native';
export default function App() {
const [modalVisible, setModalVisible] = useState(false);
return (
<View style={styles.container}>
<Button title={"Open Modal"} onPress={() => setModalVisible(true)}/>
{modalVisible &&
<KeyboardAvoidingView behavior={Platform.OS === "ios" ? "padding" : "height"}>
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}>
<SafeAreaView style={styles.safeAreaView}>
<View style={styles.modalContentContainer}>
<Button title={"Close Modal"} onPress={() => setModalVisible(false)}/>
<ScrollView>
<View style={styles.textInputContainer}>
<TextInput
value={"test 1"}
onChangeText={() => {}}
onBlur={() => {}}
/>
</View>
<View style={styles.textInputContainer}>
<TextInput
value={"test 2"}
onChangeText={() => {}}
onBlur={() => {}}
/>
</View>
<View style={styles.textInputContainer}>
<TextInput
value={"test 3"}
onChangeText={() => {}}
onBlur={() => {}}
/>
</View>
<View style={styles.textInputContainer}>
<TextInput
value={"test 4"}
onChangeText={() => {}}
onBlur={() => {}}
/>
</View>
</ScrollView>
</View>
</SafeAreaView>
</Modal>
</KeyboardAvoidingView>}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
paddingTop: 50
},
safeAreaView: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
modalContentContainer: {
margin: 100,
backgroundColor: "#d6d6d6",
width: "80%",
height: "60%",
borderRadius: 10,
},
textInputContainer: {
flex: 1,
margin: 40,
alignItems: "center",
}
});
You will notice that when you open the Modal and tap on the last TextInput field, the keyboard comes up and covers the Modal. The Modal does not avoid the keyboard, as would be expected.
Here is a screenshot from my testing in iOS, where it is not working:
And here is a screenshot from my testing in Android, where it is working:
Any ideas on how I can make a Modal avoid the keyboard in iOS?
You are wrapping your Modal with a KeyboardAvoidingView. I'm not sure about this but from my testing it seems to be impossible to resize a Modal using a KeyboardAvoidingView.
So move the KeyboardAvoidingView inside of the Modal to get the expected behaviour.
Like this:
return (
<View style={styles.container}>
<Button title={"Open Modal"} onPress={() => setModalVisible(true)}/>
{modalVisible &&
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}>
<KeyboardAvoidingView behavior={"padding"} style={styles.safeAreaView}>
<View style={styles.modalContentContainer}>
<Button title={"Close Modal"} onPress={() => setModalVisible(false)}/>
<ScrollView>
<View style={styles.textInputContainer}>
<TextInput
value={"test 1"}
onChangeText={() => {}}
onBlur={() => {}}
/>
</View>
<View style={styles.textInputContainer}>
<TextInput
value={"test 2"}
onChangeText={() => {}}
onBlur={() => {}}
/>
</View>
<View style={styles.textInputContainer}>
<TextInput
value={"test 3"}
onChangeText={() => {}}
onBlur={() => {}}
/>
</View>
<View style={styles.textInputContainer}>
<TextInput
value={"test 4"}
onChangeText={() => {}}
onBlur={() => {}}
/>
</View>
</ScrollView>
</View>
</KeyboardAvoidingView>
</Modal>}
</View>
);
I've also removed your SafeAreaView since it currently doesn't do anything. Your parent is already centered and its content will never reach the 'unsafe' areas of a device.
You might as well remove the {modalVisible && ...} from your code since visible={modalVisible} already hides and shows the modal when needed.
Demo:
https://imgur.com/W7sEPpn
If you're using react-native-modal package ... avoidKeyboard attribute would do the trick...
import Modal from "react-native-modal";
return (
<Modal
avoidKeyboard
/** rest props */
>
{/* Content goes here */}
</Modal>
);
Not in the build-in-react-native-modal ... it's in react-native-modal here

How do I pass data through navigation into a flatlist?

I am fetching data from an API that has a JSON file, I am trying to pass the data from app to appstack to sessionsStack that will then bring the data to the home page, currently my console.logs are telling me that my data is only getting to the appStack.
App
import {AppStack} from '#miniclementine:navigation/AppStack';
import Colors from '#miniclementine:common/appearance/Colors';
const DATA = "https://cg0wsrc3ue.execute-api.us-east-1.amazonaws.com/dev/getSessions"
const App: () => React$Node = () => {
const [isLoading, setLoading] = useState(true);
const [data, setData] = useState([]);
useEffect(() => {
fetch(DATA)
.then((response) => response.json())
.then((json) => setData(json.sessions))
.catch((error) => console.error(error))
.finally(() => setLoading(false));
console.log("the data: ", data)
}, []);
return (
<SafeAreaView style={styles.container}>
{ isLoading ? <ActivityIndicator /> : <AppStack data={data}/> }
</SafeAreaView>
);
};
App Stack
const Tab = createBottomTabNavigator();
export const AppStack = ({data}) => {
console.log("data in appstack", data)
return (
<NavigationContainer>
<Tab.Navigator tabBar={(props) => <MiniClementineTabBar {...props} />}>
<Tab.Screen name="Sessions" component={SessionsStack} data={data} />
<Tab.Screen name="Mantras" component={MantraStack} />
</Tab.Navigator>
</NavigationContainer>
);
};
Sessions Stack
import {createStackNavigator} from '#react-navigation/stack';
import SessionsHomepage from '#miniclementine:modules/sessions/SessionsHomepage';
import SessionsListPage from '#miniclementine:modules/sessions/SessionsListPage';
export const SESSIONS_HOMEPAGE = 'SESSIONS_HOMEPAGE';
export const SESSIONS_LIST_PAGE = 'SESSIONS_LIST_PAGE';
const Stack = createStackNavigator();
export default function SessionsStack({data}) {
console.log("data in SessionStack", data)
return (
<Stack.Navigator
screenOptions={{
headerShown: false,
}}>
<Stack.Screen name={'SESSIONS_HOMEPAGE'} component={SessionsHomepage} data={data} />
<Stack.Screen name={'SESSIONS_LIST_PAGE'} component={SessionsListPage} data={data} />
{/* #TODO: The two additional screens you need to create will be defined here */}
</Stack.Navigator>
);
}
Homepage
import {ClementineIcon, SectionHeader} from '#miniclementine:common/components';
import Colors from '#miniclementine:common/appearance/Colors';
import BlueButton from '#miniclementine:common/components/BlueButton';
import DiscoverMore from '#miniclementine:common/components/DiscoverMore';
export default function SessionHomepage({navigation, data, onPress}) {
console.log("data in HomePage", data)
const onPressSectionHeader = ({onPress}) => {
navigation.navigate('SESSIONS_LIST_PAGE', data)
};
const onPressSectionHeaderInvalid = () => {
alert('Out of scope for this task')
};
const Item = ({ item }) => (
<View style={styles.item}>
<TouchableOpacity style={styles.viewButton} onPress={() => {openSettingsModal(item) }}>
<Image source={item.image} style={styles.exerciseImage} />
<View style={styles.detailSection}>
<Text style={styles.title}>{item.title}</Text>
</View>
<ClementineIcon name="button-play" color={Colors.lilac} size={24} />
</TouchableOpacity>
</View>
);
const onContactPress = () => {
Linking.openURL('mailto:alina#clementineapp.co.uk?subject=SendMail&body=Description')
// Note: Not supported in iOS simulator, so you must test on a device.
// If you want to open an email client instead, I would suggest using the library react-native-email-link
}
return (
<SafeAreaView style={styles.container}>
<ScrollView>
<View style={styles.content}>
<SectionHeader
title="Morning Sessions"
onPress={onPressSectionHeaderInvalid}
textColor={Colors.Ink}
textColorSeeAll={Colors.cornflower}
/>
{/* <FlatList
data={DATA}
renderItem={({ item }) => <Item item={item} />}
keyExtractor={(item) => item.id}
/> */}
<DiscoverMore
title="Discover more sessions"
onPress={onPressSectionHeaderInvalid}
color={Colors.cornflower}
textColor={Colors.cornflower}
/>
<SectionHeader
title="Pick-me-ups"
onPress={onPressSectionHeader}
textColor={Colors.Ink}
textColorSeeAll={Colors.cornflower}
/>
<DiscoverMore
title="Discover more sessions"
onPress={onPressSectionHeader}
color={Colors.cornflower}
textColor={Colors.cornflower}
/>
</View>
<View style={styles.sleepSection}>
<Image
source={require('../../assets/illustrations/sleep_section.png')}
style={{height: 250, width: '100%'}}
/>
</View>
<View style={styles.sleepSection}>
<View style={styles.content}>
<View style={{paddingVertical: 10}}>
<SectionHeader
title="Sleep Sessions"
onPress={onPressSectionHeaderInvalid}
backgroundColor={Colors.ink}
textColor={Colors.stone}
textColorSeeAll={Colors.mint}
style={{paddingHorizontal: 10}}
/>
</View>
<Text>test</Text>
<DiscoverMore
title="Discover more sessions"
onPress={onPressSectionHeaderInvalid}
color={Colors.mint}
textColor={Colors.mint}
/>
</View>
</View>
<View style={{marginTop: '20%'}}></View>
<BlueButton title="Leave us feedback" onPress={onContactPress}/>
</ScrollView>
</SafeAreaView>
);
}
I am planning to put this data into a Flatlist inside the home page.
The best way to pass pros in navigation is to use params.
And as your stack is loaded with data you can use the initialParams like below
<Tab.Screen name="Sessions" component={SessionsStack} initialParams={{data:data}}/>
You can access it in SessionsStack like below
export default function SessionsStack({route}) {
.....
<Stack.Screen name={'SESSIONS_HOMEPAGE'} component={SessionsHomepage} initialParams={{data:route.params.data}} />
You can do this in the screens which are nested as well.
Warning: this patterns is good only if the data is loaded only once, if you are thinking of changing the data use something like react context.

React Native: How to dispatch redux action from Custom Drawer

I have to dispatch logoutUser() action from CustomDrawerContentComponent. How can I do that?
I have a StackNavigator as well as Drawer Navigator in my app.
Also there is a CustomDrawerComponent to show the username of authenticated user as well as a sign up button in Drawer. Since it is outside the class, I'm unable to dispatch using props.
MainComponent.js
...other import statements
import {
fetchDishes,
fetchComments,
fetchPromos,
fetchLeaders,
logoutUser,
} from "../redux/ActionCreators";
const mapDispatchToProps = (dispatch) => ({
fetchDishes: () => dispatch(fetchDishes()),
fetchComments: () => dispatch(fetchComments()),
fetchPromos: () => dispatch(fetchPromos()),
fetchLeaders: () => dispatch(fetchLeaders()),
logoutUser: () => dispatch(logoutUser()),
});
const handleSignOut = () => {
//Here I have to dispatch logoutUser() but props.logoutUser() says undefined.
};
const CustomDrawerContentComponent = (props) => (
<ScrollView>
<SafeAreaView
style={styles.container}
forceInset={{ top: "always", horizontal: "never" }}
>
<View style={styles.drawerHeader}>
<View style={{ flex: 1 }}>
<Image
source={require("./images/newlogo.png")}
style={styles.drawerImage}
/>
</View>
<View style={{ flex: 2 }}>
<Text style={styles.drawerHeaderText}>Ristorante Con Fusion</Text>
</View>
</View>
<View style={styles.displayName}>
<Avatar
title={props.screenProps.name.match(/\b(\w)/g).join("")}
rounded
>
{" "}
</Avatar>
<Text style={{ fontSize: 22, marginLeft: 5, color: "#fff" }}>
Hello, {props.screenProps.name}
</Text>
</View>
<DrawerItems {...props} />
<TouchableOpacity onPress={() => handleSignOut()}> //Here I am calling the function
<View style={{ flexDirection: "row", justifyContent: "center" }}>
<Icon name="sign-out" type="font-awesome" size={24} color="blue" />
<Text>Sign Out</Text>
</View>
</TouchableOpacity>
</SafeAreaView>
</ScrollView>
);
const MainNavigator = createDrawerNavigator(
{
Home: {
screen: HomeNavigator,
navigationOptions: {
title: "Home",
drawerLabel: "Home",
drawerIcon: ({ tintColor, focused }) => (
<Icon name="home" type="font-awesome" size={24} color={tintColor} />
),
},
},
...
...
...
{
initialRouteName: "Home",
drawerBackgroundColor: "#D1C4E9",
contentComponent: CustomDrawerContentComponent,
}
);
class Main extends Component {
componentDidMount() {
this.props.fetchDishes();
this.props.fetchComments();
this.props.fetchPromos();
this.props.fetchLeaders();
};
render() {
var displayName = this.props.user.user.displayName;
if (displayName == undefined) displayName = this.props.user.name;
return (
<Fragment>
<View
style={{
flex: 1,
paddingTop:
Platform.OS === "ios" ? 0 : Expo.Constants.statusBarHeight,
}}
>
<MainNavigator screenProps={{ name: displayName }} />
</View>
</Fragment>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Main);
You can use the following to import useDispatch
import { useDispatch } from 'react-redux'
and then call it in your handleSignOut like so:
const handleSignOut = () => {
const dispatch = useDispatch()
dispatch(logoutUser())
//Continue with normal logout and maybe other dispatches...
};
Your component is not connected to redux. You are not using mapDispatchToProps anywhere else besides declaring it. Please use connect() method https://react-redux.js.org/api/connect

How can i set focus to only one TextInput in list item after TouchableOpacity pressed in this item?

I have a list of many items where each item has TextInput and TouchableOpacity wrapped by View.
I've trying to set focus on TextInput in the list item in which TouchableOpacity has been pressed. It's needed for editing each item's name.
Below is the code of how I tried to do this. The problem of this code is that after pressing on any of the TouchableOpacity the last TextInput will always be focused due to the fact that the last iteration overwrites textInputRef.
Is there a way to make textInputRef contain a reference to the TextInput which TouchableOpacity will press?
const ListComponent = ({list}) => {
const textInputValue = useRef('');
const textInputRef = useRef(null);
changeItemName = (text) => {
textInputValue.current = text;
};
return (
<ScrollView>
{list.length > 0 &&
list.map((item) => (
<View key={item._id}>
<TouchableOpacity>
<View
<Text>{`Item: `}</Text>
<TextInput ref={textInputRef} onChangeText={changeItemName}>
{item.name}
</TextInput>
</View>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
textInputValue.current = '';
}}>
<Icon name={'check'} size={25} color="#000" />
</TouchableOpacity>
<View>
<TouchableOpacity
onPress={() => {
textInputValue.current = item.name;
textInputRef.current.focus();
}}>
<Icon name={'edit'} size={25} color="#000" />
</TouchableOpacity>
</View>
</View>
))}
</ScrollView>
);
};
I think creating an array of ref will help you to resolve.
Try this way
const ListComponent = ({list}) => {
const textInputValue = useRef('');
const textInputRef = useRef(null);
changeItemName = (text) => {
textInputValue.current = text;
};
const collectionRef = useRef(list.map(() => createRef()));
return (
<ScrollView>
{list.length > 0 &&
list.map((item, index) => (
<View key={item._id}>
<TouchableOpacity>
<View
<Text>{`Item: `}</Text>
<TextInput ref={collectionRef.current[index]} onChangeText={changeItemName}>
{item.name}
</TextInput>
</View>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
textInputValue.current = '';
}}>
<Icon name={'check'} size={25} color="#000" />
</TouchableOpacity>
<View>
<TouchableOpacity
onPress={() => {
textInputValue.current = item.name;
collectionRef[index].current.focus();
}}>
<Icon name={'edit'} size={25} color="#000" />
</TouchableOpacity>
</View>
</View>
))}
</ScrollView>
);
};

Categories

Resources