React Navigation - How to pass data across differe

2019-07-21 16:03发布

问题:

I have a TabNavigator, and in each tab is a StackNavigator. Inside the StackNavigator, I have screens. The screens in each Tab do not call each other directly; the TabNavigator handles the screen changes when a tab is pressed.

In the first tab, if the user clicks a button, some data is created. If the user then navigates to the second Tab, I would like to pass this data to the screen in the second Tab.

Here is a demo of the code:

import React from 'react';
import { Button, Text, View } from 'react-native';
import {
  createBottomTabNavigator,
  createStackNavigator,
} from 'react-navigation';


class HomeScreen extends React.Component {
  doIt = () => {
    this.props.navigation.setParams({results: ['one', 'two']});   // <--- set data when user clicks button.
  }

  render() {
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        {/* other code from before here */}
        <Button
          title="Set Results"
          onPress={this.doIt}
        />
      </View>
    );
  }
}

class SettingsScreen extends React.Component {
  render() {

    console.log(this.props.navigation);  // <--- console out when user clicks on this tab

    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <Text>Settings</Text>
      </View>
    );
  }
}

const HomeStack = createStackNavigator({
  Home: HomeScreen,
});

const SettingsStack = createStackNavigator({
  Settings: SettingsScreen,
});

export default createBottomTabNavigator(
  {
    Home: HomeStack,
    Settings: SettingsStack,
  },
  {
  }
);

The this.props.navigation.state.params never gets the data results in the second Tab. There isn't even a key for it, so if I try to access this.props.navigation.state.params.results, it will be undefined.

This is confusing because I thought props.navigation is passed to all screens automatically.

How can I pass data from one screen to another through the TabNavigator, using just react-navigation? I have seen answers that say to use Redux, but I would not like to import another library if all I want is to keep some state across screens in different react navigators.

回答1:

It may seem that this.props.navigation.state.params is only able to old one parameter? Possibly? Try this:

    doIt = () => {
    this.props.navigation.setParams({results: 'one'});   // <--- set data when user clicks button.
  }


console.log(this.props.navigation.state.params.results);


回答2:

I came across a similar problem. I had a multi page form that the client insisted on having each step be enclosed in a tab on a tab bar. I used the react navigation createMaterialTopTabNavigator to create the navigator and couldn't find an easy way to pass the form data between tabs.

What I end up doing was using react's Context API and wrapped the tab navigator in a root form container that provides the context value to the navigator and routes inside. Here is how I did it:

Root form container

// MultiScreenForm.js
imports...
import MultiScreenFormNavigator from './MultiScreenFormNavigator'

export const FormContext = React.createContext()

class MultiScreenForm extends Component {
  constructor(props) {
    super(props)
    this.state = {
      // formDataHere
      formUpdaters: {
        onToggleOpIn: this.handleToggleOptIn // example updater method
        // other
      }
  }
  handleToggleOptIn = () => {
    // toggle opt in form data with this.setState
  }

  render() {
    return (
      <FormContext.Provider value={this.state}>
        <MultiScreenFormNavigator />
      </FormContext.Provider>
    )
  }
}

export default MultiScreenForm

Example form page

// ProfileForm.js
imports...
import { FormContext } from './MultiScreenForm'

class ProfileForm extends Component {
  render() {
    // FormContext.Consumer uses function as child pattern
    return (
      <FormContext.Consumer>
        { (context) => (
             // our form can now use anything that we pass through the context
             // earlier, we passed the root form's state including an updater
             <button onPress={context.formUpdaters.onToggleOptIn} />
             // ...
          )
        }
      </FormContext.Consumer>
    )
  }
}

export default ProfileForm

Tab navigator

// MultiScreenFormNavigator.js
imports...
import ProfileForm from './ProfileForm'
import { createMaterialTopTabNavigator } from 'react-navigation'

const MultiScreenFormNavigator = createMaterialTopTabNavigator(
  {
    Profile: ProfileForm,
    // AnotherForm: AnotherForm
  },
  // { navigator options here... }
)

export default MultiScreenFormNavigator

We then render the MultiScreenForm instead of the tab navigator directly.

This worked for me but I feel there should be an easier way to do this. I hope people who read this can share their approaches.



回答3:

Setting props did not work when passing data across different tabs. I even tried playing with AsyncStorage, trying to save and retrieve them in different tabs.

I ended up using Redux to save my states, and that has worked well so far.



回答4:

@tempomax tried same with AsyncStorage but data came in with a delay. Sometimes you don't need Redux if your app stays small.

So tried to find a way without Redux. Here is what I came up with

I hope it's not too late to answer. Solved it with NavigationEvents and setting params to Route.

The problem with tab is that you can´t pass params to screen because navigation.navigate will be triggered automatically if createMaterialTopTabNavigator is swiped or clicked on non-active TabBar Button.

This can be solved with NavigationEvent like follow.

    import React from 'react';
    import { View } from 'react-native';
    import { NavigationEvents } from 'react-navigation';

    const MyScreen = () => (
      <View>
        <NavigationEvents
          onWillFocus={payload => console.log('will focus',payload)}
          onDidFocus={payload => console.log('did focus',payload)}
          onWillBlur={payload => 
             /*
              if screen is about to change this will be triggred
              In screen 'MyScreen2' you can get it with navigation.params
             */
              this.props.navigation.navigate('MyScreen2', { name: 'Brent' })
          }
          onDidBlur={payload => console.log('did blur',payload)}
        />
        {/* 
          Your view code
        */}
      </View>
    );

    export default MyScreen;

Now you can get the data in MyScreen2

 /* 2. Get the param, provide a fallback value if not available */
const { navigation } = this.props;
const itemId = navigation.getParam('name', 'DefaultName');
const otherParam = navigation.getParam('otherParam', 'some default value');