How to force update Single Page Application (SPA) pages? - javascript

In fully server side based rendering (non Web 2.0), deploying server side code would directly update client side pages upon page reload. In contrast, in React based Single Page Application, even after React components were updated, there would be still some clients using old version of the components (they only get the new version upon browser reload, which should rarely happen) -> If the pages are fully SPA, it's possible that some clients only refresh the pages after a few hours.
What techniques should be employed to make sure the old components versions are not used anymore by any clients?
Update: the API doesn't changed, only React Component is updated with newer version.

You can have a React component make an ajax request to your server, when the application loads, to fetch "interface version". In the server API, you can maintain an incremental value for the client version. The React component can store this value on the client (cookie/local storage/etc). When it detects a change, it can invoke window.location.reload(true); which should force the browser to discard client cache and reload the SPA. Or better still, inform the end-user that a new version will be loaded and ask them if they wish to save the work and then reload etc. Depends on what you wanna do.

Similar to Steve Taylor's answer but instead of versioning API endpoints I would version the client app, in the following way.
With each HTTP request send a custom header, such as:
X-Client-Version: 1.0.0
The server would then be able to intercept such header and respond accordingly.
If the server is aware that the client's version is stale, for example if the current version is 1.1.0, respond with an HTTP status code that will be appropriately handled by the client, such as:
418 - I'm a Teapot
The client can then be programmed to react to such a response by refreshing the app with:
window.location.reload(true)
The underlying premise is that the server is aware of the latest client version.
EDIT:
A similar answer is given here.

What techniques should be employed to make sure the old components
versions are not used anymore by any clients?
today (2018), many front apps use service workers. With it, it's possible to manage your app lifecycle by several means.
Here is a first example, by using a ui notification, asking your visitors to refresh webpage in order to get latest application version.
import * as SnackBar from 'node-snackbar';
// ....
// Service Worker
// https://github.com/GoogleChrome/sw-precache/blob/master/demo/app/js/service-worker-registration.js
const offlineMsg = 'Vous êtes passé(e) en mode déconnecté.';
const onlineMsg = 'Vous êtes de nouveau connecté(e).';
const redundantMsg = 'SW : The installing service worker became redundant.';
const errorMsg = 'SW : Error during service worker registration : ';
const refreshMsg = 'Du nouveau contenu est disponible sur le site, vous pouvez y accéder en rafraichissant cette page.';
const availableMsg = 'SW : Content is now available offline.';
const close = 'Fermer';
const refresh = 'Rafraîchir';
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
function updateOnlineStatus() {
SnackBar.show({
text: navigator.onLine ? onlineMsg : offlineMsg,
backgroundColor: '#000000',
actionText: close,
});
}
window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);
navigator.serviceWorker.register('sw.js').then((reg) => {
reg.onupdatefound = () => {
const installingWorker = reg.installing;
installingWorker.onstatechange = () => {
switch (installingWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller) {
SnackBar.show({
text: refreshMsg,
backgroundColor: '#000000',
actionText: refresh,
onActionClick: () => { location.reload(); },
});
} else {
console.info(availableMsg);
}
break;
case 'redundant':
console.info(redundantMsg);
break;
default:
break;
}
};
};
}).catch((e) => {
console.error(errorMsg, e);
});
});
}
// ....
There's also an elegant way to check for upgrades in background and then silently upgrade app when user clicks an internal link. This method is presented on zach.codes and discussed on this thread as well.

You can send app’s version with every response from any endpoint of your API. So that when the app makes any API request you can easily check there’s a new version and you need a hard reload. If the version in the API response is newer than the one stored in localStorage, set window.updateRequired = true. And you can have the following react component that wraps react-router's Link:
import React from 'react';
import { Link, browserHistory } from 'react-router';
const CustomLink = ({ to, onClick, ...otherProps }) => (
<Link
to={to}
onClick={e => {
e.preventDefault();
if (window.updateRequired) return (window.location = to);
return browserHistory.push(to);
}}
{...otherProps}
/>
);
export default CustomLink;
And use it instead of react-router's Link throughout the app. So whenever there's an update and the user navigates to another page, there will be a hard reload and the user will get the latest version of the app.
Also you can show a popup saying: "There's an update, click [here] to enable it." if you have only one page or your users navigate very rarely. Or just reload the app without asking. It depends on you app and users.

I know this is an old thread, and service workers are probably the best answer. But I have a simple approach that appears to work:
I added a meta tag to my "index.html" file :
<meta name="version" content="0.0.3"/>
I then have a very simple php scrip in the same folder as the index.html that responds to a simple REST request. The PHP script parses the server copy of the index.html file, extracts the version number and returns it. In my SPA code, every time a new page is rendered I make an ajax call to the PHP script, extract the version from the local meta tag and compare the two. If different I trigger an alert to the user.
PHP script:
<?php
include_once('simplehtmldom_1_9/simple_html_dom.php');
header("Content-Type:application/json");
/*
blantly stolen from: https://shareurcodes.com/blog/creating%20a%20simple%20rest%20api%20in%20php
*/
if(!empty($_GET['name']))
{
$name=$_GET['name'];
$price = get_meta($name);
if(empty($price))
{
response(200,"META Not Found",NULL);
}
else
{
response(200,"META Found",$price);
}
}
else
{
response(400,"Invalid Request",NULL);
}
function response($status,$status_message,$data)
{
header("HTTP/1.1 ".$status);
$response['status']=$status;
$response['status_message']=$status_message;
$response['content']=$data;
$json_response = json_encode($response);
echo $json_response;
}
function get_meta($name)
{
$html = file_get_html('index.html');
foreach($html->find('meta') as $e){
if ( $e->name == $name){
return $e->content ;
}
}
}

Yes in server side rendering if you need to update a small part of the page also you need to reload whole page. But in SPAs you update your stuffs using ajax, hence no need to reload the page. Seeing your problem I have some assumptions:
You see one of your component got updated but other components getting data from same API didn't update. Here comes Flux Architecture. where you have your data in store and your component listen to store's changes, whenever data in your store changes all your components listening to it's change will be updated (no scene of caching).
Or
You need to control your component to be updated automatically. For that
You can request your server for data in specific intervals
Websockets can help you updating component data from server.

Related

React : Handle multiple browser(s), browser tabs to restrict duplicate operations

I have a button Submit when clicked performs some operation calling an API. Post click, the button is disabled or basically the initial state of the button and the operation is changed.
I have two or multiple browser tabs which shows same screen of Submit. If in any one of the tab, the Submit operation is performed, the other tabs should show the updated version. The other tabs should show the disabled version and should not show the initial state.
How do I achieve this? I am using React, JS
#1 Data duplication MUST be restricted at the server side.
I would recommend some cache like node-cache. Node-cache will having scalability issues, so better to go with redis. (The logic should be smt. like: If the form has submited with the user_id, or with form_id, you can create a cache for that, and if you proceed it, store it in the db, other case throws an error. On the other browser you must validate before the submit if the unique_id neither in the cache nor in the db. If exists, you can throws an error in the nr.2 browser.
#2 If you want to disable the button, you have to use websockets
If you're looking for a client-only solution, here is a great article about sharing state between browser tabs. The limitation is that it won't work on different browsers/machines.
The best way to handle this from a UI/UX perspective is to use validation. If User A clicks submit, then User B clicks submit from a different browser or tab, an error should be displayed to User B indicating that "This action has already taken place".
That being said, what you are trying to achieve is possible.
One way is by using a WebSocket. A WebSocket is a persistent connection between the client and server, that allows bi-directional communication.
The page with the submit button in your React app would be a "subscriber" to some websocket channel. When the submit button is clicked for the first time(it doesn't matter from where), a message can be "published" from a WebSocket server to ALL subscribers, regardless of the browser or tab being used.
Basically, you would add an onMessage handler in your React app where you can disable the submit button when a specific message is received.
I don't know what tech you are using on the server side, but for a WebSocket server, there are many options out there. For the React app, there is react-websocket which is straight-forward to use.
you can do it in client-side
const Form = () => {
const [buttonDisabled, setButtonDisable] = useState(false);
// first tab fire
const onSubmit = () => {
.
.
.
localStorage.setItem("formSubmited", "true");
};
useEffect(() => {
const disableButton = (e) => {
if (e.storageArea.formSubmited) {
setButtonDisable(true);
}
};
// second tab fire
window.addEventListener("storage", disableButton);
return () => {
window.removeEventListener("storage", disableButton);
};
}, []);
.
.
.
};

SignalR only available in the active SILO

Im developing a real time application using SignalR, im using MVC and Angularjs (SILO). Each cshtml page is a SPA. i have an angularjs common service which contains generic operations like delete, post etc.. Also in my common service i introduced signalR. The problem im having is that since the Serivice is only loaded when a SPA loads, so signalR only works in that SPA. What i want is my signalR to be available in my entire application.. what can i do to achieve this?
My Common Service Code
self.hub = $.connection.MyHub;
var initializeSignalR = function () {
self.hub.client.addIssue = function (issue) {
alert(JSON.stringify(issue));
};
$.connection.hub.url = "https://sitename.azurewebsites.net/signalr";
$.connection.hub.logging = true;
$.connection.hub.start().done(function () {
var tenant = sessionStorage.getItem("tenantName");
if (tenant !== "") {
self.hub.server.subscribe(tenant);
} else {
console.log("Error occured");
}
});
};
initializeSignalR();
And on my SILO i have this
viewModelHelper.hub.on('addIssue', function (issueItem) {
issues.push(issueItem);
});
viewModelHelper is my service, lets say if im in SPA Home, signalR methods in SPA Contacts are not excecuted, i understand why, Its because my service is not initialized in that SPA. How can i achieve signalR availability throughtout my app even when the SPA is not loaded.
I think you will have two options:
Load it again in every SILO by placing your SignalR service reference in shared layout. This can keep your current application structure.
Change your app to become real Single Page App(SPA) then you can load and reuse just one SignalR connection in entire app. But this will changed so much of your current system. In that way, you can not use cshtml as current, all view layer and some part of logic will be pushed to client side.

iOS error while trying to write fetched database data using AsyncStorage when App is in Background

This is the context :
When I open my app I'm querying my firebase database. These queries are async, I'm waiting for firebase to give me the answer and then I return the value in redux. I'm using redux-persist so when redux get the response of firebase, redux-persist write the data on my phone.
This is the problem :
The problem is that if the user leave the app and then reopen it, if firebase return the response while the app is in background, redux-persist will try to write with AsynStorage. iOS doesn't allow this so my app will crash with these 2 errors
Error storing data for key: "profile"
'Failed to symbolicate warning, "%s":', 'Possible Unhandled Promise
Rejection (id: 0):
Failed to write manifest file.Error Domain=NSCocoaErrorDomain
Code=513 "You don’t have permission to save
the file “manifest.json” in the folder “RCTAsyncLocalStorage_V1”."
UserInfo=
{NSFilePath=/var/mobile/Containers/Data/Application/
C892F501-5057-4FE4-A622-2037C32D1373/Documents/
RCTAsyncLocalStorage_V1/manifest.json,
NSUserStringVariant=Folder, NSUnderlyingError=0x17025b810 {Error
Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}'
What I tried :
I have tested two solutions :
First Try I have tried to pause the persistor of redux-persist depending on the app state.
Store.js
export const persistor = persistStore(store, { storage: AsyncStorage }, () => {
console.log('restored');
});
And then in my Home component
componentDidMount() {
if (Platform.OS === 'ios') {
AppState.addEventListener('change', this.handleAppStateChange);
}
handleAppStateChange = (appState) => {
if (appState !== 'active') {
persistor.pause();
} else {
persistor.resume()
}
};
componentWillUnmount() {
if (Platform.OS === 'ios') {
AppState.removeEventListener('change', this.handleAppStateChange);
}
}
Result Same Error as before, when I tried to console log the current state of the persistor he was paused and resumed on time.
Second Try I have tried to use a CustomAsyncStorage instead of AsyncStore in the persitStore(), I took all the code from AsyncStorage.js and then I add on every method a verification of the current app State who return the method if app state isn't active.
Result No luck redux-persist and redux stop working.
My Other ideas
Cancel every call of firebase when the app pass in background -> Can't be done no Method exist for that
In each method where I call Firebase and dispatching value in redux, check the current state of the app and return it only if app is active -> That means I should check every call so it's a lot of repetition.
React-native Version: 0.39.2
Redux-persist Version: 4.4.2
I'm open of any idea or any help for this case, what do you think I should do for resolve this issue? And which is the best
Thanks.
Check whether Data Protection is enabled in your iOS app's entitlements. It's probably configured to be NSFileProtectionComplete.
When data protection is enabled like this, all files are encrypted by default. This includes the storage backing React Native's AsyncStorage. This isn't a problem when the application is in the foreground, but, shortly after backgrounding the app or locking the device, iOS will throw away your decryption keys.
If your app attempts to write to a file (such as via AsyncStorage) after its decryption keys have been unloaded, the file operation will fail with precisely the (unhelpful) message that you saw:
Error Domain=NSCocoaErrorDomain Code=513 "You don’t have permission to save the file ...
An easy fix would be to change your default data protection level to be NSFileProtectionCompleteUntilFirstUserAuthentication. For more information, see "Encryption and Data Protection" in this guide: https://www.apple.com/business/docs/iOS_Security_Guide.pdf

Error when using React.js and the Twilio API

I'm investigating creating a small Twilio app for a project using React.js. I'm fairly good at React and JavaScript (but not an expert), but I'm having a bit of trouble.
Initially, I am trying to load all the messages on the account to the webpage. Here is the code I have:
import React from 'react'
import {connect} from 'react-redux'
var accountSid = '####';
var authToken = "####";
var client = require('twilio')(accountSid, authToken);
var msgList = []
const messages = () => {
client.messages.list(function(err, data) {
data.messages.forEach(function(message) {
msgList.push(message)
});
})
return msgList
}
class LandingPage extends React.Component {
render() {
return (
<h1>Hello!</h1>
)
}
}
export default connect(select)(LandingPage)
(of course, there are more files, but this is my current working file where I am having the issues).
First of all, when I load this up in my browser, I get an error in the console:
Uncaught TypeError: querystring.unescape is not a function
This apparently relates to the client.messages.list(function(err, data) { line.
Also, how would I go about rendering each message.body? I guess I would have to use a for loop but I'm not sure where that would go here.
The library you are trying to use was written for Node.js, and I assume you're trying to use it in the browser or in React Native? A couple bits:
You shouldn't use the Twilio API from a client side application with your account SID and auth token. These credentials are the "keys to the kingdom" for your Twilio account, and can be compromised if you put them in any client-side app.
The module you are trying to use was written for Node.js, and may not work out of the box in the browser. I haven't tested it, but it might work with Browserify if you were to package your front-end code that way.
For the use case you're trying to implement, I would fetch the messages on your server, and send them to the client via Ajax rather than hitting the API directly from the client.

Facebook auth.sessionChange event and page changes

I am creating a Facebook Canvas application, and I am following more or less the demo app.
As in the demo, I can get the user data from the signed_request which is POSTed to my site on the first page load. Moreover, as suggested in the demo, I use the following snippet to reload the page on user changes:
var Config;
function facebookInit(config) {
Config = config;
FB.init({...});
FB.Event.subscribe('auth.sessionChange', handleSessionChange);
...
}
function handleSessionChange(response) {
if ((Config.userIdOnServer && !response.session) ||
Config.userIdOnServer != response.session.uid) {
goHome();
}
}
function goHome() {
top.location = 'http://apps.facebook.com/' + Config.canvasName + '/';
}
Here Config is an object which is populated with info from the server.
The problem appears when the user navigates the application using a link. If I use AJAX, the page is not actually reloaded and everything works fine. But if I use a standard link, the user comes to another page of my application with a standard GET request. In this case, I do not know how to get the info about the user, so Config.userIdOnServer is undefined and a page reload is triggered.
Of course I can avoid this by removing the call to handleSessionChange, or by making a one-page app with only AJAX links, but I feel I am missing something basic about the Facebook flow.
My fault, I had problems in retrieving the cookie with the user identity for subsequent requests.

Categories