How To Add Service Worker In React Project - reactjs

I want to add service worker in my react project.
Project is ready the default Service seems not to works.
Even When I try to import this, it gives this error:
Attempted import error: './registerServiceWorker' does not contain a default export (imported as 'registerServiceWorker').
Furthermore, how to add files to be cached in the default version serviceWorker file.
If I add my own custom serviceWorker file just like for no-react (framework) application, will it works for react case?
Currently I have these code in my index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { BrowserRouter } from 'react-router-dom'
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root'));
registerServiceWorker();
And in this says : " Attempted import error: './registerServiceWorker' does not contain a default export (imported as 'registerServiceWorker'). "
Service Worker which I am using is as under: (React's Default Code)
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more,
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See /CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

From your error information,there are something wrong with registerServiceWorker.js file.
import registerServiceWorker from './registerServiceWorker';
However,in registerServiceWorker.js file don't have the following
export registerServiceWorker
So, I recommend to add the below to registerServiceWorker.js
export default registerServiceWorker
Edit:
Use this to import js files
import * as registerServiceWorker from './registerServiceWorker';
And use it like this:
registerServiceWorker.unregister();
Edit2:
I think you have some misunderstanding about import/export.So I will explain it here.
If we want to import some file(e.g child.js) into another file(e.g parent.js).In the file like child.js,it must have export.
There are some ways to do so.
1. In Child.js
const child = () => {
}
export default Child
We will be able to import it in parent.js like below.With the default expression, we actually can use any name in the place of Child in the below.(Usually keep them the same.)
import Child from './child.js'
import ChildReplace from './child.js' //This also works, the ChildReplace are actually the Child in the child.js
You may see another way to import.Like this:
import * as registerServiceWorker from './registerServiceWorker';
The * means all the content in registerServiceWorker.js."as registerServiceWorker" are giving all the content a name for us to import them easily.
The way to import file because in the registerServiceWorker.js,there are many export expressions but without export default.

you can import all the functions exported from registerServiceWorker.js file by using
import * as registerServiceWorker from './registerServiceWorker';
and after that you can call any method from that file to your index.js file like -
registerServiceWorker.unregister();

Try this.
create-react-app FolderName
Is just auomatically creates a registerServiceWorker for your app

Related

React App with basename not working in production build, but works in development (create-react-app)

I have created an app using create-react-app with a basename '/admin' and it is working just fine in development mode. All routes working properly both on localhost and behind nginx proxy.
When I build the app using npm run build I get a blank screen on the '/admin' url, with the following errors in the console:
The script from “https://192.168.1.2/admin/static/js/main.49bb4878.js”
was loaded even though its MIME type (“text/html”) is not a valid
JavaScript MIME type.
The stylesheet https://192.168.1.2/admin/static/css/main.4efb37a3.css
was not loaded because its MIME type, “text/html”, is not “text/css”.
Uncaught SyntaxError: expected expression, got '<' main.49bb4878.js:1
I have tried both <BrowserRouter pathname="/admin">...</BrowserRouter> and the one I have in the following index.js file.
It seems like the server sends the index.html file no matter what the client requests...
This is my index.js file:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import { configureStore } from '#reduxjs/toolkit';
import storeToConfigure from './configureStore';
import CustomRouter from './utils/CustomRouter';
import * as buffer from 'buffer';
import { BrowserRouter } from 'react-router-dom';
console.log(window);
window.Buffer = buffer;
window.process = {}
export const store = configureStore(storeToConfigure);
export const history = createBrowserHistory({ basename: '/admin' });
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<CustomRouter history={history} basename="/thug-app">
<ScrollToTop>
<App />
</ScrollToTop>
</CustomRouter>
</Provider>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint.
reportWebVitals(console.log);
This is the CustomRouter I'm using, in order to be able to directly access the history anywhere without a hook:
import React, { useLayoutEffect, useState } from 'react';
import { Router } from 'react-router-dom';
const CustomRouter = ({
basename,
children,
history,
}) => {
const [state, setState] = useState({
action: history.action,
location: history.location,
});
useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
basename={basename}
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
};
export default CustomRouter;
Again, everything works just fine in development. The problem is when I build the app for production. I have tried both pm2 and serve packages (same names on npmjs). The serve package returns 404, while pm2 returns the errors I mention above.
Thank you for taking the time to help!
I managed to make it work with this workaround:
I ditched both pm2 and serve and used a custom express.js server.
The setup:
const express = require('express');
const path = require('path');
const fs = require('fs');
const app = express();
// app.use(express.static(path.join(__dirname, 'build')));
app.get('/*', (req, res) => {
let theUrl = req.originalUrl.replace('/admin', '');
if (!!!theUrl) {
theUrl = 'index.html';
}
try {
const target = path.join(__dirname, 'build', theUrl);
console.log(target);
if (fs.statSync(target)) {
res.sendFile(target);
} else {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
}
} catch (error) {
console.log(error);
res.sendFile(path.join(__dirname, 'build', 'index.html'));
}
});
app.listen(4000, () => console.log('admin app listening on 4000'));
It also seems to work fine without the following line:
app.use(express.static(path.join(__dirname, 'build')));
Like I had noticed, both pm2 and serve sent the index.html file no matter what the client requested.
Seems to be working fine behind nginx proxy as well.
Whenever the requested file does not exist, simply serve the index.js file.

Reactjs import dynamically js file with default export function

I have a reactjs app
In MyApp component I use an import at top like this:
import { ProvideAuth } from "util/auth.js";
Internally this file util/auth.js I have this code (I import another js file at top like this):
import analytics from "./analytics";
export function ProvideAuth({ children }) {
.....
}
How can I make this import analytics from "./analytics" dynamically depending on a cookie value?.
I made this code, but it doesn't work:
function loadLazyModule() {
console.log("loadLazyModule");
const _module = React.lazy(() =>
import("./analytics.js")
);
return _module;
}
// Provider hook that creates auth object and handles state
export function ProvideAuth({ children }) {
if (statisticsCookie == 'Y') {
console.log("statisticsCookie", statisticsCookie);
loadLazyModule();
}
.....
}
Finally my analytics.js has this code:
// Initialize analytics and plugins
// Documentation: https://getanalytics.io
const analytics = Analytics({
debug: process.env.NODE_ENV !== "production",
plugins: [
googleAnalyticsPlugin({
trackingId: process.env.NEXT_PUBLIC_GA_TRACKING_ID,
}),
],
});
....
export default analytics;
I need to this file import only if my cookie is enabled (has value 'Y'):
import analytics from "./analytics";
Help me please!
Thanks!
you can do it like this.
you can also use, .than().catch() function after that.
async function load() {
let say = await import('./say.js');
say.hi(); // Hello!
say.bye(); // Bye!
say.default(); // Module loaded (export default)!
}
or via import module
let modulePath = prompt("Which module to load?");
import(modulePath)
.then(obj => <module object>)
.catch(err => <loading error,e.g. if no such module>)
it's described here
https://javascript.info/modules-dynamic-imports

Simple MERN Axios calls don't (and have never) worked for me. Consistent 404 Error. Possibly due to File Structure

I've been told that Axios is how you get React to talk to an api (external or internal). So far, I have specifically only received 404 errors whenever I try to implement Axios calls.
Here is the axios call in client/src/App.js:
import React, {Component} from 'react';
import './App.css';
import API from "./utils/API";
class App extends Component {
state = {
recipes: []
}
componentDidMount = () => {
API.getRecipes("milk") /* This is supposed to call the getRecipes
function in API.js with "milk" as the only
parameter (ie - Search the api for "milk"
related recipes). */
.then(res => this.setState({recipes: res.data}))
.catch(err => console.log(err));
}
render(){
return (
<div>
{
this.state.recipes.map(recipe => {
return(
<p>
{recipe.title} // All recipe names are then set to a p tag
</p>
)
})
}
</div>
);
}
}
export default App;
Now, this calls API.js in the "utils" folder:
import axios from "axios";
// Function that takes the parameter and is supposed to send it to the
/api/recipes route
export default {
getRecipes: function(query) {
return axios.get("/api/recipes", { params: { q: query } });
}
};
The relevant api route (/api/recipes) is located in a folder named "routes" outside of the "src" folder. This file is the only item inside the folder.
const axios = require("axios");
const router = require("express").Router();
/* As you can see, this sends the request to "recipepuppy.com" with the
relevant query ("milk").*/
router.get("/recipes", (req, res) => {
axios.get("http://www.recipepuppy.com/api/", {params: req.query})
.then(({data: {results}}) => {res.json(results)})
.catch(err => res.status(422).json(err));
});
module.exports = router;
Going even further out, here is the server.js file (outside of the "client" folder) that determines the routes:
const express = require("express");
const path = require("path");
const PORT = process.env.PORT || 3001;
const app = express();
const apiRoutes = require("./routes/apiRoutes"); // *********
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
if (process.env.NODE_ENV === "production") {
app.use(express.static("client/build"));
}
app.use("/api", apiRoutes); // *********
app.get("*", function(req, res) {
res.sendFile(path.join(__dirname, "./client/build/index.html"));
});
app.listen(PORT, function() {
console.log(`API server now listening on port ${PORT}.`);
});
As far as I can tell, everything is set up perfectly. However, every single time I boot up the server, the browser console error pops up and says:
GET http://localhost:3000/api/recipes?q=milk 404 (Not Found)
Even though server.js directly ties to the apiRoutes folder, and the axios call within API.js calls the exact same route that would result from going to the /api route, then the /recipes route within /api (resulting in /api/recipes).
If anybody here can tell me what is going on and how to fix it, I would appreciate it.
I didn't have "proxy" set in my dependencies (package.json) in the client folder.
Once I set "proxy" to "localhost:3001" (the same as my initial server.js port value) and restarted the server, it worked immediately.

Integrating web3 from Metamask in React

I am new to ReactJS.
Seem to be having trouble integrating web3 from Metamask in React.
Metamask version: web3#1.0.0-beta.34
import Web3 from 'web3'
let web3;
window.addEventListener('load', function () {
if (typeof window.web3 !== 'undefined') {
web3 = new Web3(window.web3.currentProvider);
} else {
// No web 3 provider
console.log("Please install Metamask");
}
});
export default web3;
Getting the following error:
window is not defined
ReferenceError: window is not defined
at Object../lib/getWeb3.js (lib/getWeb3.js:5:0)
window is not defined on Server, only in client's browser, hence you can't use MetaMask server-side. However, you can connect to INFURA when you want to use web3 in your React component server-side or without MetaMask support.
The simplest way is to use react-web3-provider component.
Add the Web3Provider to your root React component:
import Web3Provider from 'react-web3-provider';
ReactDOM.render(
<Web3Provider
defaultWeb3Provider="https://mainnet.infura.io/YOUR_API_KEY"
loading="Loading..."
>
<App />
</Web3Provider>
)
Then in component where you want to use Web3:
import { withWeb3 } from 'react-web3-provider';
class MyComponent {
render() {
const { web3 } = this.props;
web3.eth.getAccounts(console.log);
// Version 1.0.0-beta.35
return "Web3 version: {web3.version}";
}
}
export default withWeb3(MyComponent);

How do I create configuration for axios for default request headers in every http call?

https://github.com/MrFiniOrg/AxiosQuestion
I would like to have my project setup so that I do not have to specify the same request header in every http call.
I have searched this online but I have not been able to accomplish this in my project.
Would someone please assist me in resolving this issue I am having.
I am new to react and axios and I am not sure how to configure this.
My project seems to be doing this but it is sending the request 2 times.
One with the header and one without.
My axios call can be found in the app.js class component
You can specify config defaults that will be applied to every request.
Global axios defaults
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
For more specific info, please visit their docs.
UPDATE:
You can do it in two ways:
1. In your index.js file [meaning the top-level aka 'root' file] you can configure your request/ response methods. Something like this:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import axios from 'axios';
axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com';
axios.defaults.headers.common['Authorization'] = 'AUTH TOKEN';
axios.defaults.headers.post['Content-Type'] = 'application/json';
axios.interceptors.request.use(request => {
console.log(request);
// Edit request config
return request;
}, error => {
console.log(error);
return Promise.reject(error);
});
axios.interceptors.response.use(response => {
console.log(response);
// Edit response config
return response;
}, error => {
console.log(error);
return Promise.reject(error);
});
ReactDOM.render( <App />, document.getElementById( 'root' ) );
registerServiceWorker();
2. Or you can create a new file, a new instance of your axios.js file to be precise, and import the configurations separately in your components where you might need them. You could name it, eg axiosConfig.js, and put your specific configs inside of it. Something like this:
axiosConfig.js
// First we need to import axios.js
import axios from 'axios';
// Next we make an 'instance' of it
const instance = axios.create({
// .. where we make our configurations
baseURL: 'https://api.example.com'
});
// Where you would set stuff like your 'Authorization' header, etc ...
instance.defaults.headers.common['Authorization'] = 'AUTH TOKEN FROM INSTANCE';
// Also add/ configure interceptors && all the other cool stuff
instance.interceptors.request...
export default instance;
After that you would import this file to components that need it and use it instead of the previous Axios [node_modules] import, like this:
Example.js
import React, { Component } from 'react';
// import axios from 'axios'; We don't need this anymore
import axiosConfig from '../../axiosConfig'; // But instead our new configured version :)
class Example extends Component {
state = {
data: [],
error: false
}
componentDidMount () {
// We could name (import) it as axios instead, but this makes more sense here ...
axiosConfig.get('/posts' )
.then(response => {
this.setState({data: response});
});
})
.catch(error => {
this.setState({error: true});
});
}
NOTE: You can combine these two methods as needed, but remember that the configurations made in your configAxios.js file will overwrite those made in your index.js file [if they are the same configurations, that is :) ]
Using interceptors, it runs on each request so if the token changes (refreshes) then the next request picks up the new token. Check for existing values in the request to allow overriding of the header. Consider we are using any token generator and updating token in local storage. Here we are using keyclock object that is stored in localStorage
import * as axios from "axios";
axios.defaults.baseURL = process.env.REACT_APP_BASE_URL;
axios.interceptors.request.use(
config => {
if (!config.headers.Authorization) {
const token = JSON.parse(localStorage.getItem("keyCloak")).token;
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
}
return config;
},
error => Promise.reject(error)
);
I also had the same issue and solution was to locate them in index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import axios from 'axios';
import './index.css';
import 'bootstrap/dist/css/bootstrap.css';
import App from './components/app/App';
import * as serviceWorker from './serviceWorker';
axios.defaults.baseURL = process.env.REACT_APP_BE_URL;
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root'),
);
serviceWorker.unregister();
Also, I use .env files to keep for example base urls:
.env.production
REACT_APP_BE_URL=http://production-url-to-backend/
.env.development
REACT_APP_BE_URL=http://localhost:3000/
And when you run locally .env.development will be used, for production build (npm run build) .env.production will be used.
More about axios global confg: https://github.com/axios/axios#global-axios-defaults
More about .env: https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables
You can put it in a file (as explained here) and then import it in the top level
import { CssBaseline } from "#mui/material";
import "./App.css";
import ProfilePage from "./view/Profile/ProfilePage";
import "./service/axios-config"; //<---import it here
function App() {
return (
<div className="App">
<CssBaseline />
<ProfilePage />
</div>
);
}
export default App;
instead of adding this code in the top level:
axios.defaults.baseURL = process.env.REACT_APP_BE_URL;
I have a simple minimalistic method of setting axios config for request header and it handles global error.
import axios, { AxiosError, AxiosHeaders } from "axios";
import useAuthStore from "../hooks/useAuthStore";
import { BASE_URL } from "./config";
import { getItem } from "./storage";
const axiosInstance = axios.create({
baseURL: `${BASE_URL}`,
headers: {
"Access-Control-Allow-Origin": "*",
},
});
axiosInstance.interceptors.request.use(
async (config) => {
const token = await getItem("jwtToken");
if (config.headers)
(config.headers as AxiosHeaders).set("Authorization", `Bearer
${token}`);
return config;
},
(error) => Promise.reject(error),
);
axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
if (error instanceof AxiosError && error.response?.status === 401)
{
useAuthStore.setState({ signedInAs: undefined });
}
return Promise.reject(error);
},
);
export default axiosInstance;
Note: The base URL is imported from another file while the useAuthStore is a custom hook from zustand that store the user state for authorization.

Resources