Render different tabs on BottomTabNavigator based on params - react-native

I have an app that starts with a loader screen where I determine whether the user is an admin or not. This screen navigates to a BottomTabNavigator, but I want to show different tabs based on whether the user is admin or not. I looked at the documentation for custom navigators but this still requires the navigator to be created outside the class, so I can't use the params. I also tried this:
export default class BottomTabNavigator extends Component {
render() {
const BottomTabComponent = createBottomTabNavigator({
...
});
return (
<BottomTabComponent {...this.props} />
);
}
}
But that doesn't work either (seems like I shouldn't pass the navigation prop down). Removing {...this.props} is also no good because then I would have to wrap the component in an app container, but that is also not right, so I'm not sure how to proceed.

Related

How to pass data from one component to Navigator and from Navigator to component

I'm actually doing a login system and when my user is logged, the login component has to send data to my main screen but I don't really understand how to do it.
Actually I've this when user is logged :
User.username = data.username;
User.id = responseJson.id;
User.token = responseJson.token;
this.props.navigation.navigate('Main', { User: User });
User is where everything is saved and it works.
Data is sended to Main a Switch Navigates inside an AppContainer :
export default createAppContainer(createSwitchNavigator(
{
Auth: { screen: AuthStack, navigationOptions: { tabBarVisible: false } },
Main: { screen: MainStack },
},
{
initialRouteName: "Auth"
}),
);
so it goes on Mainstack who's a bottomTabNavigator and works like that :
const MainStack = createBottomTabNavigator(
{
Services: {
screen: Home,
navigationOptions: {
tabBarLabel:"Services",
tabBarIcon: ({ tintColor }) => (
<Icon name="home" size={30} color="#0033CC" />
)
},
},
I know it's not complete I've other screens not only Services, just wanted to avoid too long function on paste.
So is that possible to send this data "User" to my Home screen ?
I'm open to any suggestion or answer, that's my first react Native project so there is maybe some mistakes :)
You'll probably want to persist the user data in the app (save it in the storage for when the user leaves the app then enters he stays logged in). That's why react-navigation has the switchNavigator (the one you are using). In order to persist data, you can use the AsyncStorage to save the user data and in the switch navigator constructor check whether the user data is already available in the storage or not then decide whether to navigate to the app or the authentication page. In fact React Navigation provides an example on this: https://reactnavigation.org/docs/en/auth-flow.html . Since you're new to RN, here's an extra tip on managing data: you can use a State in every component that is only for that specific component (which is also reactive), you can use global data (store) shared between all the components in the app (check react redux/flux/mobx), you can share data from parent components to children components by passing data/functions in the props (aka attributes), and you can use the new React context api to use a provider (also like a store shared between parent and children components)
I've made something maybe a bit more understandable :
I get my user data in component login from an external api and it works well. To go from login to home I'm using this :
this.props.navigation.navigate('Main', { User: User})
So it goes on Main who opens Mainstack Screen who's a bottomTabNavigator and if I don't do any mistakes my User data that I've sent is on this Mainstack but I don't know how to get it and how to share it to the other components.
Every component got his own js file, there is only the App container and Authstack/Mainstack that are on the same file.
Hope you understood it !

How to pass parameters to a deep react-navigation Stack Navigator

I have a react-native app using react-navigation. I want to pass some props all along the stack navigator, but not sure what is the right thing to do.
To give more details, my screens are:
ListThingsScreen: List all things.
ShowThingScreen: Show details of the thing.
EditThingScreen: Edit the thing.
They are organized in such way:
const HomeStack = createStackNavigator(
{
ListThings: ListThingsScreen,
ShowThing: ShowThingScreen,
EditThing: EditThingScreen,
},
{
initialRouteName: "ListThings"
}
);
export default createBottomTabNavigator({
HomeStack
});
An app user:
Log in.
See all things (in ListThingsScreen).
Click on one of them, jump to ShowThingScreen to see its details.
Click on a button of the details screen, jump to EditThingScreen to edit the thing.
Once finished editing, jump back to ShowThingScreen.
In ListThingsScreen componentDidMount(), I will make async call to fetch logged in user data such as userId, username, etc. I want all screens in the stack knows about them to behave correctly when show and when edit.
Solutions I can think of:
Pass parameters to routes, as suggested by react-navigation docs. This works, but as I have more screens in the stack, I write too much code like navigation.push("RouteName", {user}) and navigation.getParam("user", "No user").
This is pretty much the only solution I have found. An alternative is to use React context. I like this approach better, however I want to use react-navigation and the concept of Router and Route don't exist in the library.
// ListItemsScreen.js
import { BrowserRouter as Router, Route } from "react-router-dom";
const UserContext = React.createContext();
<UserContext.Provider value={{ user }}>
<Router>
<div className="app-container">
<Route path="/show/:thingId" component={ShowThing} />
<Route path="/edit/:thingId" component={EditThing} />
</div>
</Router>
</UserContext.Provider>
// ShowItemScreen.js
<UserContext.Consumer>
{({user}) => (...)}
</UserContext.Consumer>
Which approach is more "correct"? If it is the 2nd approach, how can I do it with react-navigation?

React navigation state management without redux

I'm using React Navigation library for my React Native project and struggling to understand how to handle state with it.
In normal React Native application I can have state at the top level component and pop events from child components via props, however with React Navigation it seems that I cannot pass any props to components used as Screens.
After reading through related GitHub issue it seems that library devs are very opinionated in forcing everyone to use some kind of global event handler - redux or mobx, I guess.
The handler which needs to modify the following state. I got stuck when I started to try to move the state inside the app as I couldn't figure out how to:
Pass the handler to the TaskForm component.
Pass the state as props to TaskList if its rendered as part of App.js
Please, avoid replying "just use redux". I believe that using redux in this example would be massive overkill.
I use react native and react navigation in my app without redux, and so far it’s working great. The trick is passing screenProps all the way down the line.
For example, I have a More view. I create a basic More view with 2 sub views in a stack:
class More extends Component {
render() {
return <something>
}
}
class SubView1 extends Component {...}
class SubView2 extends Component {...}
Then I create the stack:
const MoreStack = StackNavigator({
More: {
screen: More
},
SubView1: {
screen: SubView1,
},
...
}, options);
And then I create a wrapper class that I export:
export default class MoreExport extends Component {
static navigationOptions = {
title: "More"
}
render() {
return <MoreStack screenProps={this.props.screenProps} />;
}
}
If all of this is in More.js, I can just import More from More.js and use it anywhere else.
If I then pass in screenProps to my root view and then have a wrapper class for each layer, I can pass the screenProps all the way down, and all views can access them using this.props.screenProps.
I use a wrapper like the one above around each StackNavigator and TabNavigator, and the screenProps are passed all the way down.
For example, in my root class’s render method, I could do:
return <More screenProps={{prop1: something, prop2: somethingElse}} />
And then the More class and each SubView in the MoreStack would all have access to these props.
Feel free to let me know if you want more clarification!
Disclaimer: I don’t know if this is the correct or recommended way to do it, but it does work
You can set param to navigation like this:
static navigationOptions = ({ navigation }) => {
return {
tabBarIcon: ({ tintColor, focused }) =>
<View>
<Icon name="bell-ring" size={24} color={focused ? 'green' : 'black'} />
{(navigation.state.params.badgeCount && navigation.state.params.badgeCount > 0) ?
<Text>{navigation.state.params.badgeCount}</Text>
:
<View></View>
}
</View>
}
}
and change badgeCount by:
this.props.navigation.setParams({ badgeCount: 3 })
After being inspired by steffeydev I looked more around react community issues and found a very simple example of wrapper using function.
It's surprisingly simple solution and I don't know why I didn't think about it before.
The function is the following:
const createComponent = (instance, props) =>
navProps => React.createElement(instance, Object.assign({}, props, navProps));
Thanks for inspiration and pointing me to screenProps which lead me to finding this solution.
I was struggling with the same issue and tried some of the other answers before discovering the following part of the documentation for React Navigation: https://reactnavigation.org/docs/en/stack-navigator.html#params.
Essentially, each Screen in the Stack can be passed params which can include handlers and then the various screens can interact with the application state.
My general structure is then to have an App class with state and handlers and the handlers are then passed into each Navigation Screen as needed. I'm not sure if I have this pattern right, but it's the way I understood the general React tutorial.
Example: In my demo app, I have a page flow like this:
Park finder screen -> Park detail screen (with Bookmark action)
Bookmark list screen -> Park detail screen
If you find a park, you can click on a Bookmark button which adds the park to the list of bookmarks shown on the Bookmark screen. You can then click on a park bookmark to see the details.
My App looks generally like this:
import React, { Component } from 'react';
// Screens
import ParkFinderScreen from './Components/ParkFinderScreen';
import ParkBookmarksScreen from './Components/ParkBookmarksScreen';
import ParkDetailsScreen from './Components/ParkDetailsScreen';
// Navigation
import { createStackNavigator, createBottomTabNavigator, createAppContainer } from 'react-navigation';
class App extends Component {
constructor(props) {
super(props);
this.state = {
bookmarks: new Map()
};
}
// Bookmark the park shown in the detail section.
handleBookmark (park) {
let newBookmarks = this.state.bookmarks;
newBookmarks.set(park.parkCode, park);
this.setState({
bookmarks: newBookmarks
});
}
render() {
const FinderStack = createStackNavigator(
{
ParkFinder: {
screen: ParkFinderScreen
},
ParkFinderDetails: {
screen: ParkDetailsScreen,
params: {
handleBookmark: (park) => this.handleBookmark(park),
}
},
}
);
const BookmarksStack = createStackNavigator(
{
ParkBookmarks: {
screen: ParkBookmarksScreen,
params: {
bookmarks: this.state.bookmarks
}
},
ParkBookmarksDetails: {
screen: ParkDetailsScreen,
},
}
);
const AppNavigator = createBottomTabNavigator(
{
Bookmarks: BookmarksStack,
Finder: FinderStack,
}
);
const AppContainer = createAppContainer(AppNavigator);
return (
<AppContainer/>
);
}
}
export default App;
I'm using Apollo Client, but I've removed those parts.
In the Screen components, you can access the props like other ones using this.props.navigation.getParam('bookmarks').
One issue I encountered was that whenever I change the App state, I'm taken to the first screen. The state is updated, but it's a little disorienting. I'm not sure if there is a way to update the App state while staying on the screen. I can sort of understand that given the App state has updated, all the children need to be updated and so the current screen (which is part of a child I think) is reset. I don't know if that is a limitation of the system or a byproduct of how I designed the components.
I hope this helps someone. This seems to keep with the intended behavior of React Native. The library team really seems to want you to not use Redux. https://reactnavigation.org/docs/en/redux-integration.html

React Native get navigation object outside screen component

I need to be able to navigate and reset navigation stack from modules that are not necessarily screen components. It means, I can't use:
const {navigate} = this.props.navigation;
In my case, I need to properly redirect user to the right screen when he taps on push notification. I only have a callback:
onNotification: function(notification) {
// show correct screen and reset navigation stack
}
I just need more flexibility with navigation. So how can I do it for my case?
Here is my solution. I implemented base class for all screens. With this I always know what screen I am at and can always get navigation object to redirect user whenever needed:
import React, {Component} from 'react';
export default class Base extends Component {
static screen;
constructor(props) {
super(props);
Base.screen = this;
}
nav() {
return this.props.navigation;
}
}
In some other component/module I can just call this to navigate to a different screen:
Base.screen.nav().navigate(...);
I have created a navigation aware screen component to take care of it on screens.
For outside the screen you can directly access store.navigation. I have used it with redux in my example. See if this helps you.
https://github.com/aajiwani/react-navigation-aware-helper

Accessing navigator in Actions with React-Native and Redux

Using React-Native (0.19) and Redux, I'm able to navigate from scene to scene in React Components like so:
this.props.navigator.push({
title: "Registrations",
component: RegistrationContainer
});
Additionally I'd like to be able push components to the navigator from anywhere in the app (reducers and/or actions).
Example Flow:
User fills out form and presses Submit
We dispatch the form data to an action
The action sets state that it has started to send data across the wire
The action fetches the data
When complete, action dispatches that the submission has ended
Action navigates to the new data recently created
Problems I'm seeing with my approach:
The navigator is in the props, not the state. In the reducer, I do not have access to the props
I need to pass navigator into any action that needs it.
I feel like I'm missing something slightly simple on how to access Navigator from actions without sending in as a parameter.
In my opinion the best way to handle the navigation with Redux is with react-native-router-flux, because it can delegate all the navigation logic to Redux:
You can change the route from the reducer;
You can connect the router to the store and dispatch its own actions that will inform the store about route changes (BEFORE_ROUTE, AFTER_ROUTE, AFTER_POP, BEFORE_POP, AFTER_DISMISS, BEFORE_DISMISS);
An example
Here is an example on how easily you can save the currently focused route in the store and handle it in a component (that will be aware of being focused):
1. Connect a <Route> to Redux
Connecting a <Route> to Redux is easy, instead of:
<Route name="register" component={RegisterScreen} title="Register" />
you might write:
<Route name="register" component={connect(selectFnForRegister)(RegisterScreen)} title="Register" />
You can also simply connect the component itself in its own file like you usually do.
2. Connect a <Router> to Redux
If you need to inform Redux of the navigation status (i.e. when you pop a route) just override the <Router> component included in react-native-router-flux with a connected one:
import ReactNativeRouter, { Actions, Router } from 'react-native-router-flux'
const Router = connect()(ReactNativeRouter.Router)
Now when you use a <Router> it will be connected to the store and will trigger the following actions:
Actions.BEFORE_ROUTE
Actions.AFTER_ROUTE
Actions.AFTER_POP
Actions.BEFORE_POP
Actions.AFTER_DISMISS
Actions.BEFORE_DISMISS
Take a look at this for an example.
3. Catch the interested actions in your reducer
For this example I have a global reducer (where I keep the information needed by all my app) where I set the currentRoute:
case Actions.AFTER_ROUTE:
case Actions.AFTER_POP:
return state.set('currentRoute', action.name)
Now the reducer will catch every route change and update global.currentRoute with the currently focused route.
You also can do many other interesting things from here, like saving an history of the navigation itself in an array!
4. Update your component on focus
I'm doing it on componentDidUpdate of my component of the route named payment.
If global.currentRoute is payment and the previous global.currentRoute was different, than the component has just been focused.
componentDidUpdate(prevProps) {
const prevRoute = prevProps.global.currentRoute
const currentRoute = this.props.global.currentRoute
if (currentRoute === 'payment' && prevRoute !== currentRoute) {
this.props.actions.doSomething()
}
}
P.S.: Remember to check currentRoute === 'payment', otherwise you'll start doSomething() on every route change!
Also, take a look a the README.md for learning other stuff about the integration with Redux.
Hope it helps, long live Redux!
Maybe you could pass the information about title and component in an action and the component with the navigator can then push the right scene with the information of the state.