I'm fairly new to programming in general and even newer to JS and React(Native) but I have worked on this for an entire day now and I still haven't figured it out so I have resorted to Stack Overflow in hopes that someone can help me.
Basically what I want to accomplish is to set other Components as children of the App component because I want them to be able to access information that I will set in the state of App. However, at the same time, I am also using react-navigation to create bottom navigation bars and thus I have no idea on how I can pass props of App to these other Components such as the ExplorePage component which is representative of the other children components.
App
import React from 'react';
import ExplorePage from './app/tabs/ExplorePage';
import {createBottomTabNavigator} from 'react-navigation';
...other imports
class App extends React.Component {
state = {
parentState: 'testing testing',
}
}
const MainScreenNavigator = createBottomTabNavigator(
{
Home: {screen: ExplorePage},
Search: {screen: SearchPage},
Favorites: {screen: FavoritesPage},
}
);
export default MainScreenNavigator;
ExplorePage, which is just like SearchPage and FavoritesPage
...imports
export default class ExplorePage extends React.Component {
constructor(props) {
super(props);
this.state = {
}
}
componentDidMount() {
console.log(this.props.parentState ? this.props.parentState : "Parent state does not exist what do :(");
}
render(){
return(
<Text>Testing</Text>
)
}
And obviously every time the console prints that parentState does not exist. I thought that being in the same place would give the other components like ExplorePage props of App. Thanks for helping me!
for those who are looking for a React Navigation 5 solution, you can use initialParams like this:
<Stack.Navigator>
<Stack.Screen
name="screenName"
component={screenComponent}
initialParams={{key: value}}
/>
</Stack.Navigator>
You could pass a props using function. Try this
import React from 'react';
import ExplorePage from './app/tabs/ExplorePage';
import {createBottomTabNavigator} from 'react-navigation';
...other imports
class App extends React.Component {
state = {
parentState: 'testing testing',
}
render() {
// old
// const MainScreenNavigator = mainScreenNavigator(this.state.parentState);
const MainScreenNavigator = mainScreenNavigator(this.state);
return (
<MainScreenNavigator />
)
}
}
const mainScreenNavigator = value => createBottomTabNavigator(
{
// Home: { screen : props => <ExplorePage {...props} parentState={value} /> },
Home: { screen : props => <ExplorePage {...props} {...value} /> },
Search: {screen: SearchPage},
Favorites: {screen: FavoritesPage},
}
);
export default App;
Edit
First thing, I changed your MainScreenNavigator to be a function, as it is accepting state values dynamically.
Second thing, Instead of directly assigning { screen : Component }, I used function. This is the feature provided by reactnavigation. You can find about this in the documentation. ReactNavigation
If you want to pass multiple attributes then you can use es6 spread operator, as shown in the edit. {...value}, this will pass all the property of value to that component.
You should use Navigator Props "screenProps" as mentionned in API:
screenProps - Pass down extra options to child screens
On child screen, just take props via this.props.screenProps
Related
I'm using react native class component and I'm using react-navigation to route in the app. the guide is mostly for the functional component and I'm trying to implement it with class components. but when i trying to get it from reactnavigation it always throws me error that navigation is not a function or undefined. Im sorry if this is an already asked question as I'm really new to this react native.
class component
import React from 'react';
import { useNavigation } from '#react-navigation/native';
import { Button, Divider, Layout, TopNavigation ,Card,Text} from '#ui-kitten/components';
class HomeScreen extends React.Component {
navigateDetails(navigation) {
debugger
navigation('Details');
};
constructor(props) {
super(props);
console.log(this.props);
this.state = { hover: false };
}
render() {
const navigation = this.props;
return(
<Button onPress={this.navigateDetails}>OPEN DETAILS</Button>
);
}
}
export default function (props) {
const navigation = useNavigation();
return <HomeScreen {...props} navigation={navigation} />;
}
First, you have to make sure navigation prop is exist in your class component (As looking in your code its already exist) and the second thing is this.props.navigation is an object, not a function which holds different function like navigate, push etc so you have to execute these functions, here are some changes I did in your code and I hope this will work for you.
class HomeScreen extends React.Component {
constructor(props) {
super(props);
console.log(this.props);
this.state = { hover: false };
}
navigateDetails() {
this.props.navigation.navigate('Details');
};
render() {
const navigation = this.props;
return(
<Button onPress={()=>this.navigateDetails()}>OPEN DETAILS</Button>
);
}
}
export default function (props) {
const navigation = useNavigation();
return <HomeScreen {...props} navigation={navigation} />;
}
Seems I have to pass that navigation to the function as a variable in order to work. so I have changed the function as bellow to pass the navigation to the
function and also changed this.navigateDetails(navigation) to
()=>this.navigateDetails(navigation)
also, I destructed the navigation like const {navigation} = this.props;
full code line
onPress={()=>this.navigateDetails(navigation)}
I am using createMaterialTopTabNavigator from react-navigation in which i have two separate screens UpdatesStack and ShopsStack and i want to navigate to other screen from these screens so i written like <Toptab navigation={this.props.navigation} /> and it showing me following red screen error.
And if i write like <Toptab /> then there is no error but i am not able to navigate.
so how can i solve this problem and able to navigate.
code
class Parenthome extends Component {
render() {
const { navigate } = this.props.navigation;
return (
<View style={styles.container}>
<ToolbarAndroid
style={styles.toolbar}
title="Toolbar"
titleColor="#ff6600"
/>
<Toptab navigation={this.props.navigation} />
</View>
);
}
}
const UpdatesStack = createStackNavigator(
{
Updates: { screen: Home }
},
{
initialRouteName: "Updates"
}
);
const ShopsStack = createStackNavigator(
{
Shops: { screen: Conshop }
},
{
initialRouteName: "Shops"
}
);
const Toptab = createMaterialTopTabNavigator({
Updatestab: { screen: UpdatesStack },
Shopstab: { screen: ShopsStack }
});
export default Parenthome;
I know it's late but just to answer for those who stumble on this from Search Engines:
Why don't you export default TopTab itself. There seems no need to wrap TopTab with ParentTheme component in your use case. You can style the TopTab navigator itself and render it like any other component.
If you must wrap the TopTab you need to have the router from the TopTab accessible, in addition to the navigation prop. This way they both refer to the same router. Simply put, add in ParentTheme:
static router = TopTab.router;
Check out Custom Navigators for more info. https://reactnavigation.org/docs/en/custom-navigators.html
if you are using functional react components with hooks you won't be able to declare a static variable inside your components because they are not JS classes.
Instead declare the router variable as follows:
const reactComponent = (props) => {
/* your component logic and render here */
}
reactComponent.router = TopTab.router; //equivalent to static variable inside a class
export default reactComponent
I created a multiscreen app using React Navigator following this example:
import {
createStackNavigator,
} from 'react-navigation';
const App = createStackNavigator({
Home: { screen: HomeScreen },
Profile: { screen: ProfileScreen },
});
export default App;
Now I'd like to add a global configuration state using the new builtin context api, so I can have some common data which can be manipulated and displayed from multiple screens.
The problem is context apparently requires components having a common parent component, so that context can be passed down to child components.
How can I implement this using screens which do not share a common parent as far as I know, because they are managed by react navigator?
You can make it like this.
Create new file: GlobalContext.js
import React from 'react';
const GlobalContext = React.createContext({});
export class GlobalContextProvider extends React.Component {
state = {
isOnline: true
}
switchToOnline = () => {
this.setState({ isOnline: true });
}
switchToOffline = () => {
this.setState({ isOnline: false });
}
render () {
return (
<GlobalContext.Provider
value={{
...this.state,
switchToOnline: this.switchToOnline,
switchToOffline: this.switchToOffline
}}
>
{this.props.children}
</GlobalContext.Provider>
)
}
}
// create the consumer as higher order component
export const withGlobalContext = ChildComponent => props => (
<GlobalContext.Consumer>
{
context => <ChildComponent {...props} global={context} />
}
</GlobalContext.Consumer>
);
On index.js wrap your root component with context provider component.
<GlobalContextProvider>
<App />
</GlobalContextProvider>
Then on your screen HomeScreen.js use the consumer component like this.
import React from 'react';
import { View, Text } from 'react-native';
import { withGlobalContext } from './GlobalContext';
class HomeScreen extends React.Component {
render () {
return (
<View>
<Text>Is online: {this.props.global.isOnline}</Text>
</View>
)
}
}
export default withGlobalContext(HomeScreen);
You can also create multiple context provider to separate your concerns, and use the HOC consumer on the screen you want.
This answer takes in consideration react-navigation package.
You have to wrap your App component with the ContextProvider in order to have access to your context on both screens.
import { createAppContainer } from 'react-navigation'
import { createStackNavigator } from 'react-navigation-stack'
import ProfileContextProvider from '../some/path/ProfileContextProvider'
const RootStack = createStackNavigator({
Home: { screen: HomeScreen },
Profile: { screen: ProfileScreen },
});
const AppContainer = createAppContainer(RootStack)
const App = () => {
return (
<ProfileContextProvider>
<AppContainer />
</ProfileContextProvider>);
}
https://wix.github.io/react-native-navigation/docs/third-party-react-context/
As RNN screens are not part of the same component tree, updating the values in the shared context does not trigger a re-render across all screens. However you can still use the React.Context per RNN screen component tree.
If you need to trigger a re-render across all screens, there are many popular third party libraries such as MobX or Redux.
const MainNavigator = StackNavigator({
Home: {
screen: Tabs
},
Collection_Products : {
screen : Collection_Products,
navigationOptions : ({ navigation }) => ({
title : `${navigation.state.params.name.toUpperCase()}`
}),
},
MainProduct : {
screen : (props) => <MainProduct {...props} />,
},
});
export default class MainNav extends React.Component {
constructor() {
super();
this.state = {
checkout: { lineItems: { edges: [] } }
};
}
method1 = (args) => {
}
render() {
return (
<View style={{flex: 1}}>
<MainNavigator checkout= {this.state.checkout} method1 = {this.method1}/>
</View>
);
}
}
I am using react native to build application. I want to pass method 1 to different child components. How can I pass method1 function in MainProduct Component? With the syntax I am using I am only able to pass navigator props to child components.
The accepted answer is outdated for React Navigation v5 and v6
screenProps is no longer available, which caused several problems.
Use react context instead
Quote from react-navigation 5.x,
Due to the component based API of React Navigation 5.x, we have a much better alternative to screenProps which doesn't have these disadvantages: React Context. Using React Context, it's possible to pass data to any child component in a performant and type-safe way, and we don't need to learn a new API!
Alternatively, you may use routeProps
Passing parameters to routes. v6 doc
navigation.navigate('RouteName', { /* params go here */ })
In case you are new to context
Here is how you may use context to pass props.
Example:
import React from 'react'
// Create context outside of your component
// which can be export to your child component
const MyContext = React.createContext()
export default function MyParentComponent() {
const myContext = {
whatIWhatMyChildToKnow: 'Hi, I am your father.',
}
return (
<MyContext.Provider value={myContext}>
// Dont pass your own props to your navigator
// other than RNavigation props
<YourStackNavigator>
...stack logic
</YourStackNavigator>
</MyContext.Provider>
)
}
// access your context value here
function MyChildScreen() {
const { whatIWhatMyChildToKnow } = React.useContext(MyContext)
// will display 'Hi, I am your father.'
return <span>{whatIWantMyChildToKnow}</span>
}
You need to send the props as below. You need to send props name as 'screenProps' literally then only it is send. Yeah, This is little strange. I tried to change the name of the props, it did not get trough.
const propsForAll = {
checkout:/*your checkout data */,
method1: /**Your medhod1*/
}
<View style={{flex: 1}}>
<MainNavigator screenProps={propsForAll}/>
</View>
I think you may want screenProps, from the docs:
const SomeStack = StackNavigator({
// config
});
<SomeStack
screenProps={/* this prop will get passed to the screen components as
this.props.screenProps */}
/>
My app.js file looks like this
export default class App extends React.Component {
render() {
return (
<Root style={{
flex: 1
}}>
<FcmHandler/>
</Root>
)
}
}
The Root component is where the entire app resides along with all the functionality, the FcmHandler is where I handle functionality related to notifications etc. Within the FcmHandler I have a method that gets a callback when a notification is clicked, inside this callback I need to navigate to a specific screen in the app based on the notification click.
The problem is using the current code above the FcmHandler component never even gets initialized.
If I try something like this
export default class App extends React.Component {
render() {
return (
<View style={{
flex: 1
}}>
<Root/>
<FcmHandler/>
</View>
)
}
}
the FcmHandler component gets called but I do not have any access to navigation props which reside inside the <Root/> component.
The <Root/> component consists of the following
const ArticleStack = StackNavigator(
{
...
}
);
const SettingsStack = StackNavigator({
...
});
export const Root = StackNavigator({
Articles: {
screen: ArticleStack
},
Settings: {
screen: SettingsStack
},
}, {
mode: 'modal',
headerMode: 'none'
});
The basic goal I am trying to achieve is, when a notification is click, irrespective of which screen the app is currently on I should be able to navigate to a particular screen. I do not want to write the navigation code in every screen component that I have, that seems redundant.
You can follow this official guide to create your navigation service. Then use the navigation service in FcmHandler instead of navigation prop. This way there is no need to put FcmHandler as a child of the navigator.
If you are using redux or mobx, it's better to move your navigation state to the store for easier access. For redux, there is an official integration guide. For mobx, you can try this.
For react-navigation users, a really cool way is to create your own Navigation Service
You can initialize your Navigation Service module, during the time initializing your navigation store as mentioned in their docs
<AppNavigator navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav,
addListener,
})} />
// Just add another line to config the navigator object
NavigationService.configNavigator(dispatch) <== This is the important part
NavigationService.js
import { NavigationActions } from 'react-navigation'
let config = {}
const configNavigator = nav => {
config.navigator = nav
}
const reset = (routeName, params) => {
let action = NavigationActions.reset({
index: 0,
key: null,
actions: [
NavigationActions.navigate({
type: 'Navigation/NAVIGATE',
routeName,
params,
}),
],
})
config.navigator(action)
}
const navigate = (routeName, params) => {
let action = NavigationActions.navigate({
type: 'Navigation/NAVIGATE',
routeName,
params,
})
config.navigator(action)
}
const navigateDeep = actions => {
let action = actions.reduceRight(
(prevAction, action) =>
NavigationActions.navigate({
type: 'Navigation/NAVIGATE',
routeName: action.routeName,
params: action.params,
action: prevAction,
}),
undefined
)
config.navigator(action)
}
const goBack = () => {
if (config.navigator) {
let action = NavigationActions.back({})
config.navigator(action)
}
}
export default {
configNavigator,
navigateDeep,
navigate,
reset,
goBack,
}
Explanation :
The config initializes the navigator's dispatch object whenever your redux-navigation gets initialzed, therefore you can dispatch any navigation action, wrt the method's present in the Service Component.
Use
NavigationServices.navigate('ScreenName')
Update:
React Navigation now provides a HOC wrapper withNavigation, that passes the navigation prop into a wrapped component.
It's useful when you cannot pass the navigation prop into the component directly, or don't want to pass it in case of a deeply nested child.
Usage is well mentioned in their docs.
After a bit of research, the easiest way I found was to follow their official documentation:
I created a RootNavigation.js file in the ./misc folder;
import * as React from 'react';
export const navigationRef = React.createRef();
export function navigate(name, params) {
navigationRef.current?.navigate(name, params);
}
I imported it into App.js and created a reference to it in the return function:
import React from 'react'
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { navigationRef } from './misc/rootNavigation'; <- navigationRef is imported
…
const Stack = createStackNavigator();
function App() {
return (
<Provider store={store}>
<NavigationContainer ref={navigationRef}> <— reference to navigationRef
<Stack.Navigator>
…
<Stack.Screen
name="Screen"
component={Screen}
options={{
title: “Hello”,
headerLeft: () => <ScreenButton/>
}} />
</Stack.Navigator>
</NavigationContainer>
</Provider>
);
}
export default App
I called it inside the ScreenButton component
import React, { Component } from 'react'
…
import * as RootNavigation from '../misc/rootNavigation'; <—- imported
class RoomButton extends Component {
constructor(props) {
super(props)
}
render() {
return (
<TouchableOpacity onPress={
() => {RootNavigation.navigate( 'RoomSelectorScreen' ) <—- called here
…
</TouchableOpacity>
)
}
}