Scrolling a page in vue-router only after loading the components - vue.js

According vue-router documentation (https://router.vuejs.org/guide/advanced/scroll-behavior.html#async-scrolling) you can do "async scrolling". I have a single page app where the height of the page varies page by page. When navigating back, I would like to scroll the page to the same position where the user clicked the link.
Now, immediately when navigating back the component for the page has not been loaded fully and the overall height of the page is not long enough. The scroll to savedPosition will only go to the bottom of the page before the component loads.
I can do this in nuxt.config.js
router: {
scrollBehavior (to, from, savedPosition) {
if (savedPosition)
{
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(savedPosition)
}, 300)
})
}
That will wait 300ms, around the time the page height is resolved, and only then scroll down. But this is not optimal. What if the loading of the component takes longer?
How would you implement vue-router scrolling only after the component on the page has loaded?
Can I listen to events in the page in the vue-router scrollBehavior? Could I somehow wait for the Vue lifecycle hooks to trigger the scroll? Can I get a trigger about the page height changing?

With the ResizeObserver API you can add an event listener on element height changes:
// create an Observer instance to listen to height changes on an object
const resizeObserver = new ResizeObserver(entries => {
// Check if the height is enough to scroll to that point
if(entries[0].target.clientHeight >= savedPosition.top + screen.height) {
// Then resolve to trigger the scroll and disconnect the observer
resolve(savedPosition);
resizeObserver.disconnect();
}
});
// start observing a DOM node
resizeObserver.observe(document.body);
Here's a complete example:
scrollBehavior(to, from, savedPosition) {
return new Promise((resolve, reject) => {
if(savedPosition) {
// create an Observer instance
const resizeObserver = new ResizeObserver(entries => {
if(entries[0].target.clientHeight >= savedPosition.top + screen.height) {
resolve(savedPosition);
resizeObserver.disconnect();
}
});
// start observing a DOM node
resizeObserver.observe(document.body);
} else {
resolve({ top: 0 });
}
});
}

App.vue
watch: {
$route () {
setTimeout(() => {
window.scrollTo(0, 0)
}, 0)
}
}

Related

Prevent going back in Drawer Navigator

I want to prevent user from leaving screen before saving details by clicking on any item in drawer menu. I used this below snippet to prevent navigation. But issue is I've screens which are hidden in drawer menu so when I try to add drawerItemPress actions in those screens, it is not getting triggered.
useEffect(() => {
const unsubscribe = props.navigation.addListener('drawerItemPress', (e) => {
// Prevent default behaviour
console.log("props.saveTrigger")
if(rootStore.getState().transactionReducer.saveTrigger) {
console.log("stop navigation");
e.preventDefault();
}
else {
console.log("go ahead with navigation")
}
// Do something manually
// ...
});
return unsubscribe;
}, [props.navigation]);

How do I update react-native component, when click on tab on bottom Tab Navigator

I am using bottom tab navigator in React-native for Navigation. When I switches tab, component are not updating.
Pls let me know how can I update/refresh whole component when I tap on tab at bottom Tab Navigator
Here is a simple solution.
import { useFocusEffect } from '#react-navigation/native';
useFocusEffect(
React.useCallback(() => {
console.log("Function Call on TAb change")
}, [])
);
Here is the link you can read more. https://reactnavigation.org/docs/function-after-focusing-screen/
You can use Navigation listener check Navigation Events, when screen gets focused it will trigger a function like this:
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
//Your refresh code gets here
});
return () => {
unsubscribe();
};
}, [navigation]);
And class component like this:
componentDidMount() {
this._unsubscribe = navigation.addListener('focus', () => {
//Your refresh code gets here
});
}
componentWillUnmount() {
this._unsubscribe();
}
If you want to force update check this question

RN OneSignal _open Event

OneSignal on notification open event fires after the home screen got launched then it navigates to the desired screen. I want to detect if the app was launched on pressing the notification prior the home screen get rendered so I can navigate to the Second screen directly and avoid unnecessarily calling of apis.
"react-native-onesignal": "^3.9.3"
"react-navigation": "^4.0.0"
code
const _opened = openResult => {
const { additionalData, body } = openResult.notification.payload;
// how to navigate or set the initial screen depending on the payload
}
useEffect(() => {
onesignal.init();
onesignal.addEventListener('received', _received);
onesignal.addEventListener('opened', _opened);
SplashScreen.hide();
return () => {
// unsubscriber
onesignal.removeEventListener('received', _received);
onesignal.removeEventListener('opened', _opened);
}
}, []);
Debug
your question is how to navigate or set the initial screen depending on the opened notification payload?
1) - set the initial screen depending on the opened notification payload.
according to class Lifecycle useEffect runs after the component output has been rendered, so listener in useEffect not listen until the component amounting, and this the reason of logs in home screen shown before logs in useEffect, see this explanation.
//this the problem (NavigationContainer called before useEffect).
function App() {
useEffect(() => {}); //called second.
return <NavigationContainer>; //called first.
}
//this the solution (useEffect called Before NavigationContainer).
function App() {
const [ready, setReady] = useState(false);
//called second.
useEffect(() => {
//listen here
setReady(true);
SplashScreen.hide();
});
//called first
//no function or apis run before useEffect here it just view.
if(!ready) return <></>;// or <LoadingView/>
//called third.
return <NavigationContainer>;
}
your code may be like this.
function App() {
const [ready, setReady] = useState(false);
const openedNotificationRef = useRef(null);
const _opened = openResult => {
openedNotificationRef.current = openResult.notification.payload;
}
const getInitialRouteName = () => {
if (openedNotificationRef.current) {
return "second"; //or what you want depending on the notification.
}
return "home";
}
useEffect(() => {
onesignal.addEventListener('opened', _opened);
//setTimeout(fn, 0) mean function cannot run until the stack on the main thread is empty.
//this ensure _opened is executed if app is opened from notification
setTimeout(() => {
setReady(true);
}, 0)
});
if(!ready) return <LoadingView/>
return (
<NavigationContainer initialRouteName={getInitialRouteName()}>
</NavigationContainer>
);
}
2) - navigate depending on the opened notification payload.
first you need to kown that
A navigator needs to be rendered to be able to handle actions If you
try to navigate without rendering a navigator or before the navigator
finishes mounting, it will throw and crash your app if not handled. So
you'll need to add an additional check to decide what to do until your
app mounts.
read docs
function App() {
const navigationRef = React.useRef(null);
const openedNotificationRef = useRef(null);
const _opened = openResult => {
openedNotificationRef.current = openResult.notification.payload;
//remove loading screen and start with what you want.
const routes = [
{name : 'home'}, //recommended add this to handle navigation go back
{name : 'orders'}, //recommended add this to handle navigation go back
{name : 'order', params : {id : payload.id}},
]
navigationRef.current.dispatch(
CommonActions.reset({
routes : routes,
index: routes.length - 1,
})
)
}
useEffect(() => {
//don't subscribe to `opened` here
//unsubscribe
return () => {
onesignal.removeEventListener('opened', _opened);
}
}, []);
//subscribe to `opened` after navigation is ready to can use navigate
const onReady = () => {
onesignal.addEventListener('opened', _opened);
//setTimeout(fn, 0) mean function cannot run until the stack on the main thread is empty.
//this ensure _opened is executed if app is opened from notification
setTimeout(() => {
if (!openedNotificationRef.current) {
//remove loading screen and start with home
navigationRef.current.dispatch(
CommonActions.reset({
routes : [{name : 'home'}],
index: 0,
})
)
}
}, 0)
};
return (
<NavigationContainer
ref={navigationRef}
onReady={onReady}
initialRouteName={"justLoadingScreen"}>
</NavigationContainer>
);
}
refrences for setTimeout, CommonActions.

React Native screen should reload depending on the previous screen

I'm developing a social network, and I have the Home screen, where a feed is loaded and a Post screen.
How to trigger reloading only when a post is created?
Both screens are inside a Bottom Navigator.
if you wanna you components to be reloaded with states then you can use redux or react context.
but if you are not using those then you cannot reload but you can call function when tab is changed or pressed refer this https://reactnavigation.org/docs/bottom-tab-navigator/#events
and call your functions inside this to reload the data.
React.useEffect(() => {
const unsubscribe = navigation.addListener('tabPress', (e) => {
// Prevent default behavior
e.preventDefault();
// Do something manually
// ...
});
return unsubscribe;
}, [navigation]);
or even you can use focus method
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// do something
});
return unsubscribe;
}, [navigation]);

viewWillAppear in React Native

I'm trying to stream video from the camera in a View with React Native. The problem is, if the user presses the home button, temporarily leave the app and switch back, the stream URL will be changed.
So I need to update the URL in a function similar to viewWillAppear as in native iOS framework. Any suggestion on this?
Yes #Danny Want is right you can use AppState. It can be used like this:
getInitialState: function() {
return {
currentAppState: AppState.currentState,
};
},
componentDidMount: function() {
AppState.addEventListener('change', this._handleAppStateChange);
},
componentWillUnmount: function() {
AppState.removeEventListener('change', this._handleAppStateChange);
},
_handleAppStateChange: function(currentAppState) {
this.setState({ currentAppState, });
},
render: function() {
return (
<Text>Current state is: {this.state.currentAppState}</Text>
);
},
Do what you wanna do in the AppState handler: _handleAppStateChange. For more details please check out here