I want to create a method or a class in React Native that preform a background operation while opening a progress dialog then hides the progress dialog when the background operation is done.
Logically this would be done using a call back :
export const doWithProgressDialog = async (callback) =>
{
// show progress dialog
await callback();
// hide progress dialog
}
However as you can see , what I'm encountering is that i have to return a progress dialog component component before the callback is called so that the progress dialog would be rendered.
Even if i make a component class that renders a progress dialog instead of a function , i will have the same issue in render method inside component class.
Related
The context is a simple React Native app with React Navigation.
There are 3 screens.
The first simply displays a button to go to second screen using navigation.navigate("SecondScreen").
The Second contains a hook (see code below) that adds a listener to listen the mouse position. This hook adds the listener in a useEffect hook and removes the listener in the useEffect cleanup function. I just added a console.log in the listener function to see when the function is triggered.
This screen contains also a button to navigate to the Third screen, that only shows a text.
If I go from first screen to second screen: listener in hook start running. Good.
If I go back to the first screen using default react navigation 's back button in header. the listener stops. Good.
If I go again to second screen, then listener runs again. Good.
But if I now go from second screen to third screen, the listener is still running. Not Good.
How can I unmount the hook when going to third screen, and mount it again when going back to second screen?
Please read the following before answering :
I know that:
this is due to the fact that react navigation kills second screen when we go back to first screen, and then trigger the cleanup function returned by the useEffect in the hook. And that it doesn't kill second screen when we navigate to third screen, and then doesn't trigger the cleanup function.
the react navigation's hook useFocusEffect could be used to resolve this kind of problem. But it can't be used here because it will involve to replace the useEffect in the hook by the useFocusEffect. And I want my hook to be usable in every context, even if react navigation is not installed. More, I'm using here a custom hook for explanation, but it's the same problem for any hook (for example, the native useWindowDimensions).
Then does anyone know how I could manage this case to avoid to have the listener running on third screen ?
This is the code of the hook sample, that I take from https://github.com/rehooks/window-mouse-position/blob/master/index.js, but any hook could be used.
"use strict";
let { useState, useEffect } = require("react");
function useWindowMousePosition() {
let [WindowMousePosition, setWindowMousePosition] = useState({
x: null,
y: null
});
function handleMouseMove(e) {
console.log("handleMouseMove");
setWindowMousePosition({
x: e.pageX,
y: e.pageY
});
}
useEffect(() => {
window.addEventListener("mousemove", handleMouseMove);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
};
}, []);
return WindowMousePosition;
}
module.exports = useWindowMousePosition;
the react navigation's hook useFocusEffect could be used to resolve this kind of problem. But it can't be used here because it will involve to replace the useEffect in the hook by the useFocusEffect. And I want my hook to be usable in every context, even if react navigation is not installed
So your hook somehow needs to know about the navigation state. If you can't use useFocusEffect, you'll need to pass the information about whether the screen is focused or not (e.g. with an enabled prop).
function useWindowMousePosition({ enabled = true } = {}) {
let [WindowMousePosition, setWindowMousePosition] = useState({
x: null,
y: null
});
useEffect(() => {
if (!enabled) {
return;
}
function handleMouseMove(e) {
console.log("handleMouseMove");
setWindowMousePosition({
x: e.pageX,
y: e.pageY
});
}
window.addEventListener("mousemove", handleMouseMove);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
};
}, [enabled]);
return WindowMousePosition;
}
And then pass enabled based on screen focus:
const isFocused = useIsFocused();
const windowMousePosition = useWindowMousePosition({ enabled: isFocused });
Note that this approach will need the screen to re-render when it's blurred/focused unlike useFocusEffect.
My RN 0.62.2 app needs to automatically save page data just before the function component unmounts. The idea is that when the user close the page (detecting losing focus may not work here since user may zoom in image in modal screen), then the save (to backend server) is automatically triggered. Since it is function component, how to know when the component will unmount?
Here is the sample code of a function component shall do:
const MyCom = () => {
//do something here. ex, open gallery to upload image, zoon in image in `modal screen, enter input`
if (component will unmount) {
//save the data by sending them to backend server
}
}
The useEffect triggers with every rendering and will have performance issue if keep saving to backend server with each and every rendering. The auto save only happens once just before the component unmount. User may click Back or Home button to leave the page.
Yoı must use useEffect for componentWillUnmount in functional components.
const MyCom = () => {
//do something here. ex, open gallery to upload image, zoon in image in
useEffect(() => {
// Component Did Mount
return => {
// ComponentWillUnmount
}
},[])
return(/*Component*/)
}
I am using Backhander in react native with react-native-router-flux but it's reacting on all screens where I want to make it work for screen-specific, but when I am trying to get the current route name in the onBackPress method, it's giving me first screen name in router name.
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.onBackPress);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.onBackPress);
}
onBackPress = () => {
alert(this.props.navigation.state.routeName)
}
First of all - BackHandlers in React Native are global and not screen specific. But you can achieve your wanted behavior.
Some background
With BackHandler.addEventListener you push an event listener on a Stack of event listeners, with BackHandler.removeEventListener you remove the given listener from the Stack. When the BackButton is pressed, the top listener from the stack is called and the code is executed. Then the next listener is called and so on. This stops when the first listener returns true.
For your specific problem
You should ensure that you add an event listener on the page you want it to (like you are doing in your code example)
You should ensure that your event listener returns true
You should ensure that your listener gets removed when unmounting the view (like you do)
Now you BackHandler should work for the view you have implemented it in (lets call it view1). But you have to think about all the other views. Especially when you are pushing views on top of view1. Ether you can implement an "onFocus" and "onBlur" method for view1 and use this methods instead of componentDidMount and componentWillUnmount for adding and removing event listeners, or you have to add event listeners for the back handler for all views that are pushed on top of view1.
Hope that helps :-)
Source: https://facebook.github.io/react-native/docs/backhandler
If you want backHandler to act differently for specific screen then you can use Actions.currentScene in your onBackPress function :
onBackPress = () => {
if(Actions.currentScene === 'SceneKey'){
return true;
}
Actions.pop();
return true;
}
I have two screens: A and B, connected with a StackNavigator
Screen A is a QR code scanner. As soon as a QR code is scanned, it navigates to screen B.
In screen B, I make an API call using the QR code that gets passed as a navigation param from screen A. I trigger this API call in componentDidMount.
My issue is: if I navigate from A to B, then back to A, then to B again, componentDidMount does not get called and I have no way to trigger the API call.
EDIT:
Here's some code
Screen A
Handler function that gets called when a QR code is scanned:
handleQRCode = qrCode => {
NavigationService.navigate('Decode', {qrCode});
};
Screen B
The QR code is pulled from the navigation state params and used for an API call (startDecode) through redux.
componentDidMount() {
qrCode = this.props.navigation.state.params.qrCode;
this.props.startDecode(qrCode.data);
}
My issue is that componentDidMount only gets called the first time that route is taken.
In react-navigation each screen is kept mounted. This means that when you you go back to B, you might have changed the props, but componentDidMount was already invoked in the first creation of this screen.
There are two options available for you (AFAIK) that can handle this case:
Instead of calling this.props.navigation.navigate() you can use
this.props.navigation.push which will create another instance of
screen B, thus invoking the componentDidMount React lifecycle
event.
In screen B you can catch the event where its props have changed.
This can take place in the new static lifecycle event
getDerivedPropsFromState or it can be done in the soon to be
deprecated componentWillReceiveProps.
I was facing a similar issue and I used this.props.navigation.addListener() to resolve it. Basically, force-calling componentDidMount() may be possible by pushing same screen again using a key (I haven't tried it) but your stack will keep growing as well, which is not optimal. So, when you return to a screen already in stack, you can use addListener() to see if it is being re-focused, and you can replicate you componentDidMount() code here:
class MyClass extends Component {
someProcess = () => {
// Code common between componentDidMount() and willFocus()
}
componentDidMount() {
this.someProcess();
}
willFocus = this.props.navigation.addListener(
'willFocus',
(payload) => {
this.someProcess();
}
);
}
When MyClass is called for the first time, componentDidMount will get called. For the other times when it is still in stack but instead just gains focus, addListener will get called.
This happens because the B component is mounted only on the first time it is accessed, so componentDidMount won't be called again.
I recommend you to pass a callback to the setOnNavigatorEvent method of your navigator, with the 'didAppear' event. Your callback will be invoked on every event emitted by react-native-navigation, and you can verify to do your logic every time the screen appears (hence the use of 'didAppear' event). You can base your code on the following:
export default class ExampleScreen extends Component {
constructor(props) {
super(props);
this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
}
onNavigatorEvent(event) {
if (event.id === 'didAppear') {
// do API call here
}
}
}
I am using https://github.com/aksonov/react-native-router-flux for navigation in react native.How can I send callback to my parent view when I call pop actions on it.
onPressed() {
Actions.pop();
}
This is how I call pop action on it but I need to send updated value to previous view.
will I finally solve it by calling empty refresh with props after delay
Actions.popTo('pageOne');
setTimeout(() => {
Actions.refresh({name:'zzzzar'});
console.log("zzzz");
}, 10);