React Native Navigation: Navigate to a screen from outside a React component - react-native

https://wix.github.io/react-native-navigation/docs/basic-navigation#navigating-in-a-stack
indicates that pushing a new screen to the stack requires the current screen's componentId. My use case is to allow navigating based on certain events emitted by a native module. As such, componentId won't be available to me, since the event listener would reside outside any React component screen. Is there any way to navigate to a screen from outside in RNN? Or even get the current componentId.

I ended up adding a command event listener to store the current componentId in closure.
let currentComponentId;
Navigation.events().registerCommandListener((name, params) => {
if (name === 'push') {
currentComponentId = params.componentId;
}
});
Navigation.events().registerAppLaunchedListener(() => {
Navigation.setRoot(rootRouteConfig);
EventEmitter.addListener('navigate', (name) => {
Navigation.push(currentComponentId, {
component: {
name,
},
});
});
});

Related

Remove listener AppState change react native

Good evening everyone, I am facing a problem.
I am developing an app in react-native and I need that, every time a user sends the app in the background or in an inactive state, when he returns to the app I force him to go to a certain screen (Loading) where I perform certain checks (such as if he is a blocked user, deleted, etc ...).
I have now written the following function
const [appState, setAppState] = useState(AppState.currentState);
useEffect(() => {
getAttivita();
getBanner();
const appStateListener = AppState.addEventListener(
"change",
(nextAppState) => {
setAppState(nextAppState);
if (nextAppState === "active") {
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: Routes.Loading }],
})
);
}
}
);
return () => {
appStateListener?.remove();
};
}, []);
I put this listener in the Screen Diary (which represents my home).
Now if from the screen Diary, I minimize the app, then I have no problems and everything works as it should.
However if I go to another screen and minimize the app, then I get the following error
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in Diary (at SceneView.tsx:122)
Then when I log back into the app I realize that the listener for the app status is still active (so it is as if the remove () had not worked) and in fact I am pushed back into my loading screen.
So I'm wondering, is it the listener that isn't actually being removed?
Or am I doing something wrong?
Thanks in advance to who will answer me .

Why datas not update after navigate on react native?

I have a basic tab navigation HOME-UPLOAD-PROFILE. 'HOME' screen has values which fetch with an api from a different web site. After user log in navigation system direct user to this home page and "fetch" working well. But when i upload something using 'UPLOAD' screen and then return 'HOME' screen datas not updating. Whereas 'componentDidMount' should works everytime and bring datas every clicking 'HOME' screen I could not find any solution or answere
export default class Home extends Component {
state = {
data: []
};
async componentDidMount() {
//Burada kullanıcı yoksa ülkeleri getireceğiz
const rest = await fetch(
'https://www.birdpx.com/mobile/fotografgetir/' + this.state.page,
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-type': 'application/json'
},
body: JSON.stringify(datam)
}
);
const gelenVeri = await rest.text();
let convertedVeri = JSON.parse(gelenVeri);
this.setState({
data: convertedVeri
});
}
}
You can check by logging if componentDidMount is triggered.
componentDidMount won't be triggered when you navigate between tabs in tabNavigator, except for the first time when tabNavigator is mounted.
You can choose one of the following 2 ways to implement what you need.
1. Use global redux state instead of component state. (Recommended)
You can implement redux to your project (if you aren't using). Use redux state in Home component instead of current component state.
On Upload screen, once uploading is done you can change the redux store by dispatching an action. This will instantly reflect on your Home component.
2. Fetch data on component focus event
If you want to catch the focus event for a specific tab component, you can use didFocus event listener of navigation lifecycle.
const didFocusSubscription = this.props.navigation.addListener(
'didFocus',
payload => {
console.debug('didFocus', payload);
}
);
// Remove the listener when you are done
didFocusSubscription.remove();
If you are using react-navigation, components do not unmount when you navigate from one screen to the next on a stack navigator.
You can listen to navigation lifecycle events in a component (https://reactnavigation.org/docs/4.x/navigation-prop/#addlistener---subscribe-to-updates-to-navigation-lifecycle)
or if you are using withNavigationFocus HOC for passing navigation prop to a component you can use the isFocused prop to know about the recent focus on you component.

Not getting updated redux-persist state inside React-navigation useFocusEffect hook

I am using react-navigation(5.6.1) with redux-persist(6.0.0) on my react-native project. One of my reducer is persisted(profile). I added a focus event listener to run some functions when my profile screen in focus.
In those functions, the profile state will be used in some logic. On some other parts of the component, there will be other function that will be dispatching action which will update the profile state.
The issue now is whenever I update the profile state, each time the screen is in focus, I will always get the previous persisted data in the event listener. However, the component always render the latest persisted state. Anybody have any idea why this is happening? Below is the snippet of the event lister.
useFocusEffect(
React.useCallback(() => {
console.log(profile);
dispatch(showErrorDialog());
return () => {};
}
}, []),
If you do not give dependencies then useCallback will call the memorised function, not the updated one.
useFocusEffect(
React.useCallback(() => {
console.log(profile);
dispatch(showErrorDialog());
return () => {};
}
}, [profile]))

React Navigation - Check on which screen the user is

I am using react-navigation in my project and I need to detect if the user is on the Dashboard/Graph/Posts page.
For example, if I am on Posts page, I need a param to write a conditional.
e.g. Make a request if I am only at Posts page
Is it possible to check on which screen the user is?
You can get it through navigation object, try it
this.props.navigation.state.routeName
You can try the following,
on componentDidMount
componentDidMount() {
this.subs = this.props.navigation.addListener("didFocus", payload => {
console.log("PAY_LOAD...", payload);
// Check the payload and your logic(you can get the current screen name like => payload.state.routeName)
});
}
on componentWillUnmount
componentWillUnmount() {
this.subs.remove();
}
Note: Don't forgot to remove listener on 'componentWillUnmount()'

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