I am trying to send a variable(boolean) to the app.js when the registration onupdatefound function is triggered , so whenever a new update is received the app.js will know and then i can show a popup with a refresh button.
most of the part i have implemented , i am just confused as how my app.js will receive data on "message" addEventListener as i am not able to receive any data from it.
thanking you in advance.
registerServiceWorker.js
registration.onupdatefound = () => {
console.log('sw onupdatefound');
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
console.log('sw onstatechange',navigator.serviceWorker);
if (installingWorker.state === 'installed') {
console.log('sw onstatechange//',navigator.serviceWorker);
if (navigator.serviceWorker.controller) {
// available; please refresh." message in your web app.
console.log('New content is available; please refresh...');
navigator.serviceWorker.controller.postMessage({
data: {
toUpdate: true,
},
});
} else {
console.log('Content is cached for offline use.');
}
}
};
};
service-worker.js
self.addEventListener('fetch', (event) => {
console.log('in fetch of s-w');
self.clients.matchAll().then(all => all.map(client => client.postMessage(event)));
});
self.addEventListener('message', (event) => {
console.log('event msg from s-w', event.data);
if (event.data.toUpdate) {
console.log('updating');
self.skipWaiting();
}
// Select who we want to respond to
self.clients.matchAll().then(all => all.map(client => client.postMessage(event.data)));
self.clients
.matchAll({
includeUncontrolled: true,
type: 'window',
})
.then((clients) => {
clients.postMessage(event.data);
if (clients && clients.length) {
// Send a response - the clients
// array is ordered by last focused
clients[0].postMessage(event.data);
}
});
});
app.js
navigator.serviceWorker.onmessage = (event) => {
console.log('event in app.js nav on msg', event);
if (event.data.toUpdate) {
alert('Please refresh your page to upadate service worker');
}
};
window.addEventListener('message', (event) => { console.log('new event ====>', event); });
In CRA v3 onSuccess and onUpdate callbacks can be passed as options to serviceWorker.register.
Typically those callbacks update the application store which in turn would affect the UI components.
For a minimal CRA v3 demo:
// App.js
import React from 'react';
import './App.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {message: 'Hello World'};
// setup callback to get
// service worker installation status
//
this.props.listen(status => {
this.setState({message: status});
});
}
render() {
return (
<p className="App">{this.state.message}</p>
);
}
}
export default App;
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
const {listen, onUpdate, onSuccess} = setupStatusTransfer();
ReactDOM.render(
<React.StrictMode>
<App listen={ listen } />
</React.StrictMode>,
document.getElementById('root')
);
serviceWorker.register({
onUpdate,
onSuccess
});
function setupStatusTransfer() {
let cb;
let status;
const send = s => {
status = s;
if(cb && status) cb(status);
}
const listen = callback => {
cb = callback;
send(status);
};
return {
listen,
onUpdate: makeSendStatus(send, 'update'),
onSuccess: makeSendStatus(send, 'success')
};
}
function makeSendStatus(send, status) {
return _registration => {
send(status);
};
}
See also: Let Users Know When You Have Updated Your Service Workers in Create React App.
Are you attempting to notify all currently open tabs (windows) that are being served by the service worker? That could be somewhat problematic as you are relying on the old service worker to correctly process the message being posted to it by the new registration - ultimately you have no idea how out of date the currently active service worker is.
That being said, to process the postMessages within a React component you should use the componentDidMount and componentWillUnmount lifecycle methods (or useEffect hook).
The following simplified example uses a dedicated Web Worker with workerize-loader but the principle remains the same:
// App.js
import React from 'react';
import './App.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {count: '?'};
const nf = new Intl.NumberFormat(navigator.language, {minimumIntegerDigits: 6});
this.receive = event => {
const { data: count } = event;
if (typeof count === 'number') {
this.setState({count: nf.format(count)});
}
};
}
componentDidMount() {
this.props.worker.addEventListener('message', this.receive);
}
componentWillUnmount() {
this.props.worker.removeEventListener('message', this.receive);
}
render() {
return (
<p className="App">{this.state.count}</p>
);
}
}
export default App;
// App.js with hooks
import React, { useState, useEffect } from 'react';
import './App.css';
function App({ worker }) {
const [count, setCount] = useState('?');
const subscribe = () => {
return listenForUpdates(worker, setCount);
}
useEffect(subscribe, [worker]);
return (
<p className="App">{ count }</p>
);
}
function listenForUpdates(worker, setCount) {
const nf = new Intl.NumberFormat(
navigator.language,
{minimumIntegerDigits: 6}
);
const receive = ({ data }) => {
if (typeof data === 'number') {
setCount(nf.format(data));
}
};
const cleanup = () => {
worker.removeEventListener('message', receive)
};
worker.addEventListener('message', receive);
return cleanup;
}
export default App;
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
// eslint-disable-next-line import/no-webpack-loader-syntax
import worker from 'workerize-loader!./worker';
let instance = worker();
instance.start(1000);
ReactDOM.render(
<React.StrictMode>
<App worker={ instance } />
</React.StrictMode>,
document.getElementById('root')
);
// worker.js for https://github.com/developit/workerize-loader
//
export function start(delay) {
let count = 0;
const sendAndUpdate = () => {
postMessage(count);
++count;
};
setInterval(sendAndUpdate, delay);
}
addEventListener and removeEventListener should be available on the ServiceWorker interface obtainable from navigator.serviceWorker.controller.
(Your app.js example tries navigator.serviceWorker.onmessage - but navigator.serviceWorker is the ServiceWorkerContainer - the ServiceWorker is found on navigator.serviceWorker.controller - that's where the onmessage property can be found).
Update: With Create React App 4 ServiceWorker has become opt-in, i.e. is no longer included in the default CRA template.
ServiceWorker is included in cra-template-pwa (Making a Progessive Web App) which itself uses ServiceWorker via Workbox libraries.
npx create-react-app my-app --template cra-template-pwa
The ServiceWorker registration logic has been segregated into serviceWorkerRegistration.js - separately from the service-worker.js. Furthermore the ServiceWorker is only registered for production builds being served. To build:
yarn run build
And then serve, like:
http-server -c-1 ./build
Minimal cra-template-pwa v4 demo:
// file: registrationStatus.js
import { useState, useEffect } from 'react';
let current = 'Unchanged';
const subscriptions = new Set();
function subscribe(setStatus) {
subscriptions.add(setStatus);
return () => {
subscriptions.delete(setStatus);
};
}
function dispatch(status) {
current = status;
subscriptions.forEach((setStatus) => setStatus(status));
}
function useStatus() {
const [status, setStatus] = useState(current);
useEffect(() => subscribe(setStatus), []);
return status;
}
// Updated but waiting for stale tabs to close
function onUpdate(_registration) {
dispatch('Updated');
}
// Updated and running
function onSuccess(_registration) {
dispatch('Successful');
}
export { useStatus, onUpdate, onSuccess };
// file: App.js
import { useStatus } from './registrationStatus';
function App() {
const status = useStatus();
return <p className="App">Registration status: {status}</p>;
}
export default App;
// file: index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorkerRegistration from './serviceWorkerRegistration';
import reportWebVitals from './reportWebVitals';
import { onUpdate, onSuccess } from './registrationStatus';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
serviceWorkerRegistration.register({
onUpdate,
onSuccess,
});
reportWebVitals();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With