React navigation state management without redux - react-native

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

Related

How can I perform a function when the page shows or the app launches in React Native

In React Native, how can I perform a function when the page shows or any component shows, this really confuses me a lot, I can't find the solution in the Documents, and I use functions to declare the components.
Use a class component and and call your function in the componentDidMount lifecycle method
use can also use hook instead of componentDidMount, I personally find code more readable with hooks
import React, { useEffect } from 'react'
const Component = () => {
useEffect(() => {
//do anything here
}, [])
return(
//JSX
)
}

React-navigation "you should only render one navigator warning" conflicts with React context

In my app I have a stacknavigator that renders a tabnavigator.
I want to share data between the tabs and for that I decided to use the Context API React provides.
let Tabs = createMaterialTopTabNavigator(
{
Info: (e: any) => <TabOne />,
Media: (e: any) => <TabTwo />,
}
)
If I export this and hand it to the stacknavigator everything is working fine. But when implementing the Context API with another class:
class ContextWrapper extends React.Component {
render() {
return (
<MyContext.Provider value={data}>
<Tabs />
</MyContext.Provider>
)
}
}
And hand this class to the stacknavigator I get this warning of
"you should only render one navigator "
The app still works this way, but I have this warning in the console.
I am aware of this page: https://reactnavigation.org/docs/en/common-mistakes.html and it's proposed solution of static router but this way it gives me this error:
Cannot read property 'router' of undefined
Any on has a working solution for use with the Context API. Or any other way of sharing data between tabs?

Reactnavigation with parameters

I am using React navigation I am looking for a way to pass parameters into my navigation stack as follows. I have actually one screen which I want to use X amount of times. It only needs an url and a title, and based on the url it should do exactly the same for each url.
So i want to create an object like so:
const urls = {
{title: 'foo', url: 'https://someurl'},
{title: 'bar', url: 'https://someotherurl'}
}
And now in my Navigation component I would like to do something like:
export default createMaterialTopTabNavigator({
SomeKey: {
// Loop here over the urls and create a component and pass props.
}
});
My issue is that I can't find in the documentation how to pass the title and url parameter via the navigator to the specific screens.
Any Suggestions?
I can help you with one part, you can pass variables from Navigator like this
export default createMaterialTopTabNavigator({
ScreenOne: {
screen:props=> <ScreenOne {...props} screenProps={yourpropsr}/>
}
});
The documentation has example for StackNavigator but I hope this will work for TabNavigator too. Documentation link here
I'm doing something similar in my app, but I'm grabbing my array from an API and build my navigation upon that. For each item in my array, I build the same screen setup in a tab navigation and have them all available from a drawer navigator.
You could do something like this:
let NavigatorConfig = {};
urls.forEach(item => {
NavigatorConfig = {
...NavigatorConfig,
[item.title]: {
screen: props => <MyComponent {...props} url={item.url} />
}
};
});
export default createMaterialTopTabNavigator(NavigatorConfig);

React native navigation tab logout function

So I have a react native app and I am using react-navigation tab navigator. My problem is I put my Logout function on the tab navigator too, but each tab requires a screen. Now what I want to happen is when I click Logout it doesn't have to navigate anywhere it just have to clear asyncstorage then redirect to Login screen.
Thanks in advance!
P.S. I am new to react-native.
I am guessing that you have logged in your user and store his info with AsyncStorage (session).
1) What i would do with react-navigation or react-native-router-flux is to load a component that on componentWillMount() function will trigger the logout function and redirect him on the scene you need. See the example below:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { NavigationActions } from 'react-navigation'
import { logoutUser } from '../actions/index' //Action from Redux
class LogoutScene extends Component {
componentWillMount() {
this.props.logoutUser()
NavigationActions.navigate({ routeName: someRouteName })
}
}
const mapStateToProps = (state) => {}
export default connect(mapStateToProps, {logoutUser})(LogoutScene)
2) I highly recommend for you to use Redux on your project because although is hard and makes no sense at the beginning of a project in the long term makes your life so easy. In order to do though you will need something more than the above example. I think you should check this https://medium.com/#jonlebensold/getting-started-with-react-native-redux-2b01408c0053
Let me know if this makes sense

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