React Native - pass props from One screen to another screen (using tab navigator to navigate) - reactjs

I need to pass data from my HomeScreen to my SecondScreen. There are a ton of examples of how to do this if i'm clicking a button on the HomeScreen to navigate to SecondScreen, but can't find anything showing how to pass to SecondScreen if I'm using a v2 bottom tab navigator to go from HomeScreen to SecondScreen. I've tried screenprops and a couple other methods and spent about 8 hours trying to figure it out but could not get it to work. Any idea how to do this? Please, any hint would be amazing. Here is my code:
MainTabNavigator.js:
const config = Platform.select({
web: { headerMode: 'screen' },
default: {},
});
const HomeStack = createStackNavigator(
{
Home: HomeScreen,
},
config
);
HomeStack.navigationOptions = {
tabBarLabel: 'Home',
tabBarIcon: ({ focused }) => (
<MaterialIcons name="home" size={32} />
),
};
HomeStack.path = '';
const SecondStack= createStackNavigator(
{
Second: SecondScreen,
},
config
);
SecondStack.navigationOptions = {
tabBarLabel: 'Second screen stuff',
tabBarIcon: ({ focused }) => (
<MaterialIcons name="SecondScreenIcon" size={32} />
),
};
SecondStack.path = '';
const tabNavigator = createBottomTabNavigator({
HomeStack,
SecondScreen
});
tabNavigator.path = '';
export default tabNavigator;
HomeScreen.js:
class HomeScreen extends Component {
constructor(props){
super(props);
}
componentDidMount(){
this.setState({DataFromHomeScreen: 'my data that Im trying to send to SecondScreen'})
}
//....
SecondScreen.js:
class SecondScreen extends Component {
constructor(props){
super(props);
}
render()
return(
<View>{this.props.DataFromHomeScreen}</View>
)
//....
****Please find THINGS I'VE TRIED below:****
HomeScreen.js: when i do this it receives it at first but then passes null
render(){
return(
<View>
//all of my home screen jsx
<SecondScreen screenProps={{DataFromHomeScreen : 'data im trying to pass'}}/>
</View>
)
}
MaintTabNavigator.js: when i do this it receives it at first but then passes null
HomeStack.navigationOptions = {
tabBarLabel: 'Home',
tabBarIcon: ({ focused }) => (
<MaterialIcons name="home" size={32} />
),
};
<HomeStack screenProps={{DataFromHomeScreen:'data im trying to pass'}}/>
HomeStack.path = '';
I've tried like 5 other ways too that I can't even remember at this point. I don't want to have to call my database again in the second screen to get user info. Nobody I know knows react or react native. The React Native documentation at https://reactnavigation.org/docs/en/stack-navigator.html is minimal at best, only showing the below:
const SomeStack = createStackNavigator({
// config
});
<SomeStack
screenProps={/* this prop will get passed to the screen components as this.props.screenProps */}
/>
even if you go to the examples in the documentation and search for the word 'screenprop' you will not see any mention of the screen prop feature in either of the examples. All questions that I've seen only address how to pass props on button click which is easy. Is what I'm trying to do possible? I'm sure I'm not the only person using tab navigator who's retrieved data in the homescreen and need to pass it to other screens . Any advice helps. thanks.
ps.
Here is my Sign in class that is calling the Home screen:
class SignInScreen extends React.Component {
static navigationOptions = {
title: 'Please sign in',
};
render() {
return (
<View
style={styles.container}
contentContainerStyle={styles.contentContainer}>
<View>
<SocialIcon
title='Continue With Facebook'
button
type='facebook'
iconSize="36"
onPress={this._signInAsync}
/>
</View>
);
}
_signInAsync = async () => {
let redirectUrl = AuthSession.getRedirectUrl();
let result = await AuthSession.startAsync({
authUrl:
`https://www.facebook.com/v2.8/dialog/oauth?response_type=token` +
`&client_id=${FB_APP_ID}` +
`&redirect_uri=${encodeURIComponent(redirectUrl)}`,
});
var token = result.params.access_token
await AsyncStorage.setItem('userToken', token);
await fetch(`https://graph.facebook.com/me?fields=email,name&access_token=${token}`).then((response) => response.json()).then((json) => {
this.props.navigation.navigate('Home',
{
UserName : json.name,
FBID : json.id,
email : json.email
});
}) .catch(() => {
console.log('ERROR GETTING DATA FROM FACEBOOK')
});
};
}
export default SignInScreen;

I think you're calling your database in componentDidMount in your HomeScreen component, (I'm right?) and because another component in the same hierarchy needs the same data, you should considerer wrapping this into a new component and do the call to your data in that father component, then you pass the data to all the children that needs it. This is the react way to do things. The state of HomeScreen should not have the data, your data should live in a parent component in a higher hierarchy and pass the data to children as props.
In this way when you create your tabs you can pass the props as the react native docs suggest:
import { createBottomTabNavigator, BottomTabBar } from 'react-navigation-tabs';
const TabBarComponent = (props) => (<BottomTabBar {...props} />);
const TabScreens = createBottomTabNavigator(
{
tabBarComponent: props =>
<TabBarComponent
{...props}
style={{ borderTopColor: '#605F60' }}
/>,
},
);
Another solution could be to use a global state management with Redux or something similar.
I hope this helps.
Edit:
class Home extends React.Component{
constructor(props){
super(props);
this.state = {data: null}
}
componentDidMount() {
//get your props from navigation (your facebook credentials)
//your call to database
this.setState({data: yourResponseData});
}
render(){
const TabNavigator = createBottomTabNavigator(
{
HomeScreen: props =>
<HomeScreenStack
{...this.state.data}
/>,
SecondStack: props =>
<SecondStack
{...this.state.data}
/>,
},
);
return(
<TabNavigator />
)
}
}
const App = createAppContainer(Home);
export default App;

Use this.props.navigation.navigate.
In your HomeScreen, once you have the data you want to send, then navigate over to SecondScreen like so:
this.props.navigation.navigate('Second', { data: yourData })
To access this in SecondScreen whenever it is navigated to using navigation props, you can use NavigationEvents along with this.props.navigation.getParam.
/* your imports */
import { NavigationEvents } from 'react-navigation';
export default class SecondScreen extends React.Component {
/* your methods and properties */
render() {
<View>
<NavigationEvents
onDidFocus={() => this.setState({ data: this.props.navigation.getParam('data', {}) })}
/>
{ /* your SecondScreen render code */ }
</View>
}
}
Edit: For example, with your SignInScreen implementation, to access the props, use:
const username = this.props.navigation.getParam('UserName', '')
const fbid = this.props.navigation.getParam('FBID', 0)
const email = this.props.navigation.getParam('email', '')

This is the basic approach that I am using:
import {createBottomTabNavigator} from '#react-navigation/bottom-tabs';
const TestComponent = (props) => {
return <Text>{`TestComponent: ${props.name}`}</Text>;
};
const Home = () => {
const Tab = createBottomTabNavigator();
return (
<View style={{flex: 1}}>
<Tab.Navigator>
<Tab.Screen name="Screen 1">
{() => <TestComponent name="test 1" />}
</Tab.Screen>
<Tab.Screen name="Screen 2">
{() => <TestComponent name="test 2" />}
</Tab.Screen>
</Tab.Navigator>
</View>
);
};
Notice that to pass the props to a Screen I am using a child function instead of passing a value to component. The child function can then return the component you want in the syntax you are used to, that has the props available. In this case, the props are the simple name, but you can expand this to handle your state.

I ended up using Redux, it only took me like 100 read throughs and attempts to learn it, but once I learned it it's amazing and simple.

Related

react-navigation wrap root AppContainer with react Context

I'm looking for the way to manage global state across my react-native app using react-navigation. I tried to implement basic React Context, which I wanted to wrap around the react-navigation's createAppContainer() method but it didn't work.
I ended up wrapping an app container from index.js file using Context's HOC, but it seems like react-navigation has a problem with re-rendering of nested components, when Context's state is changed. I can access my Context from nested Components but they just aren't re-rendered when context state is changed.
My index.js file looks like:
import { AppRegistry } from "react-native";
import App from "./src/App";
import { withAppContextProvider } from "./src/AppContext";
import { name as appName } from "./app.json";
AppRegistry.registerComponent(appName, () => withAppContextProvider(App));
My context class looks like:
// for new react static context API
export const AppContext = createContext({});
// create the consumer as higher order component
export const withAppContext = ChildComponent => props => (
<AppContext.Consumer>
{context => <ChildComponent {...props} global={context} />}
</AppContext.Consumer>
);
// create the Provider as higher order component (only for root Component of the application)
export const withAppContextProvider = ChildComponent => props => (
<AppContextProvider>
<ChildComponent {...props} />
</AppContextProvider>
);
export class AppContextProvider extends Component {
state = {
isOnline: true
};
handleConnectivityChange = isOnline => {
this.setState({ isOnline });
};
componentDidMount = async () => {
NetInfo.isConnected.addEventListener(
"connectionChange",
this.handleConnectivityChange
);
};
componentWillUnmount() {
NetInfo.isConnected.removeEventListener(
"connectionChange",
this.handleConnectivityChange
);
}
render() {
return (
<AppContext.Provider
value={{
...this.state
}}
>
{this.props.children}
</AppContext.Provider>
);
}
}
my App.js file looks like:
const HomeStack = createStackNavigator(
{
Home: HomeScreen,
Cities: CitiesScreen
},
getStackConfig({ initialRouteName: "Home" })
);
const SettingsStack = createStackNavigator(
{
Settings: SettingsScreen
},
getStackConfig({ initialRouteName: "Settings" })
);
export default createAppContainer(
createBottomTabNavigator(
{
Home: HomeStack,
Settings: SettingsStack
}
)
);
CitiesScreen component example:
import { AppContext } from "../AppContext";
class CitiesScreen extends Component {
static contextType = AppContext;
render() {
return (
<View style={styles.container}>
<Text>This value should change on isOnline update: {this.context.isOnline}</Text>
</View>
);
}
}
Now, when I'm accessing Context, from for example CitiesScreen component, I'm currently able to get the value of isOnline state of context but whenever I switch my internet connection (on android emulator) on/off, the context state is changed but the component isn't re-rendered and my shouldComponentUpdate() method isn't triggered. Any help to make this work?
In my case, I downgraded React from 16.8 to 16.5.0, react navigation version is 3.
I'm still investigating but that's a temporary solution for now.

How to pass props in StackNavigator

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 */}
/>

Get navigation props in an independent component React Native

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>
)
}
}

Pass state data between component through navigator

my issue is look so simple but I can't figure is out how to solve it
I have two component that I navigate to them by using StackNavigator,
in one of the component I have state with values, I need to pass the state values to the other component, but as I said I using StackNavigator to go from one component to the other, I trying to figure out how can I do so
my navigator:
const Navigate = StackNavigator(
{
Home: { screen: HomeScreen },
Riddles: { screen: RiddlesScreen },
Introduction: { screen: IntroductionScreen },
About: { screen: AboutScreen },
},
{ headerMode: 'screen' }
);
My homescreen class:
class HomeScreen extends Component {
static navigationOptions = {
header: null
}
render() {
return (
<View>
<Home navigation={this.props.navigation} />
</View>
);
}
}
my riddlescreen class:
class RiddlesScreen extends Component {
static navigationOptions = {
header: null
}
render() {
return (
<View>
<Riddles navigation={this.props.navigation} />
</View>
);
}
}
in the Riddles component (at the RiddleScreen class) I have state with the values that I need to pass to Home component (at HomeScreen class).
what is the best way to achieve this goal?
every help really appreciated! thanks.
You can pass data between screens while navigating. More info here.
Example
<Button onPress={() => {this.props.navigation.navigate('SomeScreen', { some: this.state.someValue})}} />
// and in SomeScreen
console.log(this.props.navigation.state.params.some);

How to get current navigation state

I am using React Navigation's Tab Navigator from https://reactnavigation.org/docs/navigators/tab, when I switch between the Tab Screens I don't get any navigation state in this.props.navigation.
Tab Navigator:
import React, { Component } from 'react';
import { View, Text, Image} from 'react-native';
import DashboardTabScreen from 'FinanceBakerZ/src/components/dashboard/DashboardTabScreen';
import { TabNavigator } from 'react-navigation';
render() {
console.log(this.props.navigation);
return (
<View>
<DashboardTabNavigator />
</View>
);
}
const DashboardTabNavigator = TabNavigator({
TODAY: {
screen: DashboardTabScreen
},
THISWEEK: {
screen: DashboardTabScreen
}
});
DASHBOARD SCREEN:
import React, { Component } from 'react';
import { View, Text} from 'react-native';
export default class DashboardTabScreen extends Component {
constructor(props) {
super(props);
this.state = {};
console.log('props', props);
}
render() {
console.log('props', this.props);
return (
<View style={{flex: 1}}>
<Text>Checking!</Text>
</View>
);
}
}
I get props at Dashboard Screen when it renders the component first but I don't get props when I switch the tabs.
I need to get the current Screen name i.e TODAY or THISWEEK.
Your problem is about "Screen Tracking", react-navigation has an officially guide for this. you can use onNavigationStateChange to track the screen by using built-in navigation container or write a Redux middleware to track the screen if you want to integrate with Redux. More detail can be found at the officially guide: Screen-Tracking. Below is a sample code from the guide by using onNavigationStateChange:
import { GoogleAnalyticsTracker } from 'react-native-google-analytics-bridge';
const tracker = new GoogleAnalyticsTracker(GA_TRACKING_ID);
// gets the current screen from navigation state
function getCurrentRouteName(navigationState) {
if (!navigationState) {
return null;
}
const route = navigationState.routes[navigationState.index];
// dive into nested navigators
if (route.routes) {
return getCurrentRouteName(route);
}
return route.routeName;
}
const AppNavigator = StackNavigator(AppRouteConfigs);
export default () => (
<AppNavigator
onNavigationStateChange={(prevState, currentState) => {
const currentScreen = getCurrentRouteName(currentState);
const prevScreen = getCurrentRouteName(prevState);
if (prevScreen !== currentScreen) {
// the line below uses the Google Analytics tracker
// change the tracker here to use other Mobile analytics SDK.
tracker.trackScreenView(currentScreen);
}
}}
/>
);
Check all properties first, like
<Text>{JSON.stringify(this.props, null, 2)}</Text>
Above json array will show you current state of navigation under routeName index i.e.
this.props.navigation.state.routeName
Have you tried to define navigationOptions in your route object?
const DashboardTabNavigator = TabNavigator({
TODAY: {
screen: DashboardTabScreen
navigationOptions: {
title: 'TODAY',
},
},
})
You can also set navigationOptions to a callback that will be invoked with the navigation object.
const DashboardTabNavigator = TabNavigator({
TODAY: {
screen: DashboardTabScreen
navigationOptions: ({ navigation }) => ({
title: 'TODAY',
navigationState: navigation.state,
})
},
})
Read more about navigationOptions https://reactnavigation.org/docs/navigators/
Answer as of React Navigation v6
Depending on whether you want to trigger re-renders on value changes:
const state = navigation.getState();
or
const state = useNavigationState(state => state);
Reference:
https://reactnavigation.org/docs/use-navigation-state#how-is-usenavigationstate-different-from-navigationgetstate

Resources