Navigating to another Screen when a button is tapped in React Native - javascript

I'm quite new to React Native, I want to open another screen when a button is tapped, the screen that I want to show is already created. I have used TouchableOpacity as my button and used navigation on "onPress" prop. My app is already using Stack navigator and Tab navigator so I have installed those packages.
I have tried but I'm getting an error "undefined is not an object (evaluating 'navigation.navigate')"
Please help.
In the screen where I'm showing the button:
const myWebview = ({ navigation }) => {
return (
<View style={styles.buttonContainer}>
<TouchableOpacity
style={styles.button}
onPress={() => navigation.navigate("NewListingScreen")}
>
<Text style={{ color: "#ffffff", fontWeight: "bold" }}>
My Button Title
</Text>
</TouchableOpacity>
</View>
);
};
On my Tab Navigator (The screen works fine):
<Tab.Screen
name={routes.newListingScreen}
component={NewListingScreen}
options={({ navigation }) => ({
tabBarButton: () => (
<NewListingButton
onPress={() => {
navigation.navigate(routes.newListingScreen);
dispatch({
type: "SET_NEW_LISTING_SCREEN",
newListingScreen: true,
});
}}
/>
),
tabBarVisible: !user,
})}
/>
When I use const navigation = useNavigation();
I get this error:

My first guess is that the navigation object itself is undefined here. Navigation prop is only available on screens. -
const myWebview = ({ navigation }) => {
Using hooks is a better alternative to passing navigation objects to child components.
import { useNavigation } from '#react-navigation/native';
function NotificationsScreen() {
const navigation = useNavigation();
return(
...
<TouchableOpacity
style={styles.button}
onPress={() => navigation.navigate('NewListingScreen')}
>
...
);
}

Related

React Navigation | How do I change the buttons on a Tab Navigator from a child screen nested in a Stack Navigator?

I have a tab bar that looks like this:
The two side buttons are stack navigators (Learn and Journal) and the middle button needs to navigate the Journal Stack, and depending on what screen in the Journal Stack the user is on, it needs to say and do different things.
const Tab = createBottomTabNavigator();
const TabBarIcon = ({ icon, title, focused }) => {
return (
<View style={styles.iconContainer}>
<FontAwesomeIcon
icon={icon}
color={focused ? Colors.neutral[4] : Colors.neutral[6]}
size={24}
style={styles.icon}
/>
<Text style={[styles.iconText, focused && styles.iconTextFocused]}>
{title}
</Text>
</View>
);
};
const NullScreen = () => null;
const TabNavigator = () => {
return (
<Tab.Navigator
initialRouteName="Journal"
screenOptions={({ route }) => ({
...defaultNavOptions,
headerShown: false,
tabBarStyle: { backgroundColor: Colors.neutral[3] },
tabBarShowLabel: false,
})}
>
<Tab.Screen
name="Learn"
component={LearnStackNavigator}
options={{
tabBarIcon: ({ focused }) => (
<TabBarIcon
focused={focused}
title={'Learn'}
icon={faUserGraduate}
/>
),
}}
/>
<Tab.Screen
name="Null Screen"
component={NullScreen}
options={{
tabBarButton: ({ focused }) => (
<View
style={{
position: 'relative',
bottom: 25,
width: 80,
height: 80,
borderRadius: '50%',
backgroundColor: 'grey',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
shadowColor: 'black',
shadowOpacity: 0.3,
shadowOffset: { width: 0, height: 2 },
shadowRadius: 3,
}}
>
<TouchableOpacity onPress={() => Alert.alert('hello world')}> // This is the button that I want use for useful things
<View style={[styles.iconContainer, styles.paddingBottom10]}>
<FontAwesomeIcon
icon={faPlus}
color={focused ? Colors.neutral[4] : Colors.neutral[6]}
size={32}
/>
<Text style={styles.iconText}>{'Add Sport'}</Text>
</View>
</TouchableOpacity>
</View>
),
}}
/>
<Tab.Screen
name="Journal"
component={LogbookStackNavigator}
options={{
tabBarIcon: ({ focused }) => (
<TabBarIcon focused={focused} title={'Journal'} icon={faPenAlt} />
),
}}
/>
</Tab.Navigator>
);
};
And here is what the LogbookStackNavigator looks like:
const LogbookStack = createStackNavigator();
const LogbookStackNavigator = () => {
return (
<LogbookStack.Navigator
screenOptions={{
...defaultNavOptions,
headerBackTitleVisible: false,
}}
>
<LogbookStack.Screen
name="Screen1"
component={screen1Component}
options={defaultNavOptions}
/>
<LogbookStack.Screen
name="Screen2"
component={screen2Component}
options={defaultNavOptions}
/>
<LogbookStack.Screen
name="Screen3"
component={screen3Component}
options={entryScreenOptions}
/>
<LogbookStack.Screen
name="Screen4"
component={screen4Component}
options={SaveLogbookScreenOptions}
/>
<LogbookStack.Screen
name="Screen5"
component={screen1Component5}
options={defaultNavOptions}
/>
</LogbookStack.Navigator>
);
};
I know how to use navigation.setOptions, but it only affects the immediate parent navigator, not the grandparent navigator.
Another thing I tried was to make the big circle button on the page itself, but it always rendered underneath the Tab Navigator. If there was a way to make it render above, I think I could just use that. I tried 'position: 'absolute', etc and it always rendered underneath the tab navigator. As it is, I had to basically make a dummy screen in the tab navigator to give me the button on top.
What I need to be able to do, is use big circle button on the Tab Navigator, to navigate to different screens in the LogbookStackNavigator. How do I do that?
Also, I need the title to change from "Add Sport" to "Add " depending on what screen the LogbookStackNavigator is on. How do I do that?
Thanks for your help
Finally figured this out. You have to use react-native-portalize. Just wrap the elements you want to be rendered on top in a
<Portal></Portal>. This will place it above a Bottom Tab navigator.
import { Portal } from 'react-native-portalize';
const FooterButton = () => {
return(
<Portal>
<View>
<Text>I appear above the Tab Navigator!</Text>
</View>
</Portal>
);
export default FooterButton;
Don't forget to wrap the whole app in the the Host:
//In app.js
import { Host } from 'react-native-portalize';
const App = () => {
return (
<Host>
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
</Host>
)
}
export default App;
NOTE: The elements inside the Portal, do not clear when the navigator navigates to another screen. So to get around this, you have to only display the Portal, when the screen is active. Thankfully React Navigation 5+ provides a useIsFocused hook that accomplishes this perfectly.
import { Portal } from 'react-native-portalize';
import { useIsFocused } from '#react-navigation/native';
const FooterButton = () => {
const isFocused = useIsFocused();
// Only display the button when screen is focused. Otherwise, it doesn't go away when you switch screens
return isFocused ? (
<Portal>
<View style={styles.buttonContainer}>
<View style={styles.footer}>{props.children}</View>
</View>
</Portal>
) : null;
};
export default FooterButton;
If you want a modal-style popup, you can wrap react-native-modalize and wrap it with react-native-modalize
Thanks to livin52 on Reddit for the solution

React Native, how to change display of touchable opacity to visible when clicking on another, and invisible when you click again?

As the title suggests, I am struggling to find a way to make my touchable opacities have a display of none by default (well, I suppose that is easy enough with a styling of display: none), but I'm not able to figure out how to toggle that using a touchable opacity.
In my head, the logic is to have the state change from true to false onpress, and false is visible while true is invisible. However, I can't muster up the knowledge to code it out. Here is what I have so far, more info below code:
import React, {useState} from 'react';
import { KeyboardAvoidingView, StyleSheet, Text, View, TextInput, TouchableOpacity, Keyboard, ImageBackground } from 'react-native';
import Task from './components/task';
const plus = {uri: 'https://media.discordapp.net/attachments/639282516997701652/976293252082839582/plus.png?width=461&height=461'};
const done = {uri: 'https://media.discordapp.net/attachments/736824455170621451/976293111456231434/done.png?width=461&height=461'};
const exit = {uri: 'https://media.discordapp.net/attachments/639282516997701652/976293251759898664/exit.png?width=461&height=461'};
const cog = {uri: 'https://media.discordapp.net/attachments/639282516997701652/976293672884789288/cog.png?width=461&height=461'};
function App() {
const [task, setTask] = useState();
const [taskItems, setTaskItems] = useState([]);
const buttons = {plus, done, exit, cog}
const [selected, setSelected] = useState(buttons.plus)
const [done, setDone] = useState(buttons.plus);
const openMenu = () => {
setSelected(buttons.exit);
//Make 'done' and 'cog' TouchableOpacity(s) visible. Click again and they become invisible.
//this.setState({ visible : !this.state.visible}) This makes visible invisible if not invisible already.
//visible should be the name of a state.
{/*handleAddTask();*/}
}
const handleAddTask = () => {
setDone(buttons.done);
Keyboard.dismiss();
setTaskItems([...taskItems, task]); {/*Puts out everything in the taskItems as a new array then appends the new task to it */}
setTask(null);
setSelected(buttons.plus) //Makes exit button go back to plus because it shows that its finished. DO same for display none for extended buttons when I figure it out.
}
const completeTask = (index) => {
let itemsCopy = [...taskItems];
itemsCopy.splice(index, 1);
setTaskItems(itemsCopy);
}
return (
<View style={styles.container}>
{/*Tasks*/}
<View style={styles.tasksWrapper}>
<Text style={styles.sectionTitle}>Tasks</Text>
<View style={styles.items}>
{/*This is where tasks go*/}
{
taskItems.map((item, index) => {
return (
<TouchableOpacity key={index} onPress={() => completeTask(index)}>
<Task text={item} />
</TouchableOpacity>
)
})
}
</View>
</View>
{/*Write a task*/}
<KeyboardAvoidingView behavior={Platform.OS === "ios" ? "padding" : "height"} style={styles.writeTaskWrapper}>
<TextInput style={styles.input} placeholder={'Write a task'} value={task} onChangeText={text => setTask(text)}/>
<View style={styles.buttonRow}>
<TouchableOpacity onPress={() => openConfig()}>
{/* Opens config for creation (i.e. calendar, timer, repetition, etc). */}
<View style={styles.addWrapper}>
<ImageBackground source={buttons.cog} alt='button' resizeMode="cover" style={styles.plusButton} />
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => handleAddTask()}>
{/* Done (check) button which does handleAddTask. */}
<View style={styles.addWrapper}>
<ImageBackground source={buttons.done} alt='button' resizeMode="cover" style={styles.plusButton} />
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => openMenu()}>
{/* Onpress opens menu, then shows exit button to go back and revert menu to one button. */}
<View style={styles.addWrapper}>
<ImageBackground source={selected} alt='button' resizeMode="cover" style={styles.plusButton} />
</View>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
</View>
);
}
The three touchable opacities at the bottom are what I'm trying to change. The first two should by default be invisible, and I think I can do that by assigning useState(false) for them and false should make their display none. Then on the click of the third touchable opacity, it changes their previous state => !previous state.
However, I'm not sure how to code this out and am quite confused. Any help is appreciated. Thanks!
This can be done using conditional rendering. You will either need a state for each of the buttons or a state that holds an array.
Here is a minimal example which works in general.
function App() {
const [isAVisible, setAVisible] = useState(true);
const [isBVisible, setBVisible] = useState(false);
return (
<View>
{isAVisible && (
<TouchableOpacity onPress={() => setIsBVisible(prev => !prev)}}>
<Text>Toggle Visibility of B</Text>
</TouchableOpacity>
)}
{isBVisible && (
<TouchableOpacity onPress={() => setIsAVisible(prev => !prev)}}>
<Text>Toggle Visibility of A</Text>
</TouchableOpacity>
)}
</View>
)
}
The above creates two TouchableOpacity. The first toggles the visibility of the second one, and the second one toggles the visibility of the first one. Notice, that the default state of the second one is set to false, thus it will be not be visible on first render.

Cannot update a component (`ForwardRef(BaseNavigationContainer)`) while rendering a different component (`Home_Profile`)

I get this error while trying to navigate between screens with react native stack navigator. I think this was working in my previous project which had react 17.0.1 and react native 0.64.2, this project is react 17.0.2 and react native 0.66.4, helps would be appreciated.
log
Warning: Cannot update a component (`ForwardRef(BaseNavigationContainer)`) while rendering a different component (`Home_Profile`). To locate the bad setState() call inside `Home_Profile`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
error comes when I try to call navigation in an onPress prop on a Flatlist render item component.
renderItem={({ item }) => (
<View style={{ backgroundColor: "#f6f6f6" }}>
<PostCard
item={item}
onPress={() => navigation.navigate("Home_Profile")}
/>
</View>
)}
const PostCard = ({ item, onPress }) => {
React.useEffect(() => {
async function fetchUserData() {
const data = await getUserData(item.userid);
setProfileimg(data.userimg);
setName(data.name);
setLocation(data.location);
}
fetchUserData();
}, []);
return (
<TouchableOpacity onPress={onPress}>
<Text
style={{
color: "#231454",
fontWeight: "bold",
fontSize: 15,
marginBottom: 3,
}}
>
{name}
</Text>
</TouchableOpacity>
)
};
export default PostCard;
Home_Profile
import React from "react";
import { View, Text, Button } from "react-native";
const Home_Profile = ({ navigation }) => {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>Home_Profile.js</Text>
<Button title="back" onPress={navigation.navigate("Home")} />
</View>
);
};
export default Home_Profile;
I found the cause of the problem, when you render the second screen, it is executing the onPress method of the button that goes back home, and that navigation causes the render method in that home screen to execute, which means you are trying to execute two renders at the same time, to fix that, just add an arrow function to the navigate home function as shown bellow:
onPress={()=>{navigation.navigate("Home")}}
You have forgot to add an arrow function before navigation.navigation('nameOFComponent')

How do I grab state information from custom header button component in react-native?

I'm new to React. I'm having a tough time trying to change how a screen looks depending on what value I select inside a dialog/modal that pops up when I press the header button using a Stack Navigator.
This is a gif of what it looks like: https://i.imgur.com/Yw2jbHu.gifv
This is what my Stack Navigation looks like:
const HistoryStack = () =>{
return (
<Stack.Navigator screenOptions={{headerShown:true,headerStyle:{backgroundColor:'#21252B',shadowColor: '#333842'},headerTintColor:"#528bff",headerTitleAlign: 'center' }}>
<Stack.Screen name="History" component={History} />
<Stack.Screen name="Logging" options={({ navigation, route })=> ({headerTitle: props => <SwitchWorkoutButton/>})}component={Logging} />
</Stack.Navigator>
);
}
As you can see I made the header title my own custom button component. I added a currentWorkout state variable to the component that gets set whenever a value selected in the dialog.
This is what the header button looks like:
const SwitchWorkoutButton = () =>{
const [showDialog,setShowDialog] = useState(false)
const [currentWorkout,setCurrentWorkout] = useState('A')
const renderDialog = (showDialog) =>{
return (<Dialog migrate visible={showDialog} useSafeArea bottom onDismiss={setShowDialog(false)}>test</Dialog>)
}
const closeDialog = () =>{
setShowDialog(false)
}
const openDialog = () =>{
setShowDialog(true)
}
return (
<View>
<Chip label ="Workout A"
iconStyle={{margin: 4,tintColor:'#528bff',width:11,height:6}}
rightIconSource={require('../assets/chevronDown.png')}
labelStyle={{color: Colors.white,fontSize:13}}
containerStyle={{borderColor: "#2D2E33", backgroundColor: "#2D2E33", marginLeft: Spacings.s3,padding:2.5,width:115}}
onPress={() =>{ console.log('pressed',showDialog)
setShowDialog(true)
console.log('pressed',showDialog)
}}/>
<Dialog migrate height={150} containerStyle={styles.roundedDialog} panDirection={PanningProvider.Directions.UP} visible={showDialog} onDismiss={closeDialog} useSafeArea top>
<View style ={{backgroundColor:'#2d2e33',borderBottomColor:'#929497',borderBottomWidth:.2,alignItems:'center',}}>
<Text style={{fontSize:20,marginVertical:10,color:'#929497'}}>Switch Workout</Text>
</View>
<TouchableOpacity onPress={()=>{setCurrentWorkout('B')}}>
<View style ={{paddingLeft:20,marginVertical:5,marginTop:10}}>
<Text style={{fontSize:20,color:'#929497',}}>Workout B</Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={()=>{setCurrentWorkout('C')}}>
<View style ={{paddingLeft:20,marginVertical:5}}>
<Text style={{fontSize:20,color:'#929497'}}>Workout C</Text>
</View>
</TouchableOpacity>
</Dialog>
</View>
);
}
And this is the page where the content is displayed through my WorkoutSet component that gets passed a prop that I will use to display the correct data.
const Logging = ({navigation}) =>{
const [selectedIndex,setSelectedIndex] = useState(0)
return (
<SafeAreaView style={{ flex: 1,backgroundColor:'#21252B'}}>
<View style={{ flex:3}} >
<WorkoutSet id={'A'}/>
</View>
</SafeAreaView>
);
}
My question is how do I grab the value I selected in the dialog to do that. I've read through https://reactnavigation.org/docs/header-buttons/ but their use case is different than mine.

React (native) navigation toggle search bar

Have been struggling a couple of days now trying to figure out how to toggle a search bar in the react navigation.
My approach has been to
static navigationOptions = ({navigation}) => {
return {
title: 'Header Title',
headerLeft: (
{navigation.params.state.search ? <searchfield query={text => navigation.setParams(text)} > : <settings>}
),
headerRight: (
<TouchableOpacity style={{ marginHorizontal: 10 }}>
<Icon name="search" size={28} color="#5751D9" />
</TouchableOpacity>
)
}}
I then wanted to add some logic to the headerLeft so it either returns the cog icon button component or an TextInput component (plan to pass the text to setParams and use it as a filter in the list component below the header) but I can't seem to figure out how to pass down a state or state handler as props when I'm not navigating to it.. It's the initial screen.
Hook a function to your setParams inside componentDidMount which will
be called on searchedText change, use this function to setState.
componentDidMount() {
this.props.navigation.setParams({onSearchText: (searchedText) => this.onSearchText(searchedText)});
}
onSearchText(searchedText) {
//update your list using this searchedText
this.setState({searchedText})
}
Now call the function onSearchText() when searchedText changes,
static navigationOptions = ({navigation}) => {
return {
title: 'Header Title',
headerLeft: (
{navigation.params.state.search ? <searchfield query={text => onSearchText(text)} > : <settings>}
),
headerRight: (
<TouchableOpacity style={{ marginHorizontal: 10 }}>
<Icon name="search" size={28} color="#5751D9" />
</TouchableOpacity>
)
}}
Hope it will help you ...

Categories