How to refresh page when web page is rotated? - javascript

I'm using Next JSs, I'm having trouble finding a solution how to refresh the page when it's rotated
const app () => {
useEffect(()=>{
window.addEventListener("orientationchange", function() {
window.location.reload()
});})
return(<>some code</>);
}

I would strongly recommend you don't do that.
But there are a couple of problems with your implementation of it:
To hook up an event listener on component mount, you need an empty dependency array on your useEffect. Otherwise, your effect callback is called on every render which, in your case, will add multiple handlers.
You aren't removing the handler when the component is unmounted. You should return a cleanup callback from the useEffect callback.
The orientationchange event is deprecated and has poor browser support. Instead, listen to change on Screen.orientation which has better browser support. Alternatively, just listen for resize, since a change in screen orientation that you care about will trigger a resize event, and mobile devices are getting more and more sophisticated with how the screen real estate is used (split screen, etc.). It's possible not all of those would cause a change in orientation, but you seem to care about a change in size.
Separately, app isn't a valid component name. Component functions must start with a capital letter.
Fixing all of those:
const App () => {
useEffect(()=>{
const handler = () => {
// Handle the change in orientation here (I recommend **not** reloading)
};
Screen.orientation.addEventListener("change", handler); // Or window resize
return () => {
// On unmount, remove the handler
Screen.orientation.removeEventListener("change", handler); // Or window resize
};
}, []);
// ^^−−−− tells `useEffect` to only call your callback once, when the component
// is first mounted.
return (<>some code</>);
}

Related

How can I make a request in React when a specific page is navigated away from or closed using React Router v6?

I have a React application using React Router v6 where I would like to call a GraphQL mutation (using Apollo Client) when a user leaves a specific page in the app either by clicking the browser's back button, navigating away from the page using in-app buttons (page unmounts), or closing the tab/browser.
Perhaps this isn't entirely possible in the way I want but also if it isn't possible with Apollo Client but is possible with a more typical request, that's fine.
Here are some of the things I have tried
Calling it from the clean up function in the useEffect hook
I expected this to work whenever the page unmounts but I was naive in understanding how React responds to events like closing a browser or tab as React does not become aware of this. It did work for browser back arrow use and react router navigation though, albeit not super reliably.
visibilitychange event handler
const onChangeHandler = (e) => {
e.preventDefault();
callMutation();
};
useEffect(() => {
window.addEventListener('visibilitychange', onChangeHandler);
return () => {
window.removeEventListener('visibilitychange', onChangeHandler);
};
}, []);
This event listener failed to trigger when navigating away from the page at all. It only triggered on actions like minimizing the browser and switching tabs which I don't want at all and when the page was refreshed.
beforeunload event and useBeforeUnload hook
Next, I tried the above with the beforeunload event but this event would only trigger on page refresh.
I then used the useBeforeUnload hook that React Router 6 added and had the same issue where only a page refresh would trigger the event.
useBeforeUnload(
useCallback(() => {
callMutation();
}, []),
);
So in summary, wondering if there is any way for me to call out to my GraphQL API when a specific page is navigated away from in the cases of using the browser's back arrow, when the page is unmounted, and when the tab is closed. Thanks for any help/info!
Unloading the page is different than unmounting a component because navigation occurred. You may very well need to apply different logic to handle the different ways a user goes "away from a page". When the page is reloaded or the tab/window is closed, any running Javascript is immediately quit. In other words, any unmounting useEffect hook cleanup functions won't run because nothing is running anymore; it's all terminated.
Example using the window.beforeunload event to handle a page reload, tab closing etc and also handling the component simply unmounting.
useEffect(() => {
const unloadCallback = (event) => {
event.preventDefault();
event.returnValue = ""; // * required for browser compatibility
callMutation(); // call external function/effect
return ""; // * required for browser compatibility
};
// listen for page unloading
window.addEventListener("beforeunload", unloadCallback);
// return cleanup function to cancel listener and call function
// is simply unmounting
return () => {
window.removeEventListener("beforeunload", unloadCallback);
callMutation(); // call external function/effect
};
}, []);

Can I get away with not using useEffect in this instance?

I'm still learning React and the proper way to do things. In this React project I am using the TinyMCE editor: https://www.tiny.cloud/docs/tinymce/6/react-cloud/
I want to display a warning message about unsaved changes once the editor has been made "dirty" that is that the user has modified the default content you get on reload.
The component that contains the editor has the following code:
<Editor
apiKey='asdf'
onInit={(_evt, editor) => editorRef.current = editor}
onDirty={(_evt, _editor) => setEditorWasModified(true)}
...
</Editor>
Finally I have just put the code directly into the React component itself where it overwrites the window.onbeforeunload event:
const [editorWasModfied, setEditorWasModified] = useState(false);
const editorRef = useRef<TinyMCEEditor | null>(null);
window.onbeforeunload = () => {
if (editorWasModfied) {
return "You have unsaved changes. Are you sure you want to leave?";
}
};
This code seems to run and work well.
So is my question here is this the correct approach to do this? Or do I need to use an useEffect hook or something similar? If I understand useEffect correctly, it runs on every render which is not something I want. I know there is a second argument, but since it refers to the editorWasModified variable, I get a warning that the dependency array should contain it, which is false since I only want the useEffect once to update the window.onbeforeunload event.
What you have can work, though I'd consider it to be inelegant - every time there's a re-render, you're attaching a new beforeunload listener (and removing the previous one), no matter whether it's necessary or not. You're also not removing the listener when the component dismounts (which would be something to keep in mind if the page this is on has could exist without this component being mounted). After this component is unmounted, it'd be best for the listener with the editorWasModfied check to be removed as well.
If I understand useEffect correctly, it runs on every render which is not something I want.
Well, you're attaching it on every render currently anyway.
I know there is a second argument, but since it refers to the editorWasModified variable, I get a warning that the dependency array should contain it, which is false since I only want the useEffect once to update the window.onbeforeunload event.
The usual way to fix this lint issue is to attach a new listener whenever the state values it references changes. Using useEffect will also let you remove the listener when the component unmounts.
useEffect(() => {
const handler = () => {
if (editorWasModfied) { // perhaps fix typo: editorWasModfied -> editorWasModified
return "You have unsaved changes. Are you sure you want to leave?";
}
};
window.addEventListener('beforeunload', handler);
return () => window.removeEventListener('beforeunload', handler);
}, [editorWasModfied]);
You are correct that useEffect runs on every render (if no dependency array was specified), but so does your code without useEffect.
Here are the three different useEffect "settings":
Runs on every render:
useEffect(() => {
})
Run once:
useEffect(() => {
}, [])
Runs everytime editorWasModfied changes:
useEffect(() => {
}, [editorWasModfied])
Sometimes we only want the useEffect to run once, meaning we don't want any dependencies in our dependency array (second argument), but the linter complains about it.
WARNING Don't use the following as an easy escape from the linter warnings. You should always try to write code following linter rules!
With that said, to suppress this linter warning, you can use // eslint-disable-next-line, but only do this if you are absolutely sure that what you are doing is correct (suppressing the linter is an edge case and is not recommended unless absolutely necessary):
useEffect(() => {
// eslint-disable-next-line
}, [])

window.addEventListener('load',... not firing on Safari?

I need redirect page after it load and get param from URL. I can do by button click.
But I want redirect page automatic (without user input). I am use window.addEventListener('load', () => handleClick()) and it work well on Chrome. But on Safari (desktop and mobile) it not always fire (only sometimes it fire - not always).
I can see this by add alert('Beginning'); in handler - on Chrome this fire automatic after page load, but not on Safari.
How I can solve this?
Thanks!
const handleClick = async (event) => {
alert('Beginning'); //For debugging
const stripe = await stripePromise;
const { error } = await stripe.redirectToCheckout({
param,
});
}
if (typeof window !== `undefined`) {
const param = new URLSearchParams(window.location.search).get('param');
}
const Page = () => {
if (typeof window !== `undefined`) {
window.addEventListener('load', () => handleClick())
}
return (
<section >
<button role="link" onClick={handleClick}> //Only for fallback
Press
</button>
</section>
);
};
export default Page;
The load event probably wouldn't be very reliable, since your component probably wouldn't be mounted until after the DOM is loaded. Instead, you can use the lifecycle methods of React component to respond when your component is mounted to the page. For example:
function Component() {
useEffect(() => alert(1), []);
return (
<h1>hello world!</h1>
);
}
You can find more information about the Effect hook in the documentation. Using it with an empty array makes it work like componentDidMount() for class components.
if you want to fire a function just in loading page and redirect it to another url you can use auto call function like this
(function(){
alert('Beginning');
if (typeof window !== 'undefined') {
const param = new URLSearchParams(window.location.search).get('param');
}
if (param !== 'undefined' && param !== null $$ param!==[]){
window.location.href = 'your_new_url_string?param='+param;
}
})();
I strongly advise that you avoid referencing the DOM directly using listeners such as window.addEventListener or document.addEventListener. In fact, this is why you are using React and not jQuery, to create a virtual DOM instead of charging the real DOM directly and reducing the web performance.
In React, you have React hooks to achieve what you are trying to do. They expose a bunch of lifecycle methods that trigger some events. In your case, useEffect fits your requirements. This hook is similar to componentDidMount() and componentDidUpdate():
By using this Hook, you tell React that your component needs to do
something after render. React will remember the function you passed
(we’ll refer to it as our “effect”), and call it later after
performing the DOM updates. In this effect, we set the document title,
but we could also perform data fetching or call some other imperative
API.
Due to the needs of Strapi payments and Safari mobile (needs to wait for the page load) I've mixed the hook and your approach to ensure that the effect is triggered when the page is loaded.
Applied to your code:
const Page = () => {
useEffect(() => {
window.addEventListener('load', () => handleClick())
}, []);
return (
<section >
<button role="link" onClick={handleClick}> //Only for fallback
Press
</button>
</section>
);
};
export default Page;
Basically, the code will trigger the useEffect function once the DOM tree is loaded and it will execute your handleClick function. The empty array ([]) passed as an argument is called deps; means that you are triggering the effect only once. Of course, it will work in all browsers since it's a React feature.

Is it necessary to `window.removeEventListener('unload')`?

I am storing the lastest input data values to localStorage before the user leaves the webpage. Writing to localStorage is executed on window.onunload.
useEffect(() => {
const updater = () => {
updateStorageValue('last_input', input);
};
window.addEventListener('unload', updater);
return () => window.removeEventListener('unload', updater);
}, [input]);
Let's say the component (where this useEffect was used) was mounted when the user closed/refreshed the tab/window.
I am just curious if removing the unload event has any effect while the whole webpage will stop working.
React doesn't unmount the app components when closing/refreshing the page. So the answer to your question is: No, it doesn't have any effect.
But if it's a normal component living on the page (not mounting just before exit), then the cleanup function should be there to remove the previous unload event listener before adding the next one.
Your useEffect removes the unload event listener and adds a new one when input changes. But if you remove the cleanup function, then you will have as many unload event listeners as input updates.
For example, assume that the input value changes in the following order:
'R'
'Re'
'Rea'
'Reac'
'React'
In this case, these functions will be called on the unload event:
updateStorageValue('last_input', 'R');
updateStorageValue('last_input', 'Re');
updateStorageValue('last_input', 'Rea');
updateStorageValue('last_input', 'Reac');
updateStorageValue('last_input', 'React');

Throttle function (lodash inspired) to handle resize event using React hooks only (with Sandbox)

Sorry for the long question, but I needed to make an introduction in order to make it clearer.
I was in need of some code to toggle between my Headers components <HeaderDesktop> and <MobileHeader>.
At first I was using CSS media queries to toggle between them using display: block | none;. This is less then ideal, since both components will be rendered at the same time, which is inneficient and could bring problems in advertisement displays on hidden elements.
Someone here on SO suggested that I could use window.innerWidth and use React to determine which component to render based on that. That is much better indeed. Now only 1 component gets rendered at a time. This is what I did:
// INSIDE HEADER COMPONENT
return(
<HeaderWrapper>
{window.innerWidth < 1200 ?
<HeaderMobile/>
: <HeaderDesktop/>
}
</HeaderWrapper>
);
But I needed a way to handle resize events. So I did:
// INSIDE HEADER COMPONENT
const [windowSize, setWindowSize] = useState(window.innerWidth);
function handleResize() {
setWindowSize(window.innerWidth);
}
return(
<HeaderWrapper>
{windowSize < 1200 ?
<HeaderMobile/>
: <HeaderDesktop/>
}
</HeaderWrapper>
);
Nice! That works, but now my component renders 1 trillion times a second every time a resize is happening. That's no good for performance.
So I've done my research and found out about lodash throttle and debounce methods. Both can reduce and control the number of events handled, even when hundreds are fired subsequentely.
https://css-tricks.com/debouncing-throttling-explained-examples/
But I'm not a fan of bringing entire libraries to my dependency list just to use a simple functionality like that, so I've ended up creating the following effect hook to mimic the throttle functionality on my resize event handler.
// INSIDE HEADER COMPONENT
// Ref to store if there's a resize in progress
const resizeInProgress = useRef(false);
// State to store window size
const [windowSize, setWindowSize] = useState(window.innerWidth);
useEffect(() => {
// This function trigger the resize event handler
// And updates the ref saying that there's a resize in progress
// If theres a resize in progress, it doesn't do anything
function handleResize() {
if (resizeInProgress.current === true) {
return;
}
resizeInProgress.current = true;
throttled_updateWindowSize();
}
// This function sets a timeout to update the state with the
// new window size and when it executes, it resets the
// resizeInProgress ref to false. You can execute what's the interval
// You want to handle your resize events, in this case is 1000ms
function throttled_updateWindowSize() {
setTimeout(() => {
console.log("I will be updated!");
console.log(window.innerWidth);
setWindowSize(window.innerWidth);
resizeInProgress.current = false;
}, 1000);
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
You can see this in action in the following Sandbox:
https://codesandbox.io/s/v3o0nmvvl0
QUESTION 1
Can you give me any suggestions on how to improve my code for the throttled version of the resize event handler?
QUESTION 2
I'm guessing I'll be needing that functionality in other components. How can I make this easily reusable? Can I make this a custom Hook? I have never created one, so I'm still having some trouble on how to reason about them and what is the proper way to create them. Can you help me to put that into a Custom Hook?
Or would it better to create a Higher Order Component for that?
This isn't something i'd do with a hook. You can get it to work as a hook, but you're limiting yourself to only doing throttling inside components, when throttling is a more useful utility function than that, and hooks don't make it any easier or let you do anything extra.
If you don't want to import all of lodash, that's understandable, but you could implement something similar yourself. Lodash's throttle is a higher order function: you pass it in a function, and it returns you a new function that will only execute if the appropriate amount of time has passed since the last execution. The code to do that (without quite as many options and safety checks as lodash does) can be replicated like this:
const throttle = (func, delay) => {
let inProgress = false;
return (...args) => {
if (inProgress) {
return;
}
inProgress = true;
setTimeout(() => {
func(...args); // Consider moving this line before the set timeout if you want the very first one to be immediate
inProgress = false;
}, delay);
}
}
To be used like this:
useEffect(() => {
const handleResize = throttle(() => {
setWindowSize(window.innerWidth);
}, 1000);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);

Categories

Resources