How to pass current state of App to Tab Navigation Screen
Asked Answered
G

3

1

If I'm using React Navigation v5, what is the best way to pass the current state of a parent component (in my case, the main App) down through a Tab and Stack navigator to a screen that I'd like to use the current state in?

Following the documentation, I have created a stack navigator for each tab that holds the respective screens.

App.js contains a state that needs to be used for a few things. Most importantly, it will provide badge count on the Tab navigator, as well as be a source of Flatlist data on one of the tab screens.

What is the correct approach to getting the state from App all the way down to a child component in a stack navigator in a tab navigator?

App.js

const Tab = createBottomTabNavigator()

export default class App extends React.Component {
  constructor(props){
    super(props)
    this.state = {
      neededArray: []
    }
  }

  const updateTheArray = (newArray) => {
    this.setState({
      neededArray: newArray
    })
  }

  componentDidMount(){
    //Listener that searches for nearby bluetooth beacons and updates the array with the passed function
    startObserver(updateTheArray)
  }

  componentWillUnmount(){
    stopObserver()
  }

  render(){
    return(
      <NavigationContainer>
        <Tab.Navigator>
          <Tab.Screen
            name = "Home"
            component = { HomeStack }/>
          <Tab.Screen
            name = "About"
            component = { AboutStack }/>

          //The Stack that contains the screen that I need to use the App's state in
          <Tab.Screen
            name = "Nearby"
            component = { NearbyStack }/>
        </Tab.Navigator>
      </NavigationContainer>
    )
  }
}

NearbyStack.js

//This stack holds the screen that I need to use the App's state in

const NearbyStackNav = createStackNav()

const NearbyStack = () => {
  return(
    <NearbyStackNav.Navigator>
      <NearbyStackNav.Screen
        name = "Nearby"
        component = { NearbyScreen }
      />
    </NearbyStackNav.Navigator>
  )
}

NearbyScreen.js

//The screen that I want to use the App's state in
const NearbyScreen = () => {
  return(
    <View>
      <FlatList
        //Where I would like to use the App's state
      />
    </View>
  )
}
Gyno answered 26/3, 2020 at 14:55 Comment(0)
G
0

My solution was to use React's Context API.

BeaconContext.js - New

import React from 'react'

const BeaconContext = React.createContext()

export default BeaconContext

App.js - Modified

import BeaconContext from './path/to/BeaconContext'

const Tab = createBottomTabNavigator()

export default class App extends React.Component {
  constructor(props){
    super(props)
    this.state = {
      neededArray: []
    }
  }

  const updateTheArray = (newArray) => {
    this.setState({
      neededArray: newArray
    })
  }

  componentDidMount(){
    startObserver(updateTheArray)
  }

  componentWillUnmount(){
    stopObserver()
  }

  render(){
    return(
      // Wrap the nav container in the newly created context!!!

      <BeaconContext.Provider value = { this.state.neededArray }
        <NavigationContainer>
          <Tab.Navigator>
            <Tab.Screen
              name = "Home"
              component = { HomeStack }/>
            <Tab.Screen
              name = "About"
              component = { AboutStack }/>
            <Tab.Screen
              name = "Nearby"
              component = { NearbyStack }/>
          </Tab.Navigator>
        </NavigationContainer>
      </BeaconContext.Provider>
    )
  }
}

NearbyStack.js - Unchanged

const NearbyStackNav = createStackNav()

const NearbyStack = () => {
  return(
    <NearbyStackNav.Navigator>
      <NearbyStackNav.Screen
        name = "Nearby"
        component = { NearbyScreen }
      />
    </NearbyStackNav.Navigator>
  )
}

NearbyScreen.js - Modified

import BeaconContext from './path/to/BeaconContext'

const NearbyScreen = () => {
  return(
    <View>
      //Wrap the component in the new context's consumer!!!

      <BeaconContext.Consumer>
      {
        context => <Text>{ context }</Text>
      }
      </BeaconContext.Consumer>
    </View>
  )
}
Gyno answered 13/4, 2020 at 20:23 Comment(0)
C
2

You can pass some initial params to a screen. If you didn't specify any params when navigating to this screen, the initial params will be used. They are also shallow merged with any params that you pass. Initial params can be specified with an initialParams prop:

Usage

<Tab.Screen
            name = "Nearby"
            component = { NearbyStack }
            initialParams={{ arrayItem: this.state.neededArray }}
 />

NearbyScreen.js

React.useEffect(() => {
    if (route.params?.arrayItem) {
      // Post updated, do something with `route.params.arrayItem`
      // For example, send the arrayItem to the server
    }
  }, [route.params?.arrayItem]);
Caerleon answered 26/3, 2020 at 15:7 Comment(4)
I did notice the initialParams prop, but does this update as the parent (App) 's state is updated with this.setState?Gyno
Yes, it properly detects the nearby items and fires the App's updateTheArray function (I have it log the updated value with console.log, but I excluded it from the example to keep it as simple as possible)Gyno
I have just tested this approach and it looks like the initialParams prop does not get updated when the parent component's state is updated. The App component initializes the neededArray as an empty array. Then the setState is called four times before I navigate to the goal Tab/Screen. When I tap the tab, I log the route and see that the params still has the initial empty array for 'neededArrray'Gyno
It is the first thing in the NearbyScreen's functional component. When I log the route params from there I get {"key": "Nearby-QQF9G7Ux1u", "name": "Nearby", "params": {"nearbyArray": []}} so it definitely seems that the prop passed down is still the value as initialized instead of the updated state that has four values in the array.Gyno
G
0

My solution was to use React's Context API.

BeaconContext.js - New

import React from 'react'

const BeaconContext = React.createContext()

export default BeaconContext

App.js - Modified

import BeaconContext from './path/to/BeaconContext'

const Tab = createBottomTabNavigator()

export default class App extends React.Component {
  constructor(props){
    super(props)
    this.state = {
      neededArray: []
    }
  }

  const updateTheArray = (newArray) => {
    this.setState({
      neededArray: newArray
    })
  }

  componentDidMount(){
    startObserver(updateTheArray)
  }

  componentWillUnmount(){
    stopObserver()
  }

  render(){
    return(
      // Wrap the nav container in the newly created context!!!

      <BeaconContext.Provider value = { this.state.neededArray }
        <NavigationContainer>
          <Tab.Navigator>
            <Tab.Screen
              name = "Home"
              component = { HomeStack }/>
            <Tab.Screen
              name = "About"
              component = { AboutStack }/>
            <Tab.Screen
              name = "Nearby"
              component = { NearbyStack }/>
          </Tab.Navigator>
        </NavigationContainer>
      </BeaconContext.Provider>
    )
  }
}

NearbyStack.js - Unchanged

const NearbyStackNav = createStackNav()

const NearbyStack = () => {
  return(
    <NearbyStackNav.Navigator>
      <NearbyStackNav.Screen
        name = "Nearby"
        component = { NearbyScreen }
      />
    </NearbyStackNav.Navigator>
  )
}

NearbyScreen.js - Modified

import BeaconContext from './path/to/BeaconContext'

const NearbyScreen = () => {
  return(
    <View>
      //Wrap the component in the new context's consumer!!!

      <BeaconContext.Consumer>
      {
        context => <Text>{ context }</Text>
      }
      </BeaconContext.Consumer>
    </View>
  )
}
Gyno answered 13/4, 2020 at 20:23 Comment(0)
C
0

I've been struggling with the exact same issue - when using the initialProps property to pass a state to a Tab.Screen the screen never receives any updates. It reads the intial state value once then nothing.

To make it work I skipped using the initialProps property and instead used the children property on Tab.Screen like so:

App containing <Tab.Navigator> and <Tab.Screen>:

const[myBool, setMyBool] = useState(false)

<Tab.Screen
   name="MyTab"
   children={() => (
     <MySecondScreen passedStateParam={ myBool } />
   )}
   .
   .
   .
</Tab.Screen>

MySecondScreen consuming updates on passed myBool state:

export function MySecondScreen ({ passedStateParam }) {
    const myPassedBoolState = passedStateParam
    React.useEffect(() => {
        if(myPassedBoolState) {
             //Act upon App.tsx updating the state
        }
    }, [myPassedBoolState])
}

Not sure if I'm missing something when trying to perform this with the initialParams property but this way (using children property) I got it to work at least.

Crackbrained answered 16/12, 2022 at 13:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.