I am using service worker and precache assets in install event.
I also have fetch listener which intercepts requests and caches then at runtime dynamically. I know that people say to use indexeddb for dynamic content such as json data and possibly images.
Question: Why isn't it a good practice to use cache API for that json data too even though it's request/response storage?
The reason I am asking this is because I tried the following: I have index.html and main.js as precached in install event and in main.js I have axios request which returns some json and puts it in index.html. If I use dynamic caching which means when the request to that json api endpoint gets made, it goes first to my service worker, which gets the response and puts it into cache. Then I tested that and when refreshed the page in offline mode, I still got the same result (json data put in index.html accordingly).
So I guess even though Cache API store request/response, it still worked for json endpoint api urls flawlessly.
Any good idea why to prefer indexeddb over cache API while using service worker?
It's perfectly fine to cache JSON data using the Cache Storage API, as an alternative to using IndexedDB. I would expect similar performance characteristics, and in both cases you could read/write the data from either the service worker or window context.
It would be slightly more awkward to use the Cache Storage API if you have JSON data that isn't already associated with a Response object, or that doesn't have a "real" request URL, since you'll have to effectively "fake" them. But that's not particularly hard to do:
const data = {
// some data
};
const jsonString = JSON.stringify(data);
const jsonResponse = new Response(jsonString, {
headers: {
'content-type': 'application/json',
},
});
const cache = await caches.open('json-cache');
await cache.put('/some-json-url', jsonResponse);
Related
I want to periodically call an API from my service worker to send data stored in the localStorage. This data will be produced and saved in localStorage when a user browses my website. Consider it something like saving stats in localStorage and sending it periodically through the service worker. How should I do this? I understand that I can't access localStorage from the service worker and will have to use the postMessage API. Any help would be highly appreciated.
You cannot access localStorage (and also sessionStorage) from a webworker process, they result will be undefined, this is for security reasons.
You need to use postMessage() back to the Worker's originating code, and have that code store the data in localStorage.
You should use localStorage.setItem() and localStorage.getItem() to save and get data from local storage.
More info:
Worker.postMessage()
Window.localStorage
Pseudo code below, hoping it gets you started:
// include your worker
var myWorker = new Worker('YourWorker.js'),
data,
changeData = function() {
// save data to local storage
localStorage.setItem('data', (new Date).getTime().toString());
// get data from local storage
data = localStorage.getItem('data');
sendToWorker();
},
sendToWorker = function() {
// send data to your worker
myWorker.postMessage({
data: data
});
};
setInterval(changeData, 1000)
Broadcast Channel API is easier
There are several ways to communicate between the client and the controlling service worker, but localStorage is not one of them.
IndexedDB is, but this might be an overkill for a PWA that by all means should remain slim.
Of all means, the Broadcast Channel API results the easiest. It is by far much easier to implement than above-mentioned postMessage() with the MessageChannel API.
Here is how broadcasting works
Define a new broadcasting channel in both the service worker and the client.
const channel4Broadcast = new BroadcastChannel('channel4');
To send a broadcast message in either the worker or the client:
channel4Broadcast.postMessage({key: value});
To receive a broadcast message in either the worker or the client:
channel4Broadcast.onmessage = (event) => {
value = event.data.key;
}
I've been using this package called localforage that provides a localStorage-like interface that wraps around IndexedDB. https://github.com/localForage/localForage
You can then import it by placing it in your public directory, so it is served by your webserver, and then calling: self.importScripts('localforage.js'); within your service worker.
https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers says
Note: localStorage works in a similar way to service worker cache, but it is synchronous, so not allowed in service workers.
Note: IndexedDB can be used inside a service worker for data storage if you require it.
Also there is a bit of discussion here: How do I access the local storage using service workers in angular?
Stumbling over this question myself for a tiny webapp-project, I considered the following solution:
When the user is online, the data can be sent immediately. When he is offline, I use the SyncEvent.tag property to send information from the client to the serviceworker. Like this:
//offline_page.html (loads only, when user is offline)
button.onclick = function() {
//on click: store new value in localStorage and prepare new value for synchronization
localStorage.setItem("actual", numberField.value);
navigator.serviceWorker.ready.then(function(swRegistration) {
return swRegistration.sync.register('newval:'+numberField.value);
});
}
//sw.js
self.addEventListener('sync', function(event) {
//let's say everything in front of ':' is the option, everything afterwards is the value
let option = event.tag.replace(/(.*?)\:.*?$/, "$1");
let value = event.tag.replace(/.*?\:(.*?)$/, "$1");
if(option == "newval") {
event.waitUntil(
fetch("update.php?newval="+value)
.then(function(response) {
console.log(response);
})
);
}
});
update.php saves the new value to backend, as soon as the user goes online.
This won't give the service worker access to the localStorage, but it will send him every change made.
Just starting to get used to this syncing topic. So I would really be interested, wheather this is helpful and what others think about this solution.
I am working on a web app using ReactJS in which I have created a form and stored the values of all the input fields into state of the app. That is, the state of my App.js file contains values of all the input fields created in Form.js file. I want the information stored in state to be passed on to the backend so that I can process a dataset based on it.
How do I add a functionality so that on clicking a submit button everything that's in my app state gets passed on to the backend- say to a text file, or in a json file.
Basically I want to search through a dataset (using Elasticsearch) based on the information provided by a user in the form (using ReactJS).
I am new to React so I don't have much knowledge. I have made the web-app but I need suggestions on how to pass the information that I obtain through the form to a backend so that I can do further work.
How do I add a functionality so that on clicking a submit button
everything that's in my app state gets passed on to the backend- say
to a text file, or in a json file.
You can make a request for data to use in your application using Axios OR Fetch API. You can consume REST APIs using two of the most popular methods known as Axios (a promise-based HTTP client) and Fetch API (a browser in-built web API).
The fetch() API is an inbuilt JavaScript method for getting resources from a server or an API endpoint. It’s similar to XMLHttpRequest
Axios is an easy to use promise-based HTTP client for the browser and node.js. Since Axios is promise-based, we can take advantage of async and await for more readable and asynchronous code. With Axios, we get the ability to intercept and cancel request, it also has a built-in feature that provides client-side protection against cross-site request forgery.
To know more you can refer this
Both these above methods, can be used to submit the data from your front end into the back end, so that the data is stored into the back end, and then you can perform several operations on it according to your requirement.
You can refer several blogs and documentation to know more about this:
Use axios to fetch data from an api in ReactJS
Fetch API for POST Request
Axios
Difference between Axios and Fetch API
Recently, I have also created an application, wherein I am integrating React with SpringBoot application.
In this Application. I have set up router and route, created and submit form, called GET, POST, PUT, DELETE request using axios (have also done with fetch API).
submitBook= event =>{
event.preventDefault();
const book = {
title: this.state.title,
author: this.state.author,
coverphotoURL: this.state.coverphotoURL,
isbnNumber: this.state.isbnNumber,
price: this.state.price,
language: this.state.language
};
const headers = new Headers();
headers.append("Content-Type", "application/json");
fetch("http://localhost:8080/rest/books",{
method:"POST",
body:JSON.stringify(book),
headers
})
.then(response => response.json())
.then((book) => {
if(book){
this.setState({"show":true, "method":"post"});
setTimeout(() => this.setState({"show":false}),3000);
}
else{
this.setState({"show":false});
}});
this.setState(this.initialState);
};
To view the full code, you can refer my Github Repository
You need to look into making XHR or using the fetch API (or axios) to make http requests to the backend API.
use axios for calling api urls that you have defined in the backend. you can watch a short tutorial on youtube to get familiar with the basics.
You may want to use Fetch API documented in MDN. It handles url request, including REST API to parse data over HTTP, e.g. JSON object etc.
I can't for the life of me figure this out, it seems like it should be straight forward but it's just not clicking.
I have an ES6 app that I created using create-react-app. I've got all the templates and layouts set up for the project and came to trying to pull in data from an API that I want to sit inside the app - like a botched MVC where React handles the views and I run the models and controllers in PHP.
So I have a function in one of my components that I want to fetch some data. I use the fetch() function (I know this isn't yet compatible with a number of browsers but that's a problem for another day) to fetch a relative path from the component to the model I want to load, however the fetch function treats my path as a call to the base URL followed by the request. So with the site running on localhost:3000, I run the following code in my getData() function...
let test = fetch('../models/overall-stats.php').then(function(response) {
console.log(response);
return response;
});
...the URL that fetch hits is then http://localhost:3000/models/overall-stats.php which simply resolves back to the index.html file and loads the app, rather than the PHP file I'm requesting.
If I need to hit that PHP file to get my data, am I wrong in using fetch? Or am I just using it incorrectly? If I shouldn't be using fetch what's a better approach to this problem I'm having?
When I run this on an apache server (after building and deploying) I can get the fetches to work fine (apache recognizes the structure of the URL and hits it as I am expecting) and I hit the file no issues, but I need to be able to work in a local development environment and have the same functionality. The app will end up being deployed live on an apache server.
Any help would be greatly appreciated.
I knew after sleeping on this it would be very straight-forward... I simply had to move my models and controllers into the public directory for them to be accessible. I'll be putting in authentication to the models so that they can't be hit directly, but only through GET requests.
Why don't you just use something like ${baseUrl}/models/... ?
Also for solving browsers problem with fetch you can import the Polyfill or simply use axios (my choice)!
Maybe you can try to use ajax to get or post the data from server, just like this:
$.ajax({
url: '../models/overall-stats.php',
data: {
},
type: 'GET',
dataType : 'json',
success : function(res){
let obj = parseJSON(res)
}
})
or add this on top in your php file because the CORS :
header('Access-Control-Allow-Origin: *');
I'm relatively new to full-stack development, and currently trying to figure out an effective way to send and fetch large data between my front-end (React) and back-end (Express) while minimizing memory usage. Specifically, I'm building a mapping app which requires me to play around with large JSON files (10-100mb).
My current setup works for smaller JSON files:
Backend:
const data = require('../data/data.json');
router.get('/', function(req, res, next) {
res.json(data);
});
Frontend:
componentDidMount() {
fetch('/')
.then(res => res.json())
.then(data => this.setState({data: data}));
}
However, if data is bigger than ~40mb, the backend would crash if I test on local due to running out of memory. Also, holding onto the data with require() takes quite a bit of memory as well.
I've done some research and have a general understanding of JSON parsing, stringifying, streaming, and I think the answer lies somewhere with using chunked json stream to send the data bit by bit, but am pretty much at a loss on its implementation, especially using a single fetch() to do so (is this even possible?).
Definitely appreciate any suggestions on how to approach this.
First off, 40mb is huge and can be inconsiderate to your users especially if there' s a high probability of mobile use.
If possible, it would be best to collect this data on the backend, probably put it onto disk, and then provide only the necessary data to the frontend as it's needed. As the map needs more data, you would make further calls to the backend.
If this isn't possible, you could load this data with the client-side bundle. If the data doesn't update too frequently, you can even cache it on the frontend. This would at least prevent the user from needing to fetch it repeatedly.
Alternatively, you can read the JSON via a stream on the server and stream the data to the client and use something like JSONStream to parse the data on the client.
Here's an example of how to stream JSON from your server via sockets: how to stream JSON from your server via sockets
I'm using Next.js, and I have a custom server using Express. I have a page that requires some data from the database.
getInitialProps(), when running on the server, could just grab the data from the database and return it, without any problems.
However, getInitialProps() can also run on the client side (when the user initially requests a different page, then navigates to this one). In that case, since I'm on the client side, I obviously can't just fetch the data from the database - I have to use AJAX to talk to the server and ask it to retrieve it for me.
Of course, this also means that I have define a new Express route on the server to handle this request, which will contain exactly the same code as the server-side part of getInitialProps(), which is very undesirable.
What's the best way to handle this?
getInitialProps() always receives the request and response as parameters which are only set on the server:
static async getInitialProps({req}){
if(req){
// called on server
} else {
// called on client
}
}
https://github.com/zeit/next.js#fetching-data-and-component-lifecycle
Since no good solution seemed to have existed, I have created and published a library to provide a simple and elegant solution to this problem: next-express.
In your getInitialProps you should be making a http request to a new express route that has your logic for fetching from the database. That logic should never live in the UI layer.
This route should then be called regardless of whether you are on the client or on the server - you don't need to do any code branching.
Make an API distinct from your next.js app. Think of the next app as a frontend client that happens to render pages on the server
With time new solutions come around.
Nextjs has introduced a new method getServerSideProps primarily for such use cases
getServerSideProps only runs on server-side and never runs on the browser.
For me, the quickest way I found is to get the data from __NEXT_DATA__
MyApp.getInitialProps = async (): Promise<AppCustomProps> => {
const isInBroswer = typeof window !== 'undefined';
if (isInBroswer) {
const appCustomPropsString =
document.getElementById('__NEXT_DATA__')?.innerHTML;
if (!appCustomPropsString) {
throw new Error(`__NEXT_DATA__ script was not found`);
}
const appCustomProps = JSON.parse(appCustomPropsString).props;
return appCustomProps;
}
// server side, where I actually fetch the data from db/cms and return it
}