How to add workbox to React post update - reactjs

I have decided to try to use workbox but all of the guides i have seen talk about integrating with the service worker that react makes.
But when i install a CRA i no longer get the service worker made for me. What do i need to do to integrate workbox here?
This is my current code:
App.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import swDev from './swDev'
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
swDev()
swDev.js
export default function swDev(){
let swURL = `${process.env.PUBLIC_URL}/sw.js`;
if('serviceWorker' in navigator){
navigator.serviceWorker.register(swURL).then(res => {
console.log('Service worker has been registered');
}).catch(err => console.log('Service worker was not registered'))
}
}
then this is the service worker in the public file
const cacheVersion = 'v1'
self.addEventListener('install', ()=>{
console.log('Service worker has been installed');
})
self.addEventListener('activate', ()=>{
console.log('Service worker is being activated');
})
self.addEventListener('fetch',(e)=>{
e.respondWith(handleRequest(e.request))
// function to update the cache
// updateCache(e.request)
})
async function handleRequest(req){
const cache = await caches.open(cacheVersion)
const cacheRes = await cache.match(req)
if(cacheRes){
return cacheRes;
}else{
const netRes = await fetch(req);
console.log(netRes);
if(netRes.ok){
return netRes;
}else{
// return an error message or something by matching it to a 404 page
return netRes;
}
}
}
async function updateCache(req){
if(navigator.onLine){
const res = await fetch(req);
const cache = await caches.open(cacheVersion);
if(res.ok){
// add the response to the caches and return the response
await cache.put(req, res.clone())
}
}
}

create-react-app v4 will check for the presence of a src/service-worker.js file at build time, and if found, run workbox-webpack-plugin's InjectManifest plugin, passing in that file as the swSrc parameter.
If you're starting a new project and follow the instruction from create-react-app's "Making a Progressive Web App" guide, i.e. you run npx create-react-app my-app --template cra-template-pwa, you'll end up with everything in the right place.
Which is to say your project will:
automatically bundle the code in src/service-worker.js (transforming the ES module imports into code that can be run inside the service worker)
look for the symbol self.__WB_MANIFEST somewhere inside your src/service-worker.js, and replace it with a precache manifest, consisting of URLs and revision info about all your webpack assets, so that Workbox can precache them.
If you're not interested in precaching your webpack assets, then you don't need to use the InjectManifest plugin at all, and you can just put whatever code you want in a file named anything other than src/service-worker.js, and register that file as your service worker. That's up to you.
If you are interested in Workbox's precaching, but you're upgrading from an older create-react-app and you don't have a "correct" src/service-worker.js file, you can manually copy the file from the template into your project.

Related

Invalid Hook in reactJS Using Auth0

I have an app.js with the following const function:
const serverUrl = process.env.REACT_APP_SERVER_URL;
const { getAccessTokenSilently } = useAuth0();
const callEndpoint = async (props) => {
const {endpoint} = props;
const token = await getAccessTokenSilently();
try {
const token = await getAccessTokenSilently();
console.log('endpoint is:', `${serverUrl}/${props.endpoint}`);
const response = await fetch(
`${serverUrl}/${props.endpoint}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const responseData = await response.json();
console.log(responseData.message);
} catch (error) {
console.log(error.message);
}
};
Further down in the render:
<Button variant="outline-primary" onClick = { (e) => {callEndpoint({endpoint:"get_timestamp_public"})}} >Fetch Public Data</Button>
It works fine.
However, when I try to move my endpoint into a new js file called 'api_funcs.js':
import React, { useEffect, useState } from 'react';
import { useAuth0 } from '#auth0/auth0-react';
const UseAPI = (props) => {
const {endpoint} = props;
const serverUrl = process.env.REACT_APP_SERVER_URL;
console.log("I am in here");
const { getAccessTokenSilently } = useAuth0();
const callEndpoint = async () => {
const token = await getAccessTokenSilently();
try {
// const token = await getAccessTokenSilently();
const response = await fetch(
`${serverUrl}/{props.endpoint}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const responseData = await response.json();
//setMessage(responseData.message);
console.log(responseData.message);
} catch (error) {
//setMessage(error.message);
console.log(error.message);
}
};
}
export default UseAPI;
The const { getAccessTokenSilently } = useAuth0(); throws me an error of:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
I don't understand -- isn't my UseAPI a function component? What am I missing?
As you are not breaking the Rules of Hooks.
There might be two common reasons because of which you might be seeing this error:
You might have mismatching versions of React and React DOM.
You might be using a version of react-dom (< 16.8.0) that doesn’t yet support Hooks. You can run
> npm ls react-dom
in your application folder to check which version you’re using.
You might have more than one copy of React in the same app.
In order for Hooks to work, the react import from your application code needs to resolve to the same module as the react import from inside the react-dom package.
If these react imports resolve to two different exports objects, you will see this warning. This may happen if you accidentally end up with two copies of the react package.
If you use Node for package management, you can run this check-in your project folder:
npm ls react
If you see more than one React, you’ll need to figure out why this happens and fix your dependency tree. For example, maybe a library you’re using incorrectly specifies react as a dependency (rather than a peer dependency). Until that library is fixed, Yarn resolutions is one possible workaround.
You can also try to debug this problem by adding some logs and restarting your development server:
// Add this in node_modules/react-dom/index.js window.React1 =
require('react');
// Add this in your component file require('react-dom'); window.React2
= require('react'); console.log(window.React1 === window.React2);
If it prints false then you might have two Reacts and need to figure out why that happened. This issue includes some common reasons encountered by the community.
This problem can also come up when you use npm link or an equivalent. In that case, your bundler might “see” two Reacts — one in application folder and one in your library folder. Assuming myapp and mylib are sibling folders, one possible fix is to run npm link ../myapp/node_modules/react from mylib. This should make the library use the application’s React copy.
Thus, running the command
npm link <path_to_local_library>/node_modules/react
might solve your issue.
Note
In general, React supports using multiple independent copies on one page (for example, if an app and a third-party widget both use it). It only breaks if require('react') resolves differently between the component and the react-dom copy it was rendered with.
Source React Doc

Jest + React Testing Library: waitFor is not working

I am writing unit tests for my React JS application using Jest and React testing library. I am trying to test the async functions. But it is not working.
This is my test
import React from 'react';
import "regenerator-runtime/runtime"
import {render, waitFor, fireEvent, screen} from '#testing-library/react';
import {DonorSelect} from "../../src/components";
import MockAdapter from "axios-mock-adapter";
import Axios from 'axios';
import vars from "../configVars";
import {searchUsersResponse} from "../mock/apiResponse";
import { act } from "react-dom/test-utils"
test ("DonorSelect Component", async () => {
let selectedUser = null;
let users = [ ];
let inputValue=""
//mock the users api endpoint
let mock = new MockAdapter(Axios);
mock.onGet(`${vars.baseApEndpoint}/users?keyword=client`)
.reply(200, searchUsersResponse)
await act(async() => await render(
<DonorSelect
id={"user_select"}
onSelect={(user, displayText) => {
selectedUser = { ...user }
}}
onInputChange={(textFieldValue) => {
inputValue = textFieldValue;
}}
onUsersFetched={(userItems) => {
users = [ ...userItems ]
}}
onChange={(name, value) => {
}}
label={"Search User"}
placeholder={"Please, select a user."}
name={"user"}
value={selectedUser!=null? selectedUser.id:""}
inputValue={inputValue}
/>
))
//assert that input is rendered
expect(screen.getByTestId("user_select_input")).toBeTruthy()
fireEvent.change(screen.getByTestId("user_select_input"), {
target: { value: "client" }
})
fireEvent.focus(screen.getByTestId("user_select_input"))
await waitFor(() => {
//assert that if the label is rendered
expect(screen.getByTestId("user_select_label")).toBeTruthy()
// assert that input is rendered
expect(screen.getByTestId("user_select_user_item_0")).toBeTruthy()
})
})
As you can see in the test what is not working is the last expect()
expect(screen.getByTestId("user_select_user_item_0")).toBeTruthy()
It is always failing. What that component is doing is that, when the input value changes and focus on the input, it will make the api request and render the items. The component is working as expected. I have fully tested it. But it is just not working in the test. What is wrong with my code and how can I fix it?
The default waitFor timeout time is 1000ms.
If you are calling a real endpoint without mocking (mocking is recommended, for example using msw), this might take more than 1 second to execute.
This will result in the timeout being exceeded and the waitFor throws an error.
IF you do not want to mock the endpoint, intercept it and return a test value, which should be under 1 sec, you could also extend the timeout time ti wait for the real api call to be executed and resolved:
await waitFor(() => {
//assert that if the label is rendered
expect(screen.getByTestId("user_select_label")).toBeTruthy()
// assert that input is rendered
expect(screen.getByTestId("user_select_user_item_0")).toBeTruthy()
}
,{timeout: 4000}) // this will now wait 4 secs for the execution, but you should see what works for you.
Based on the information here:
Testing: waitFor is not a function #8855 link
The Solution that works for me is update the library to new version:
This module is distributed via npm which is bundled with node and should be installed as one of your project's devDependencies:
npm install --save-dev #testing-library/react
or
for installation via yarn
yarn add --dev #testing-library/react
This library has peerDependencies listings for react and react-dom.
React Testing Library versions 13+ require React v18. If your project uses an older version of React, be sure to install version 12:
npm install --save-dev #testing-library/react#12
yarn add --dev #testing-library/react#12
For more information

How to initialize firebase in gatsby?

I'm stuck on making firebase work in my gatsby application that uses Redux with Redux-sagas. I know about the existence of firebase-sagas but I'm trying to make without using it.
I'm trying to init firebase auth by:
import * as firebase from 'firebase/app';
import 'firebase/auth';
export const app = firebase.initializeApp(
{
apiKey : "apiKey",
authDomain : "project.firebaseapp.com",
databaseURL : "https://project.firebaseio.com",
projectId : "project",
storageBucket : "project.appspot.com",
appId : "appId"
}
)
export const authRef = () => app.auth(); //also tried: firebase.auth() and firebase.auth(app)
//firebase.auth returns a function, but firebase.auth() throws error
I have the following config on my gatsby-node.js:
const path = require('path');
exports.onCreateWebpackConfig = ({ actions, plugins, loaders, getConfig }) => {
const config = getConfig()
config.resolve = {
...config.resolve,
mainFields: ['module', 'browser', 'main'],
alias: {
...config.resolve.alias,
['firebase/app'] : path.resolve(__dirname, 'node_modules/firebase/app/dist/index.cjs.js'),
['firebase/auth'] : path.resolve(__dirname, 'node_modules/firebase/auth/dist/index.cjs.js'),
}
}
actions.replaceWebpackConfig(config)
}
It trows the error:
{ [M [Error]: The XMLHttpRequest compatibility library was not found.]
code: 'auth/internal-error',
message: 'The XMLHttpRequest compatibility library was not found.' }
I think it's some problem related to webpack. I would love any insights on this problem :)
As Gatsby builds pages in a server environment, you can't access Firebase during Gatsby build time. Firebase calls (using the Web SDK) have to happen when the user is on a browser/client environment.
One solution to this problem is creating a function like so:
firebase.js:
import firebase from '#firebase/app';
import '#firebase/auth';
import '#firebase/firestore';
import '#firebase/functions';
const config = {
... firebase config here
};
let instance;
export default function getFirebase() {
if (typeof window !== 'undefined') {
if (instance) return instance;
instance = firebase.initializeApp(config);
return instance;
}
return null;
}
This file returns a function, which returns an instance of Firebase if the user has the global window available (e.g. on the browser). It also caches the Firebase instance to ensure it cannot be reinitialised again (in case of the user changing page on your website).
In your components, you can now do something similar to the following:
import getFirebase from './firebase';
function MyApp() {
const firebase = getFirebase();
}
As Gatsby will try to build this page into HTML during gatsby build, the firebase const will return null, which is correct, as the Firebase Web SDK cannot initialise on a server environment. However, to make use of Firebase on your website, you need to wait until Firebase is available (so the user has to have loaded your website), so we can make use of Reacts useEffect hook:
import React { useEffect } from 'react';
import getFirebase from './firebase';
function MyApp() {
const firebase = getFirebase();
useEffect(() => {
if (!firebase) return;
firebase.auth().onAuthStateChanged((user) => { ... });
}, [firebase]);
}
This works as Firebase is being used in a browser environment and has access to the browser, which is needed for the Web SDK to work.
It does have drawbacks; your compo have to return null in instances when you need Firebase to display content, which will mean your HTML build on the server will not contain any HTML, and it'll be injected via the client. In most cases though, such as an account page, this is fine.
If you need access to data from say Cloud Firestore to display page content, you're best using the Admin SDK to fetch content and add it to GraphQL during Gatsby build. That way it will be available on the server during build time.
Sorry if that was a waffle or not clear!

Web3 with Webpack Build not found without webpack-dev-server

With a Reactjs webpack project I am able to run webpack dev server and access my index.html with web3 picked up.
If I build the project and open the index.html in Chrome then web3 is not detected.
Everything works when running webpack-dev-server --mode development --open --hot
but with webpack --mode development then web3 is not injected
The purpose of my app is a tool to be run locally, it does not have to be served from anywhere public, also I don't see that I need to run a lite server to serve the content.
web3: 1.0.0-beta.36
webpack: 4.22.0
webpack-cli: 3.1.2
webpack-dev-server: 3.1.8
import './index.css';
import IxoTimelockApp from './components/IxoTimelockApp';
import InstallMetaMask from './components/install-
metamask/install-metamask-component.jsx';
let regeneratorRuntime = require("regenerator-runtime");
class App extends Component {
state = {
web3Obj:null
}
componentDidUpdate(prevprops) {
if (prevprops != this.props){
this.setState({web3Obj: this.props.web3Obj})
}
}
componentDidMount(){
window.addEventListener('load', async () => {
// Modern dapp browsers...
if (window.ethereum) {
window.web3 = new Web3(ethereum);
try {
// Request account access if needed
await ethereum.enable();
this.setState({web3Obj: window.web3})
} catch (error) {
// User denied account access...
}
}
// Legacy dapp browsers...
else if (window.web3) {
window.web3 = new Web3(web3.currentProvider);
this.setState({web3Obj: window.web3})
}
// Non-dapp browsers...
else {
console.log('Non-Ethereum browser detected. You should consider trying MetaMask!');
}
});
}
render() {
if(this.state.web3Obj) {
return <TimelockApp/>
}else return <InstallMetaMask/>
}
}
export default App;
const wrapper = document.getElementById("root");
wrapper ? ReactDOM.render(<App />, wrapper) : false;
From: MetaMask Compatibility Guide
Requirements 🔩
🌐 Http(s) - Web Server Required
Due to browser security restrictions, we can't communicate with dapps
running on file://. Please use a local server for development.

create-react-app for server-side rendering

Im using create-react-app tool for building my first react application with react-routes and now I would like to use server side rendering to avoid loading all pages at once.
I followed guides and installed express.js, separated client-side and server-side with .js files and run it with
NODE_ENV=production babel-node --presets 'react,es2015' src/server.js
But I get an error when app is trying to compile sass #import statements. I think I have to serve assets first, but I don't know how to insert webpack functions in server.js logic
create-react-app also has npm run build command for production build and create js and css files, so maybe there is some way to skip assets parts while compiling server.js ?
Server.js file contents
import path from 'path';
import { Server } from 'http';
import Express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { match, RouterContext } from 'react-router';
import routes from './routes';
import NoMatch from './pages/NoMatch';
// initialize the server and configure support for ejs templates
const app = new Express();
const server = new Server(app);
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
// define the folder that will be used for static assets
app.use(Express.static(path.join(__dirname, 'static')));
// universal routing and rendering
app.get('*', (req, res) => {
match(
{ routes, location: req.url },
(err, redirectLocation, renderProps) => {
// in case of error display the error message
if (err) {
return res.status(500).send(err.message);
}
// in case of redirect propagate the redirect to the browser
if (redirectLocation) {
return res.redirect(302, redirectLocation.pathname + redirectLocation.search);
}
// generate the React markup for the current route
let markup;
if (renderProps) {
// if the current route matched we have renderProps
markup = renderToString(<RouterContext {...renderProps}/>);
} else {
// otherwise we can render a 404 page
markup = renderToString(<NoMatch/>);
res.status(404);
}
// render the index template with the embedded React markup
return res.render('index', { markup });
}
);
});
// start the server
const port = process.env.PORT || 3000;
const env = process.env.NODE_ENV || 'production';
server.listen(port, err => {
if (err) {
return console.error(err);
}
console.info(`Server running on http://localhost:${port} [${env}]`);
});
Try installing node-sass and following the official guide
https://create-react-app.dev/docs/adding-a-sass-stylesheet/

Resources