How to detect a query param appended to URL using pure React - javascript

I have an injected external widget in my React application that have some links and when clicking in these links a query param is appended to the URL.
Imagine something like this:
base url: http://localhost:8080/
url after adding the query param: http://localhost:8080/?shouldOpenModal
Is there any way to detect this appended query param to the URL using React? It's important in here that the page isn't fully reloaded, since there's information that cannot be lost and we also can't use external libraries such as React Router DOM (which is widely used in similar questions in here).
So far, to append the URL without triggering a reload is using this:
if (history.pushState) {
const newUrl = window.location.protocol + "//" +
window.location.host +
window.location.pathname + '?stOpenBenefitsModal';
window.history.pushState({path:newurl},'',newurl);
}

You also need to update some app state when update url to re-render your component tree:
....
[state, setState] = useState(window.location)
const navigate = (url)=>{
window.history.push('','',url);
setState(window.location)
}
const handlePopState = ()=>{
setState(window.location)
}
useEffect(()=>{
// sync back and forward browser button with state
window.addEvenListener('popstate',handlePopState)
},[])
....
//render something based on state value

Related

Force hard reload in Nextjs [duplicate]

How can I force the web browser to do a hard refresh of the page via JavaScript?
Hard refresh means getting a fresh copy of the page AND refresh all the external resources (images, JavaScript, CSS, etc.).
⚠️ This solution won't work on all browsers. MDN page for location.reload():
Note: Firefox supports a non-standard forceGet boolean parameter for location.reload(), to tell Firefox to bypass its cache and force-reload the current document. However, in all other browsers, any parameter you specify in a location.reload() call will be ignored and have no effect of any kind.
Try:
location.reload(true);
When this method receives a true value as argument, it will cause the page to always be reloaded from the server. If it is false or not specified, the browser may reload the page from its cache.
More info:
The location object
window.location.href = window.location.href
Accepted answer above no longer does anything except just a normal reloading on mostly new version of web browsers today. I've tried on my recently updated Chrome all those, including location.reload(true), location.href = location.href, and <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />. None of them worked.
My solution is by using server-side capability to append non-repeating query string to all included source files reference as like below example.
<script src="script.js?t=<?=time();?>"></script>
So you also need to control it dynamically when to keep previous file and when to update it. The only issue is when files inclusion is performed via script by plugins you have no control to modify it. Don't worry about source files flooding. When older file is unlinked it will be automatically garbage collected.
Changing the current URL with a search parameter will cause browsers to pass that same parameter to the server, which in other words, forces a refresh.
(No guarantees if you use intercept with a Service Worker though.)
const url = new URL(window.location.href);
url.searchParams.set('reloadTime', Date.now().toString());
window.location.href = url.toString();
If you want support older browsers:
if ('URL' in window) {
const url = new URL(window.location.href);
url.searchParams.set('reloadTime', Date.now().toString());
window.location.href = url.toString();
} else {
window.location.href = window.location.origin
+ window.location.pathname
+ window.location.search
+ (window.location.search ? '&' : '?')
+ 'reloadTime='
+ Date.now().toString()
+ window.location.hash;
}
That said, forcing all your CSS and JS to refresh is a bit more laborious. You would want to do the same process of adding a searchParam for all the src attributes in <script> and href in <link>. That said it won't unload the current JS, but would work fine for CSS.
document.querySelectorAll('link').forEach((link) => link.href = addTimestamp(link.href));
I won't bother with a JS sample since it'll likely just cause problems.
You can save this hassle by adding a timestamp as a search param in your JS and CSS links when compiling the HTML.
This is a 2022 update with 2 methods, considering SPA's with # in url:
METHOD 1:
As mentioned in other answers one solution would be to put a random parameter to query string. In javascript it could be achieved with this:
function urlWithRndQueryParam(url, paramName) {
const ulrArr = url.split('#');
const urlQry = ulrArr[0].split('?');
const usp = new URLSearchParams(urlQry[1] || '');
usp.set(paramName || '_z', `${Date.now()}`);
urlQry[1] = usp.toString();
ulrArr[0] = urlQry.join('?');
return ulrArr.join('#');
}
function handleHardReload(url) {
window.location.href = urlWithRndQueryParam(url);
// This is to ensure reload with url's having '#'
window.location.reload();
}
handleHardReload(window.location.href);
The bad part is that it changes the current url and sometimes, in clean url's, it could seem little bit ugly for users.
METHOD 2:
Taking the idea from https://splunktool.com/force-a-reload-of-page-in-chrome-using-javascript-no-cache, the process could be to get the url without cache first and then reload the page:
async function handleHardReload(url) {
await fetch(url, {
headers: {
Pragma: 'no-cache',
Expires: '-1',
'Cache-Control': 'no-cache',
},
});
window.location.href = url;
// This is to ensure reload with url's having '#'
window.location.reload();
}
handleHardReload(window.location.href);
Could be even combined with method 1, but I think that with headers should be enought:
async function handleHardReload(url) {
const newUrl = urlWithRndQueryParam(url);
await fetch(newUrl, {
headers: {
Pragma: 'no-cache',
Expires: '-1',
'Cache-Control': 'no-cache',
},
});
window.location.href = url;
// This is to ensure reload with url's having '#'
window.location.reload();
}
handleHardReload(window.location.href);
UPDATED to refresh all the external resources (images, JavaScript, CSS, etc.)
Put this in file named HardRefresh.js:
function hardRefresh() {
const t = parseInt(Date.now() / 10000); //10s tics
const x = localStorage.getItem("t");
localStorage.setItem("t", t);
if (x != t) location.reload(true) //force page refresh from server
else { //refreshed from server within 10s
const a = document.querySelectorAll("a, link, script, img")
var n = a.length
while(n--) {
var tag = a[n]
var url = new URL(tag.href || tag.src);
url.searchParams.set('r', t.toString());
tag.href = url.toString(); //a, link, ...
tag.src = tag.href; //rerun script, refresh img
}
}
}
window.addEventListener("DOMContentLoaded", hardRefresh);
window.addEventListener("deviceorientation", hardRefresh, true);
This code do a fully controled forced hard refresh for every visitor, so that any update will show up without a cashing problem.
Duplicated DOM rendering is not a performance issue, because the first render is from cache and it stops rendering in <script src="js/HardRefresh.js"> where it reload a page from server. When it run a refreshed page it also refresh urls in page.
The last refresh time x is stored in localStorage. It is compared with the current time t to refresh within 10 seconds. Assuming a load from server not take more than 10 sec we manage to stop a page refresh loop, so do not have it less than 10s.
For a visitor of page the x != t is true since long time ago or first visit; that will get page from server. Then diff is less than 10s and x == t, that will make the else part add query strings to href and src having sources to refresh.
The refresh() function can be called by a button or other conditioned ways. Full control is managed by refining exclusion and inclusion of urls in your code.
For angular users and as found here, you can do the following:
<form [action]="myAppURL" method="POST" #refreshForm></form>
import { Component, OnInit, ViewChild } from '#angular/core';
#Component({
// ...
})
export class FooComponent {
#ViewChild('refreshForm', { static: false }) refreshForm;
forceReload() {
this.refreshForm.nativeElement.submit();
}
}
The reason why it worked was explained on this website: https://www.xspdf.com/resolution/52192666.html
You'll also find how the hard reload works for every framework and more in this article
explanation: Angular
Location: reload(), The Location.reload() method reloads the current URL, like the Refresh button. Using only location.reload(); is not a solution if you want to perform a force-reload (as done with e.g. Ctrl + F5) in order to reload all resources from the server and not from the browser cache. The solution to this issue is, to execute a POST request to the current location as this always makes the browser to reload everything.
The most reliable way I've found is to use a chache buster by adding a value to the querystring.
Here's a generic routine that I use:
function reloadUrl() {
// cache busting: Reliable but modifies URL
var queryParams = new URLSearchParams(window.location.search);
queryParams.set("lr", new Date().getTime());
var query = queryParams.toString();
window.location.search = query; // navigates
}
Calling this will produce something like this:
https://somesite.com/page?lr=1665958485293
after a reload.
This works to force reload every time, but the caveat is that the URL changes. In most applications this won't matter, but if the server relies on specific parameters this can cause potential side effects.

Why window.history.pushState updates my content but not the URL

I want to be able to switch the language on my client-only React app without page reload. I was able to achieve that, but my url is not changing as it was before.
I have 2 languages - French - /fr, English - /en
This is the URL language method I tried with window.history.pushState
function generateUrlSwitch(lang) {
const location = window.location.pathname;
if (location.length > 3) {
const actualPath = location.substr(4, location.length);
const url = `/${lang}/${actualPath}`;
return window.history.pushState(null, null, url);
} else {
return '/' + lang;
}
}
Current Behaviour:
Content update to chosen language without reload - url stays the same, but I can see the parameter is switching in milliseconds between en/fr and also when I console.log both options are in console.
Expected Behaviour:
Content updated based on chosen language without page reload + url update based on chosen language
Language Switcher is just calling the function:
href={generateUrlSwitch('en')}
href={generateUrlSwitch('fr')}
What am I doing wrong ?

Is it possible to get full url (including origin) from route in VueJS?

In this example:
const resolved = this.$router.resolve({
name: 'about'
})
console.log(resolved.route.path)
Is it possible to get route with origin included? Like, if the url is site.com/about, the code would give /about, so I'll need to append origin myself: window.location.origin + resolved.route.path.
I've been using this (tested with vue-router v4, browser only):
const route = router.resolve({ /* your route here */ });
const absoluteURL = new URL(route.href, window.location.origin).href;
Edit: Used location.origin instead of location.href since resolve().href already includes base.
No, not from the router.
Even the router's base property is relative to the app root:
The base URL of the app. For example, if the entire single page application is served under /app/, then base should use the value "/app/".
$route.fullPath also begins at the app root. The docs describe it as:
The full resolved URL including query and hash.

Setting location.href in Ember.js

I am developing an Ember.js application whose initial page resides at:
https://localhost:8443/
I want to perform a redirect from JavaScript to the following location if the user is authenticated:
https://localhost:8443/admin
To do this, I write code as follows:
App.ApplicationRoute = Ember.Route.extend({
model: function () {
var that = this,
session = this.get('session'),
sessionService = this.get('services').session();
return sessionService.getStatus().then(function (sessionStatus) {
session.setProperties(sessionStatus);
if (/^\/[signup]|[forgotPassword]/.test(that.router.get('url'))) return;
if (!session.get('authenticated'))
that.transitionTo('signin');
else
window.location.href = window.location.origin + '/admin';
});
}
});
However, when I do perform this, the page browses to:
https://localhost/admin
I also tried location.assign() with the same result.
Why does setting location.href or location.assign() remove the port-number from the URL? Is this something that the Ember.js Router would be doing?
Any other way I can accomplish this?
You have to look at the documentation for the Location object to see why. Essentially, there's ways to get every part of the URL and you just happen to be using the wrong one. Try one of these:
window.location.href = window.location.host + '/admin';
Or, more preferably:
window.location.href = '/admin';
The second way is nice because it allows you to forget about the base URL altogether and just worry about the relative path you need.
Also, Ember.js doesn't affect this kind of functionality at all, you're just dealing with the Javascript browser API.

History API and domain-level urls

I have two pages on MyDomain.com. The index view which is visible from MyDomain.com/ and MyDomain.com/Foo/Bar. Each view has an ajax call to the other and each one pushes the state using the HTML5 History API.
There are the steps that create the problem:
Start at MyDomain.com/ (Works as expected.)
Click the ajax link to MyDomain.com/Foo/Bar/ (Works as expected.)
Click the ajax link to MyDomain.com/ (Works as expected.)
Click the ajax link to MyDomain.com/Foo/Bar/
Now the URL appears as MyDomain.com/Foo/Foo/Bar/
I don't want a Foo Foo Bar.
My current workaround is to add "../../../" to the front of the URL, but this is inelegant and not foolproof. Another option is a regex expression to count the directory levels.
Is there a better way to get absolute URLs with the History API?
function push(updateElementID, controller, action, url)
{
if (typeof url == "undefined")
{
url = "/" + controller + "/" + action;
}
var state = {
id: updateElementID,
controller: controller,
action: action
}
history.pushState(state, null, url);
}
You may want to ensure that the base element of the href element in the DOM is cleared...at least in the following case, it works for me: On your shared layout page (_layout.cshtml), reset the absolute URL within the DOM by placing the following within the head tags:
<base id="htmldom" href="http://localhost:59805/"/>
of course replacing your port # and on launch, replacing the root URL to the actual domain name.
By doing this, you are setting, or resetting the href property and specifying a base URL for all other relative URLs.
Now would doing it this way would affect any user-input data preserved in the page between back and forward buttons? I'm guessing it would be fine, as the above only resets the href property, and any other info in the DOM should be there.

Categories