I am trying to implement react-navigation-shared-element where when I click on a card I want to show a smooth navigation.
I have 2 screens one is Home which is in a BottomTabNavigator and the Other in a in Stack Navigator that I call MainNavigator
so I wrapped my Image in both card and post details screen like this:
<SharedElement id={`item.${ad?._id}.photo`}>
<CarImage
avatar
ad={ad}
postId={ad?._id}
imageStyle={styles.imageStyle}
iconsSize={'normal'}
/>
</SharedElement>
and in details like this
<SharedElement id={`item.${post?._id}.photo`}>
<View>
<MediaCarousel ad={post} showImage={showImage} />
</View>
</SharedElement>
and in Main Main Stack I have this
const MainStack = createSharedElementStackNavigator();
return (
<NavigationWrapper>
<MainStack.Navigator screenOptions={mainOptions}>
...
<MainStack.Screen
name="PostDetails"
component={PostDetails}
sharedElements={(route) => {
const { adId } = route.params;
return [`item.${adId}.photo`];
}}
/>
Why is that not working, is it because My Home Screen is in BottomTabNavigator and the PostDetails is in StackNavigator ?
Related
I'm writing a React Native app, while using react-navigation package for navigating through the app screens.
On my app's home screen, there is a bottom tab bar with about 4-5 buttons, each leading to a different screen. Besides that, all of my app's screens contain a navigation drawer that leads to the rest of the screens. All the screens listed on the bottom tab bar are included in the navigation drawer as well.
App.js:
const App = () => {
return (
<NavigationContainer>
<DrawerNavigator />
</NavigationContainer>
)
}
DrawerNavigator.js:
const DrawerNavigation = createDrawerNavigator();
const DrawerNavigator = () => {
return (
<DrawerNavigation.Navigator>
<DrawerNavigation.Screen
name="ScreenA"
component={BottomTabNavigator} />
<DrawerNavigation.Screen
name="ScreenB"
component={ScreenB} />
<DrawerNavigation.Screen
name="ScreenC"
component={ScreenC} />
</DrawerNavigation.Navigator>
)
}
BottomTabNavigator.js:
const BottomTabNavigation = createBottomTabNavigator();
const BottomTabNavigator = () => {
return (
<BottomTabNavigation.Navigator>
<BottomTabNavigation.Screen
name="ScreenA"
component={ScreenA} />
<BottomTabNavigation.Screen
name="ScreenB"
component={ScreenB} />
</BottomTabNavigation.Navigator>
)
}
My question is how can I sync between them?
Let's say on the navigation drawer I have ScreenA, ScreenB, and ScreenC, while on the bottom tab bar I have only Screen A and Screen B. I want to click on ScreenB in the drawer, and have ScreenB selected as well on the tab bar, and vice versa, clicking on ScreenB on the tab bar and have it selected too on the drawer.
Is such thing possible? How would you implement it?
In my react-native project I am using libraries
"#react-navigation/native": "^5.8.10",
"#react-navigation/stack": "^5.12.8",
I have nested navigator, like this:
// root level I have a stack navigator where it contains two screens, `Home` and `Settings`.
const App = ()=> {
const rootStack = createStackNavigator();
return (
<NavigationContainer>
<rootStack.Navigator>
<rootStack.Screen name="Home" component={Home} />
<rootStack.Screen name="Settings" component={Settings} />
</rootStack.Navigator>
</NavigationContainer>
);
}
// The Settings screen is a nested stack navigator
const Settings = ()=> {
const settingsStack = createStackNavigator();
return (
<settingsStack.Navigator>
<settingsStack.Screen name="SettingsOne" component={SettingsOneScreen} />
<settingsStack.Screen name="SettingsTwo" component={SettingsTwoScreen} />
</settingsStack.Navigator>
);
}
As you can see, the Settings screen is actually another level (nested) stack navigator.
On SettingsOneScreen, there is a button navigates user to SettingsTwoScreen.
const SettingsOneScreen = ({navigation}) => {
...
return (
...
<Button onPress={()=>navigation.navigate("SettingsTwo")}/>
)
}
Now, on SettingsTwoScreen, I have a button, I would like to close the whole settings navigator stack when user tap on the button. That's dismiss the whole settings stack & show user the Home. How to achieve it?
const SettingsTwoScreen = ({navigation}) => {
...
return (
...
<Button onPress={/*dismiss the settings stack*/}/>
)
}
(Of course I can't use the navigation.goBack() which only navigate user back to the previous screen i.e. SettingOneScreen in this case.)
1-) use navigate.
//this will go back to Home and remove any screens after that.
navigation.navigate('Home')
docs say that.
In a stack navigator, calling navigate with a screen name will result in different behavior based on if the screen is already present or not. If the screen is already present in the stack's history, it'll go back to that screen and remove any screens after that. If the screen is not present, it'll push a new screen.
2-) use reset.
navigation.reset({
index: 0,
routes: [{ name: 'Home' }],
})}
see docs about reset
3-) use replace then goBack.
//from SettingsOne call replace instead navigate
//this will remove SettingsOne and push SettingsTwo
navigation.replace('SettingsTwo');
//then from SettingsTwo
//calling goBack will back to home because SettingsOne removed in last step
navigation.goBack();
4-) use one stack with pop.
import { StackActions } from '#react-navigation/native';
//merge two stacks in one
<NavigationContainer>
<rootStack.Navigator>
<rootStack.Screen name="Home" component={Home} />
<rootStack.Screen name="SettingsOne" component={SettingsOneScreen} />
<rootStack.Screen name="SettingsTwo" component={SettingsTwoScreen} />
</rootStack.Navigator>
</NavigationContainer>
//back 2 screen
navigation.dispatch(StackActions.pop(2));
see docs about pop
for methods 1, 2 you can try snack here.
I have faced the same problem before, this will help you more
<Button onPress={()=>navigation.navigate("Settings",{
screen: 'SettingsTwo',
params: { data: data }//put here the data that you want to send to SettingTow
)}
/>
//more explanation
goto = (data) => {
navigation.navigate('parent_stack', {
screen: 'screen_on_children_stack',
params: { data: data }
});
}
You can create a Switch navigator for the "root" app and create two stacks "home" and "setting".
const Root = ()=> (createAppContainer(createSwitchNavigator(
{
Home: Home,
Settings: Settings,
},
{
initialRouteName: 'Home',
}
)
// The Settings screen is a nested stack navigator
const Settings = ()=> {
const settingsStack = createStackNavigator();
return (
<settingsStack.Navigator>
<settingsStack.Screen name="SettingsOne" component={SettingsOneScreen} />
<settingsStack.Screen name="SettingsTwo" component={SettingsTwoScreen} />
</settingsStack.Navigator>
);
}
Then you can easily switch between stacks of Home and Settings
this.props.navigation.navigate('Settings');
https://reactnavigation.org/docs/upgrading-from-4.x/#dismiss
navigation.dangerouslyGetParent().pop();
I have made an splash page looks like this:
export default class Splash extends Component {
performTimeConsumingTask = async () => {
return new Promise((resolve) =>
setTimeout(() => {
resolve('result');
}),
);
};
async componentDidMount() {
const data = await this.performTimeConsumingTask();
// const navigation = useNavigation();
if (data !== null) {
this.props.navigation.navigate('BottomMainNavgigation');
}
}
render() {
return (
<View style={styles.viewStyles}>
{/* <Text style={styles.textStyles}>Welcome</Text> */}
<Image
style={styles.tinyLogo}
source={{
uri: URL.logov2,
}}
/>
</View>
);
}
}
THen I use this like this, in my navigation:
const RootStackScreen = (props) => {
const [t] = useTranslation();
return (
<SplashRootStack.Navigator>
<SplashRootStack.Screen
name="Main"
component={Splash}
options={{headerShown: false, headerBackTitle: t('back')}}
/>
<SplashRootStack.Screen
name="BottomMainNavgigation"
component={BottomMainNavgigation}
options={{headerShown: false, headerBackTitle: t('back')}}
/>
</SplashRootStack.Navigator>
);
};
and also:
<PaperProvider theme={MyTheme}>
<NavigationContainer linking={linking} fallback={<Splash />}>
<RootStackScreen />
</NavigationContainer>
</PaperProvider>
and like this in my app.js:
const App = () => {
return (
<Provider store={store}>
<PersistGate loading={<Splash />} persistor={persistor}>
<Suspense fallback={<Splash />}>
<Navigation />
</Suspense>
</PersistGate>
</Provider>
);
};
export default App;
when I run the application it looks like this:
there is a warning :
I get this warning with id= 0, 1 and 2 and I get this warning also:
what have I donr incorrectly ad how can I remove these warnings, also when I load the app in the emulator, I get a white screen for few seconds before I get my own splash page and then to my app.
how can I do this better?
You are using Splash component in redux persist as a loader and in your Splash component there isn't any navigation prop available because it's a parent component and not the part of navigation tree you need to use switch navigator for the same purpose but with the current structure navigation will not work unless you move navigation part inside the navigator tree. Now the solution is,
Use only splash as a static UI component.
Move you navigation or componentDidMount logic inside the stack navigator.
Add simple Activity indicator as fallback.
<PersistGate
loading={<ActivityIndicator style={{top: '45%'}}
animating color={theme.appColor} size='large' />}
persistor={ReduxStore.persistor}>
<Navigator />
</PersistGate>
Your warnings
Undefined is not an object :
The problem is that you are using the Splash as the fallback component so until your deeplink is resolved Splash would be displayed and the Splash here is not part of navigation which will not get the 'navigation' prop so you are getting the warning.
Same for the other higher order components like PersistGate and suspense you can given the splash for everything and all this is outsider navigation.
resolution : Use the activity indicator instead of splash for the fallback
This is due to one of your middleware in redux taking longer, better check your redux middleware.
White screen,
this is whats causing the white screen maybe caused by the same reason as your middleware warning or the component did mount of the splash screen. And you have several providers so better remove one or two and check whats causing that.
You can check this sample to get an idea on using splash screens and authentication.
https://snack.expo.io/#guruparan/rnn-v5
I have a React Native StackNavigator like so:
const AppStack = () => {
return (
<NavigationContainer theme={{ colors: { background: "white" }}}>
<Stack.Navigator headerMode="screen">
<Stack.Screen name="Master" component={ Master } />
<Stack.Screen
name="Details"
component={ Details }
options={{ headerTitle: props => <Header {...props} /> }} // <-- how can I pass props from Master to the Header here
/>
</Stack.Navigator>
</NavigationContainer>
)
}
The navigation works fine. When I'm on Master, if I press a TouchableOpacity, it brings up Details, with the header component Header.
However, what I want is to pass props from Master to the Header component in Details.
Something like this:
{/* What I want is that onPress, I want to pass someones_name and someones_photo_url to
the Details' Header component */ }
const Master = () => {
const someones_name = "Steve";
const someones_photo_url = "http://somephoto.com/001.jpg";
return (
<TouchableOpacity onPress={() => navigation.navigate("Details")}>
<Text>{ someones_name }</Text>
<Image source={{ uri: someones_photo_url }}>
</TouchableOpacity>
)
}
Is this possible?
You can pass values to other screens when navigating by doing the following:
Assume I am on screen "Feed" and click on someones name thus I want to go to screen "Profile" where it will show that user name that I clicked on.
In screen Feed
props.navigation.navigate('Profile, { userName: 'Shane' })`
In screen Profile I can grab that value passed in via navigate with:
const { userName } = props.route.params
To set this up inside a header assuming your screen options are outside the screen component and cannot access the props. Look into using setOptions to configure your header title dynamically.
props.navigation.setOptions({
headerTitle: ...
})
I am new to React Native and I have been building a simple Food Recipe app. My main Stack Navigator contains two screens: Bottom Tab Navigator and Recipe screen. I am in Home screen in the Tab Navigator and when I press any recipe, I want to pass recipe props and navigate to Recipe screen in the main Stack Navigator. Unfortunately, I haven't found any solution to this.
Could you please help me solve this?
This is the main file App.js
const Navigator = createBottomTabNavigator(
{
Home: Home,
Add: Add,
Profile: Profile
}
);
const Stack = createStackNavigator(
{
Navigator: Navigator,
Recipe: Recipe
},
{
headerMode: "none",
navigationOptions: {
headerVisible: false,
}
}
);
export default createAppContainer(Stack)
This is my Home Screen class
export default class Home extends Component {
render() {
return (
<SafeAreaView style={styles.container}>
<View style={styles.header}>
<Text style={styles.headerTitle}>Recipes</Text>
</View>
<ScrollView style={styles.categoryContainer}>
<Category name={"All Foods"} recipes={recipes} />
<Category name={"Meat"} recipes={[ recipes[1], recipes[3] ]} />
<Category name={"Vegetarian"} recipes={[ recipes[0], recipes[2] ]} />
<Category name={"Desserts"} recipes={[ recipes[4] ]} />
</ScrollView>
</SafeAreaView>
)
}
}
This is the Category class
export default class Category extends Component {
render() {
return (
<View style={styles.container}>
<View style={styles.categoryHeader}>
<Text style={styles.categoryHeaderTitle}>{this.props.name}</Text>
</View>
<ScrollView >
<View style={styles.categoryContent}>
<FlatList
data={this.props.recipes}
renderItem={({ item }) => <Block name={item.name} image={item.image} time={item.time} portions={item.portions} />}
keyExtractor={(item, index) => index.toString()}
horizontal={true}
/>
</View>
</ScrollView>
</View>
)
}
}
This is the actual recipe block where I want to navigate all the way back to the Recipe screen and pass recipe props using onPress={} function
export default class Block extends Component {
render() {
return (
<TouchableOpacity style={styles.container} onPress={null} >
<Image style={styles.image} source={this.props.image} />
<Text style={styles.title}>{this.props.name}</Text>
<Text style={styles.info}>{this.props.time} min | portions: {this.props.portions}</Text>
</TouchableOpacity>
)
}
}
Thank you for all your answers
If you need the StackNavigator to display a header on top of a BottomTabNavigator i would suggest to use a StackNavigator for every child of the bottomTabBar. Doing like that you can have more screens in a single tabBarItem, and you won't have the bottomTabBar disappearing for the other screens.
If you don't mind of this, you can access your stackNavigator from his child using dangerouslyGetParent and then navigate with the props you need.
navigateToRecipe = (recipe) => {
this.props.navigation.dangerouslyGetParent().navigate("Recipe", { recipe : myRecipe}) //pass an object with the data you need
}
Then you can access your recipe props either using getParam this.props.navigation.getParam("recipe") or directly using this.props.navigation.state.params