Hi I was wonder how I could pass props down the navigators or if there's a better way of designing my structure.
const MangaTabRouter = createMaterialTopTabNavigator(
{
Info: { screen: Manga },
Chapter: { screen: Chapter }
}
);
const Stack = createStackNavigator(
{
Main: {
screen: MangaTabRouter,
navigationOptions: {
header: props => <CustomHeader {...props} />
}
}
}
);
const HomeScreenRouter = createDrawerNavigator(
{
MyLibrary: { screen: HomeScreen },
Sources: { screen: Sources },
Manga: { screen: Stack }
},
{
contentComponent: props => <SideBar {...props} />,
initialRouteName: "MyLibrary"
}
);
const AppContainer = createAppContainer(HomeScreenRouter);
I then have a button that calls the Main URI and goes there as well as passing in some props.
this.props.navigation.navigate('Main', {data});
Currently, I use dangerouslyGetParent() to access the props but I find it not appropriate for my design and also it doesn't work with multiple Manga entries (ie. props don't change). I would ideally like to pass props (the data object) to both the Info and Chapter URIs under the tab navigator. I assume it has to do with passing props to the router itself? So my question is: is it possible to send props to sub children as well or would I have to somehow redesign my routers to pass in a component to Main that then has MangaTabRouter as a child component within it? Also, is there a way to pass the props that I sent to Info to the header as well? I'm experimenting but coming up empty handed. Thanks!
const MangaTabRouter = createMaterialTopTabNavigator(
{
Info: { screen: props => <Manga {...props}/> },
Chapter: { screen: props => <Chapter {...props}/> }
}
);
class ExtendStack extends React.Component {
static router = MangaTabRouter.router;
render() {
const { navigation } = this.props;
return <MangaTabRouter navigation={navigation} screenProps={navigation.state.params} />;
}
}
const Stack = createStackNavigator(
{
Main: {
screen: ExtendStack,
navigationOptions: {
header: props => <CustomHeader {...props} />
}
}
}
);
I had to use screenProps to pass in the new information. I'll take a closer look at the header and try with a similar approach.
you need to pass the data as item: this.props.navigation.navigate('Main', {data : item})
and call this.props.navigation.state.params.data in your child component
Related
I've created an app with several pages to navigate through and it works fine when I send navigation requests from child pages (goBack or navigate('...')). The problem is that I need to switch pages from a class that created the AppContainer. It looks more complicated. I tried in several ways with refs, but all the time navigate function is not accessible. I want to push a home page in my callback function of the BasicFooter.
I tried to use dispatch as well, but the same problem. I am not sure if it is even possible to make it work by reference to AppContainer or do I need to build a structure with
<NavigationContainer>
<Stack.Navigator>{/* ... */}</Stack.Navigator>
</NavigationContainer>
That's my code:
export default class App extends React.Component {
constructor(props) {
super(props);
this.refNavi = React.createRef();
this.goToMain_audio = this.goToMain_audio.bind(this);
}
goToMain_audio() {
console.log("Object ", this.refNavi.current.navigation.navigate('Home')); //<==Undefined!
}
render() {
return ( <
View style = {
styles.container
} >
<AppContainer ref = {
this.refNavi
}
style = {
styles.container
} </AppContainer>
<View style = {
styles.footer1
}>
<BasicFooter style = {
styles.footer1
}
clickedAudio = {
this.goToMain_audio
}
clickedMood = {
this.refNavi.current.navigate('Home')
} //<== Undefined!
>
<
/BasicFooter> </View>
}
}
const AppNavigator = createStackNavigator({
Home: {
screen: MainScreen,
},
SettingsPage: {
screen: Settings,
},
SettingsMain: {
screen: SettingsMain,
},
ConnectionPage: {
screen: Connection,
}
}, {
initialRouteName: "Home",
transitionConfig,
header: null,
headerMode: 'none'
});
const AppContainer = createAppContainer(AppNavigator);
I have a React Native app with a Navigator. I also have a few screens and a header (common to all screens).
In my header I have a button that toggles the app's language. Passing the language to the Navigator's props I'm able to access it in the screens and translate stuff around.
The only thing I can't change is the tabBarLabel property in the navigationOptions of each screen.
I've tried to simply write:
MyStack1.navigationOptions = {
tabBarLabel: myGlobalTranslationFunction('MyOriginalLabel1', this.props.screenProps.language)
};
But that didn't work.
As a tree, you can think of App being the root of my app, which has a state.language passed to the Navigator by:
<Navigator screenProps={ {language: this.state.language} }/>
In my Navigator.js file, I have, for example:
const MyStack1 = createStackNavigator({
MyScreen1: MyScreenComponent1,
MyScreen2: MyScreenComponent2,
MyScreen3: MyScreenComponent3,
});
MyStack1.navigationOptions = {
tabBarLabel: 'MyLabel1'
};
export default createBottomTabNavigator({
MyStack1,
MyStack2
},
{
initialRouteName: 'MyStack1',
tabBarOptions: {
showLabel: true
}
}
);
I think this problem lies in defining that the labels depend on the language prop, but I don't know how to do that. I thought about writing an actual class that extends React.Component for the Navigator, but I'm not sure how someone who do that, since the code above was basically the one I got by following the documentation.
What's react-navigation version you are using?
You can pass down the translations through screenProps of the Navigator.
<Navigator screenProps={{
myStack1: i18n._('stack1TabBarLabel'),
myStack2: i18n._('stack2TabBarLabel')
}}/>
And modify how createBottomTabNavigator create the component.
export default createBottomTabNavigator({
myStack1: {
screen: MyStack1,
navigationOptions: ({ screenProps }) => ({
tabBarLabel: screenProps && screenProps.myStack1
})
},
myStack2: {
screen: MyStack2,
navigationOptions: ({ screenProps }) => ({
tabBarLabel: screenProps && screenProps.myStack2
})
}
},
{
initialRouteName: 'myStack1',
tabBarOptions: {
showLabel: true
}
}
);
Having a bit of a struggle with understanding how to properly implement navigation in RN. The following code should be enough to see the current situation.
HeaderConnected is a component with a menu button which has a custom navigate prop to be able to open the Drawer menu (However, I believe this is not a proper way to pass navigate).
– Problem
HeaderConnected is in Main.js
– Expected
HeaderConnected to be above Navigator
App.js
// ...
import { DrawerNavigator } from "react-navigation";
export const Navigator = DrawerNavigator({
Main: { screen: Main },
Edit: { screen: EditScreen },
});
export default class App extends React.Component {
render() {
return (
<Provider store={createStore(reducer)}>
<Navigator />
</Provider>
);
}
}
Main.js
export class Main extends React.PureComponent {
static navigationOptions = {
drawerLabel: "Main",
};
render() {
return (
<View style={styles.container}>
<HeaderConnected
navigate={this.props.navigation.navigate}
/> // <--- This should be moved above <Navigator /> in App.js
<PostsConnected />
</View>
);
}
}
Of course, you could create another wrapper to hold both Navigator and HeaderConnected which you would then pass to Provider. However, this didn't work for me.
After reading react-navigator docs it seems there are several ways to handle this, alas not sure what would be the best/optimal way.
I had this problem in a recent project and worked it around by building a wrapper as you said, try using this code:
const mapNavigationStateParamsToProps = (SomeComponent) => {
return class extends Component {
static navigationOptions = SomeComponent.navigationOptions; // better use hoist-non-react-statics
render() {
const { navigation: { state: { params } } } = this.props
return (
<View style={{ flex: 1 }}>
<HeaderConnected
navigate={this.props.navigation.navigate}
/>
<SomeComponent {...params} {...this.props} />
</View>
)
}
}
}
then on your Navigator:
export const Navigator = DrawerNavigator({
Main: { screen: mapNavigationStateParamsToProps(Main) },
Edit: { screen: mapNavigationStateParamsToProps(EditScreen) },
});
I'm not sure if it is the best/optimal way, but it works for me.
How can I make navigationOptions interact with view inside of TabNavigator?
for example, from this question how do I access a component's state from static navigationOptions, I can make navigationOptions responsive to component's state.
But what if I want to make it responsive a step further, responsive to component's state inside of TabNavigator?
As image shows below, I want to make Next button enabled only if more than one checkbox get checked.
But there's no effect to provide headerRight in my component.
export class CreateGroupCamera extends Component {
static navigationOptions = ({ navigation }) => {
const { state, setParams, navigate } = navigation;
const params = state.params || {};
return {
headerTitleStyle: { alignSelf: 'center' },
headerRight: <Button title='Next' disabled={params.validator ? !params.validator() : true} />
}
}
}
I found an answer myself after try error 3 hours...
follow these steps you can have a workable version.
1. Instead of expose TabNavigator directly, wrap it.
const Tabs = TabNavigator({
...
});
export class TabView extends Component {
render() {
return (<Tabs />);
}
}
2. Pass callback function down with screenProps. When called, setParams() into navigationOptions.
export class TabView extends Component {
static navigationOptions = ({navigation}) => {
const { state, setParams, navigate } = navigation;
const params = state.params || {};
return {
headerRight = <Button title='Next' disabled={params.validator ? !params.validator() : true} />
};
}
_validator(validator) {
this.props.navigation.setParams({validator});
}
render() {
return (<Tabs screenProps={{ callback: this._validator } />);
}
}
3. Inside main component, pass function with screenProps.callback.
_validator() {
return this.selectedDevices().length > 0;
}
render() {
return (
<Checkbox onChecked={ () => {
this.props.screenProps.callback(this._validator);
}} />
);
}
result:
Im following this tutorial https://reactnavigation.org/docs/intro/ and im running into a bit of issues.
Im using the Expo Client app to render my app every time and not a simulator/emulator.
my code is seen down below.
I originally had the "SimpleApp" const defined above "ChatScreen" component but that gave me the following error:
Route 'Chat' should declare a screen. For example: ...etc
so I moved the decleration of SimpleApp to just above "AppRegistry" and that flagged a new error
Element type is invalid: expected string.....You likely forgot to export your component..etc
the tutorial did not add the key words "export default" to any component which I think it may have to do with the fact that im running it on the Expo app? so I added "export default" to "HomeScreen" and the error went away.
The new error that I cant seem to get rid off(based on the code below) is the following:
undefined is not an object (evaluating 'this.props.navigation.navigate')
I can't get rid of it unless I remove the "{}" around "const {navigate}" but that will break the navigation when I press on the button from the home screen
import React from 'react';
import {AppRegistry,Text,Button} from 'react-native';
import { StackNavigator } from 'react-navigation';
export default class HomeScreen extends React.Component {
static navigationOptions = {
title: 'Welcome',
};
render() {
const { navigate } = this.props.navigation;
return (
<View>
<Text>Hello, Chat App!</Text>
<Button
onPress={() => navigate('Chat')}
title="Chat with Lucy"
/>
</View>
);
}
}
class ChatScreen extends React.Component {
static navigationOptions = {
title: 'Chat with Lucy',
};
render() {
return (
<View>
<Text>Chat with Lucy</Text>
</View>
);
}
}
const SimpleApp = StackNavigator({
Home: { screen: HomeScreen },
Chat: { screen: ChatScreen },
});
AppRegistry.registerComponent('SimpleApp', () => SimpleApp);
Additional Info:
When you are nesting child components, you need to pass navigation as prop in parent component.
//parent.js
<childcomponent navigation={this.props.navigation}/>
And you can access navigation like this
//child.js
this.props.navigation.navigate('yourcomponent');
Reference: https://reactnavigation.org/docs/en/connecting-navigation-prop.html
With Expo you should't do the App registration your self instead you should let Expo do it, keeping in mind that you have to export default component always:
Also you need to import View and Button from react-native: please find below the full code:
import React from 'react';
import {
AppRegistry,
Text,
View,
Button
} from 'react-native';
import { StackNavigator } from 'react-navigation';
class HomeScreen extends React.Component {
static navigationOptions = {
title: 'Welcome',
};
render() {
const { navigate } = this.props.navigation;
return (
<View>
<Text>Hello, Chat App!</Text>
<Button
onPress={() => navigate('Chat', { user: 'Lucy' })}
title="Chat with Lucy"
/>
</View>
);
}
}
class ChatScreen extends React.Component {
// Nav options can be defined as a function of the screen's props:
static navigationOptions = ({ navigation }) => ({
title: `Chat with ${navigation.state.params.user}`,
});
render() {
// The screen's current route is passed in to `props.navigation.state`:
const { params } = this.props.navigation.state;
return (
<View>
<Text>Chat with {params.user}</Text>
</View>
);
}
}
const SimpleAppNavigator = StackNavigator({
Home: { screen: HomeScreen },
Chat: { screen: ChatScreen }
});
const AppNavigation = () => (
<SimpleAppNavigator />
);
export default class App extends React.Component {
render() {
return (
<AppNavigation/>
);
}
}
As Bobur has said in his answer, the navigation prop isn't passed to children of the routed component. To give your components access to navigation you can pass it as a prop to them, BUT there is a better way.
If you don't want to pass the navigation prop all the way down your component hierarchy, you can use useNavigation instead. (Which in my opinion is just cleaner anyways, and reduces the amount of code we have to write):
function MyBackButton() {
const navigation = useNavigation();
return (
<Button
title="Back"
onPress={() => {
navigation.goBack();
}}
/>
);
}
https://reactnavigation.org/docs/use-navigation/
This is just really nice because if you have multiple levels of components you wont have to continuously pass the navigation object as props just to use it. Passing navigation just once requires us to 1. Add a prop to the component we want to pass it to. 2. Pass the prop from the parent component. 3. Use the navigation prop to navigate. Sometimes we have to repeat steps 1 and 2 to pass the prop all the way down to the component that needs to use navigation. We can condense steps 1 and 2, no matter how many times they are repeated, into a single useNavigation call with this method.
I think it is best.
Try this Code: onPress={() => this.props.navigation.navigate('Chat')}
<ChildComponent navigation={props.navigation} {...data} />
This will make the navigation from the parent propagated to the subsequent child navigations.
const AppNavigation =()=>{ <SimpleApp />}
export default class App extends React.Componet{
render(){
return (
<AppNavigation/>
);
}
}