react-navigation: Can the screens have different names then what is passed in the routeConfig? - reactjs

I have a situation where I do not intend for the keys I pass in the route config to be presented to the user:
const routes = {
'tab/Search': Search,
'tab/Scan': Scan,
'tab/Me': Me
};
const Nav1 = createBottomTabNavigator(routes, ...);
const routes = {
'stack/Camera': Camera,
'stack/Home': Home,
'stack/ItemDetails': ItemDetails,
'stack/SearchItems': SearchItems
};
const Nav2 = createStackNavigator(routes, ...);
I need to use these keys as identifiers for the screens in code so I can tell whether we're coming from a stack navigator screen or a tab navigator screen by checking routeName.startsWith('tab/'). Is it possible to change the display name in the UI? Thanks.

In your actual screen component:
export class Search extends Component {
static navigationOptions = {
title: 'Search'
}
...etc
}
You can also use tabBarLabel in that object for screens within a tab bar.

Related

React-navigation crash when going back from a nested navigation

I'm using React-navigation 6 on my React native application
I have two StackNavigator in a BottomStackNavigator with those screens and params on each stack :
export type BottomNavigationStack = {
Planner: undefined,
Profile: undefined,
}
export type PlannerNavigationStack = {
PlannerHome: undefined,
PlannerDetails: {
detail: { ... }
},
Profile: {
screen: string,
initial?: boolean,
params?: { [name: string]: string }
}, // We can go to ProfileNavigationStack from PlannerNavigationStack
}
export type PlannerNavDetailProps = StackScreenProps<PlannerNavigationStack, 'PlannerDetails'>
export type ProfileNavigationStack = {
ProfileHome: undefined,
ProfileAddThings: {
service: string
from: string,
},
Planner: {
screen: string,
initial?: boolean,
params?: { [name: string]: string }
}, // We can go to PlannerNavigationStack from ProfileNavigationStack
}
export type ProfileNavAddThingsProps = StackScreenProps<ProfileNavigationStack, 'ProfileAddThings'>
So i'm trying to navigate from PlannerStack to ProfileStack, when I'm in ProfileStack, I want to go back to PlannerStack.
I got no problem going to ProfileStack from PlannerStack like this :
navigation.navigate('Profile', {
screen: 'ProfileAddThings',
params: {
service: 'myservice',
from: 'myfrom',
},
initial: false,
})
So at that moment I'm on ProfileStack on screen ProfileAddThings.
if I read the documentation, the PlannerStack keep it's history, and if I click on the bottomTab button of Planner I can see the page is still PlannerDetails.
But, when I click on the back button of the screen ProfileAddThings, I'm going back to ProfileHome.
I tried to overide the backButton action of the screen ProfileAddThings with that code :
navigation.navigate('Planner', { screen: 'PlannerDetails' })
But I got the error : undefined is not an object (evaluating 'route.params.detail')
Detail is a parameter of PlannerDetails screen.
I really don't understand why this parameter is undefined because de PlannerStack history is still present.
Someone has already gone back from a nested navigation in react native ?
Thanks
You need to get the parent navigator.
You can do this by using the getParent() method from the current screen.
Will be something like this:
let parent = navigation.getParent(); // This will assing PlannerStack to the parent variable.
then you should call navigation method with the Route name
parent.navigate('PlannerDetails');
The route history, register the hole component from the screen (in your case PlannerStack) and in your PlannerStack the innitialRoute probably is ProfileHome, thats the reason you get there when using the back button.

How to create React elements in an array (with props) and then render them by simply mapping over that array?

I am wondering if there is a way to create elements in an array and then simply render them, if that is possible it will simplify my code a bit.
Right now I am defining sections for a page in one file, then that gets sent to another and so on, so at the moment it looks like this
const heroSectionProps = { ... }
const pageLayout = {
sections: [{name: "Hero", props: heroSectionProps}]
}
and later when rendering the page I am doing the following:
import { Hero } from "~/sections"
const section = {
Hero: Hero
}
export const Page = ({ sections, ...props }) => {
return sections.map(({ name, props }) => {
const Section = section[name];
return <Section {...props} />
})
}
And that works just fine, now the question is, is it possible to somehow move to a syntax that would look more like this:
import { Hero } from "~/sections"
const heroSectionProps = { ... }
const pageLayout = {
sections: [Hero(heroSectionProps)]
}
and then in page:
export const Page = ({ sections, ...props }) => {
return sections.map(Section => (<Section />))
}
So the idea is to already have the components created within the layout object and then to simply pass them to the page to directly render them. Of course, doing it the above way, I got an error:
Warning: React.jsx: type is invalid -- expected a
string (for built-in components) or a class/function (for composite components) but got: <HeroContainer />. Did you accidentally export a JSX literal
instead of a component?
And other similar about invalid type.
So is that even possible, worth it and how?
You can render an array, so long as it's filled with renderable values (strings, numbers, elements and components). So, say we have an array of components:
import Hero from '../Hero';
const componentsArray = [<p>Hello world!</p>, <div><img src="images/flower.png" /></div>, <Hero {...heroSectionProps} />];
You can instantly render it:
const Component = (props) => {
//...
return componentsArray;
};

Use constant value as typescript key name

I am trying to deine and configure all of my screens for react-navigation in a single file a bit like this:
export const SCREEN_1: ScreenConfig = { path: '/screen-1', component: ... };
export const SCREEN_2: ScreenConfig = { path: '/screen-2', component: ... };
This then allows me to render and navigate to the screens like this:
import * as screens from './screens';
screens.map(screen => <Screen name={screen.path} component={screen.component} />);
...
import { SCREEN_1 } from './screens'
navigation.navigate(SCREEN_1.path, props);
I now also wanna add type checking for the props passed to the navigate function. react-navigation allows me to define types like this:
type ScreenParams = {
'/screen-1': Screen1Props,
'/screen-2': Screen2Props,
}
Is there a way to automatically generate the ScreenParams type withouth having to define the path in a second location.
The best I got so far is something like this:
type ScreenParams = {
'/screen-1': React.ComponentProps<SCREEN_1.component>,
'/screen-2': React.ComponentProps<SCREEN_2.component>,
}

React Navigation navigate to specific tab

The example on React Navigation shows how you can jump to a tab, but it shows it in the case you are in the navigator the tabs are part of.
But how do you navigate to a specific tab from a screen which is part of a different navigator?
I have:
function MealsNavigator() {
const MealsStack = createStackNavigator();
return (
<MealsStack.Navigator>
<MealsStack.Screen
name="Meals"
component={MealsScreenTabs}
/>
</MealsStack.Navigator>
);
}
function MealsScreenTabs() {
const MealsTabs = createMaterialTopTabNavigator();
return (
<MealsTabs.Navigator>
<MealsTabs.Screen name="Upcoming" component={MealsUpcomingScreen} />
<MealsTabs.Screen name="Past" component={MealsPastScreen} />
</MealsTabs.Navigator>
);
}
I want to be able to navigate to Meals > Past with a button click from my Profile screen inside the Account navigator. How?
Got some help from the community on Discord. Here' how you do it:
navigation.navigate('Meals', {
screen: 'Meals',
params: {screen: 'Past'},
})
And to see it in action, have a look at a snack I created
in react navigation v5
navigation.navigate('Meals', {screen: 'Past'});

react-navigation deep linking with multiple paths

Is there any way to configure react-navigation so that single screen can handle multiple links?
Each screen in StackNavigator can have an optional property path which enabled deep links, StackNavigator also accepts paths option that lets u override paths per specific screen but it's still one-to-one mapping.
Is there a way to declare unlimited amount of paths that should be handled by single screen?
You can use variables for unlimited number of paths like its shown in StackNavigator docs
Example from docs
StackNavigator({
// For each screen that you can navigate to, create a new entry like this:
Profile: {
// `ProfileScreen` is a React component that will be the main content of the screen.
screen: ProfileScreen,
// When `ProfileScreen` is loaded by the StackNavigator, it will be given a `navigation` prop.
// Optional: When deep linking or using react-navigation in a web app, this path is used:
path: 'people/:name',
// The action and route params are extracted from the path.
// Optional: Override the `navigationOptions` for the screen
navigationOptions: ({ navigation }) => ({
title: `${navigation.state.params.name}'s Profile'`,
}),
},
...MyOtherRoutes,
});
Update
You can create a custom route handler for more detailed control over paths shown here.
Example from docs
import { NavigationActions } from 'react-navigation'
const MyApp = StackNavigator({
Home: { screen: HomeScreen },
Profile: { screen: ProfileScreen },
}, {
initialRouteName: 'Home',
})
const previousGetActionForPathAndParams = MyApp.router.getActionForPathAndParams;
Object.assign(MyApp.router, {
getActionForPathAndParams(path, params) {
if (
path === 'my/custom/path' &&
params.magic === 'yes'
) {
// returns a profile navigate action for /my/custom/path?magic=yes
return NavigationActions.navigate({
routeName: 'Profile',
action: NavigationActions.navigate({
// This child action will get passed to the child router
// ProfileScreen.router.getStateForAction to get the child
// navigation state.
routeName: 'Friends',
}),
});
}
return previousGetActionForPathAndParams(path, params);
},
});

Resources