Reusable hook for checking internet connection in React.js - javascript

For my sample application I was trying to check whether internet connection is there or not, I was using a reusable hook for this as shown below:
function useNetwork() {
const [isOnline, setNetwork] = useState(window.navigator.onLine);
const updateNetwork = () => {
setNetwork(window.navigator.onLine);
};
useEffect(() => {
window.addEventListener("offline", updateNetwork);
window.addEventListener("online", updateNetwork);
return () => {
window.removeEventListener("offline", updateNetwork);
window.removeEventListener("online", updateNetwork);
};
});
return isOnline;
}
The problem is it's working in Chrome, but when I checked in Safari when I tried turning off the wifi still isOnline returns true, I tried in the console of safari also, as window.navigator.isOnline it returns true.
Reading through different questions, here Danilo recommends to send a httpRequest and check it.
So should I send a get request to any site like google.com so I can know the status and set my onLine value as true. Because I think I need to poll this, since the above hook takes care when the dom is changed.
How can I achieve this, or is there any better way? How can I implement this in the above reusable hook.

you can do something like this,and have an api end point/or call any reliable api
const useNetworkStatus = () => {
const [isOnline, setStatus] = useState();
useEffect(() => {
(async () => {
try {
window.networkPoll = setInterval(async () => {
const networkStatus = await fetch(/*your api*/);
if (networkStatus.code != 200) {
setStatus(false);
}
if (networkStatus.code == 200) {
setStatus(true);
}
}, 5000);
}
catch (error) {
setStatus(false);
}
})();
}, []);
};

Related

Is there a reason why a promise will be undefined on Reactjs build but gets resolved on the localhost?

I have a react project setup with Redux and Axios. This is a function I am using to get data from an endpoint in my Redux actions:
export const getCSEfirstStageApplicants = () => async (dispatch) => {
try {
dispatch(LOADING());
const response = await axios.get(
`${baseUrl}/Franchisee/CSEFirstStageApplication`
);
if (response.status === 200) {
const { message, data } = response?.data || {};
return { message, data };
}
} catch (error) {
const { message } = error?.response?.data || {};
return message;
} finally {
dispatch(STOP_LOADING());
}
};
My component looks something like this:
import { useState, useEffect } from "react";
import {getCSEfirstStageApplicants} from "../../../redux/user/actions";
import { useDispatch } from "react-redux";
const MyComponent = () => {
const [cseApplicants, setCseApplicants] = useState([]);
const dispatch = useDispatch();
const getFirstStage = async () => {
const response = await dispatch(getCSEfirstStageApplicants());
if (response && response.data) {
console.log(response);
setCseApplicants(response.data);
return;
}
setCseApplicants([]);
};
useEffect(() => {
getFirstStage();
}, [dispatch]);
}
Apparently, this is working fine on my localhost. But when I build the app and push it to the server, it is giving an error on Chrome and Firefox and is working on Edge (browsers I have tested), indicating that response is undefined.
Chrome shows this error:
Firefox shows this error:
At first I thought it was the way the network call was made as preflight seemed to come after the xhr request. But checking Chrome showed that wasn't the error.
Another indication was an error that showed up as asyncgenerator error. I haven't been able to find a relation with this.
add properties to the empty object
const { message, data } = response?.data || {data:[], message:''};

bypass service worker for a specific request

I have an react app and I am using a service worker for offline purpose, I have a custom hook, this hook sends a request to check connection. If the request return a response of 200 it mean the user is online else offline. Currently I am getting a 200 ok from service worker, but the network is off. I would like to bypass the service worker and let the request run normally. The goal is to ensure the request does not return 200 while offline
here is my hook :
import { useEffect, useState } from 'react';
export const useOnline = () => {
const [online, setOnline] = useState(true);
const checkOnline = async () => {
try {
const response = await fetch('/hello.png');
setOnline(response.ok);
} catch {
setOnline(false);
}
};
useEffect(() => {
const change = () => {
setOnline(!online);
};
checkOnline();
window.addEventListener('online', () => change());
window.addEventListener('offline', () => change());
return () => {
window.removeEventListener('online', () => change());
window.removeEventListener('offline', () => change());
};
}, [online]);
return online;
};
I was thinking to add this in my service worker
self.addEventListener('fetch', function (event) {
if (event.request.url.match('^.*(/hello.png/).*$')) {
return false;
}
});
Not possible, because your hook will automaticly send 200 message.
It's a violation of service worker's goal, and your code will not work

Get axios responses in the same order as requests for search functionality

I'm currently working on a search functionality in React Native using axios.
When implementing search functionality i'm using debounce from lodash to limit the amount of requests sent.
However, since request responses are not received in same order there is a possibility of displaying incorrect search results.
For example when the user input 'Home deco' in input field there will be two requests.
One request with 'Home' and next with 'Home deco' as search query text.
If request with 'Home' takes more time to return than second request we will end up displaying results for 'Home' query text not 'Home deco'
Both results should be displayed to the user sequentially, if responses are returned in order but if 'Home' request is returned after 'Home deco' request then 'Home' response should be ignored.
Following is a example code
function Search (){
const [results, setResults] = useState([]);
const [searchText, setSearchText] = useState('');
useEffect(() => {
getSearchResultsDebounce(searchText);
}, [searchText]);
const getSearchResultsDebounce = useCallback(
_.debounce(searchText => {
getSearchResults(searchText)
}, 1000),
[]
);
function getSearchResults(searchText) {
const urlWithParams = getUrlWithParams(url, searchText);
axios.get(urlWithParams, { headers: config.headers })
.then(response => {
if (response.status === 200 && response.data)
{
setResults(response.data);
} else{
//Handle error
}
})
.catch(error => {
//Handle error
});
}
return (
<View>
<SearchComponent onTextChange={setSearchText}/>
<SearchResults results={results}/>
</View>
)
}
What is the best approach to resolve above issue?
If you want to avoid using external libraries to reduce package size, like axios-hooks, I think you would be best off using the CancelToken feature included in axios.
Using the CancelToken feature properly will also prevent any warnings from react about failing to cancel async tasks.
Axios has an excellent page explaining how to use the CancelToken feature here. I would recommend reading if you would like a better understanding of how it works and why it is useful.
Here is how I would implement the CancelToken feature in the example you gave:
OP clarified in the replies that they do not want to implement a cancelation feature, in that case I would go with a timestamp system like the following:
function Search () {
//change results to be a object with 2 properties, timestamp and value, timestamp being the time the request was issued, and value the most recent results
const [results, setResults] = useState({
timeStamp: 0,
value: [],
});
const [searchText, setSearchText] = useState('');
//create a ref which will be used to store the cancel token
const cancelToken = useRef();
//create a setSearchTextDebounced callback to debounce the search query
const setSearchTextDebounced = useCallback(
_.debounce((text) => {
setSearchText(text)
), [setSearchText]
);
//put the request inside of a useEffect hook with searchText as a dep
useEffect(() => {
//generate a timestamp at the time the request will be made
const requestTimeStamp = new Date().valueOf();
//create a new cancel token for this request, and store it inside the cancelToken ref
cancelToken.current = CancelToken.source();
//make the request
const urlWithParams = getUrlWithParams(url, searchText);
axios.get(urlWithParams, {
headers: config.headers,
//provide the cancel token in the axios request config
cancelToken: source.token
}).then(response => {
if (response.status === 200 && response.data) {
//when updating the results compare time stamps to check if this request's data is too old
setResults(currentState => {
//check if the currentState's timeStamp is newer, if so then dont update the state
if (currentState.timeStamp > requestTimeStamp) return currentState;
//if it is older then update the state
return {
timeStamp: requestTimeStamp,
value: request.data,
};
});
} else{
//Handle error
}
}).catch(error => {
//Handle error
});
//add a cleanup function which will cancel requests when the component unmounts
return () => {
if (cancelToken.current) cancelToken.current.cancel("Component Unmounted!");
};
}, [searchText]);
return (
<View>
{/* Use the setSearchTextDebounced function here instead of setSearchText. */}
<SearchComponent onTextChange={setSearchTextDebounced}/>
<SearchResults results={results.value}/>
</View>
);
}
As you can see, I also changed how the search itself gets debounced. I changed it where the searchText value itself is debounced and a useEffect hook with the search request is run when the searchText value changes. This way we can cancel previous request, run the new request, and cleanup on unmount in the same hook.
I modified my response to hopefully achieve what OP would like to happen while also including proper response cancelation on component unmount.
We can do something like this to achieve latest api response.
function search() {
...
const [timeStamp, setTimeStamp] = "";
...
function getSearchResults(searchText) {
//local variable will always have the timestamp when it was called
const reqTimeStamp = new Date().getTime();
//timestamp will update everytime the new function call has been made for searching. so will always have latest timestampe of last api call
setTimeStamp(reqTimeStamp)
axios.get(...)
.then(response => {
// so will compare reqTimeStamp with timeStamp(which is of latest api call) if matched then we have got latest api call response
if(reqTimeStamp === timeStamp) {
return result; // or do whatever you want with data
} else {
// timestamp did not match
return ;
}
})
}
}

React Native Firebase adding and getting data

wondering if anyone can assist me in this matter. I'm following the documentation for https://rnfirebase.io/firestore/usage. it does not work for my use case for some reason.
I just need to set the data, which it works and then read it back so i can push it onto my state and i'll render it.
I just can't read the data back properly. This addItemFunction is trigger when when user click on a button to add.
const addItemFunction = async (numb,exercise) =>{
firestore().collection(userEmail).get().then((snap) =>{
if(!snap.empty){
var finalID = uuid.v4();
firestore().collection(userEmail).doc(final).update({
[finalID]:{
exe:[exercise],
num:[numb],
}
}).then(() =>{
//RETURN SNAPSHOT NOT WORKING
console.log('user_added');
firestore().collection(userEmail).doc(final).onSnapshot(documentSnapshot =>{
console.log("here" + documentSnapshot.data());
});
}
Thanks for your time.
If you are using react with hooks I would suggest you put the onSnapshot listener in a useEffect hook:
useEffect(() => {
const unsubscribe = firestore
.collection(collectionName)
.doc(docId)
.onSnapshot(
(documentSnapshot) => {
const document = documentSnapshot.data();
console.log(document)
},
(error: Error) => {
throw error;
}
);
return () => unsubscribe();
}, [ docId, collectionName]);
this approach will separate concerns and the snapshots will run every time there is a change on the document, then where I put the console.log you could set the document to state.
Another approach will be to use get() instead of onSnapshot like:
const addItemFunction = async (numb,exercise) =>{
firestore().collection(userEmail).get().then((snap) =>{
if(!snap.empty){
var finalID = uuid.v4();
firestore().collection(userEmail).doc(final).update({
[finalID]:{
exe:[exercise],
num:[numb],
}
}).then(() =>{
console.log('user_added');
firestore().collection(userEmail).doc(final).get().then(() => {
console.log("here" + documentSnapshot.data());
})
}
}
}
this approach will not subscribe to changes and it will return the new updated document every time you call the addItemFunction

How to create a mock POST request to test if page has changed

I have an App.js file that contains a form that when on submitted, causes triggers a state change to render a new page. I'm trying to create a mock Jest test that does these steps:
Take mock data
Sends a POST request like addInfo is doing
Checks if "DONE WITH FORM" is rendered onto the screen.
I also had an idea that we could just fill out a form that takes in the valid_address and valid_number and click a button that triggers the addInfo function to run with the information passed in however I'm unsure of that method and it leads me to a CORS error.
From what I've seen on the web, I think mocking this addInfo using Jest and then testing what is rendered is the best way to go however I'm completely stuck on building this test.
Here's what I have for my App.js
const addInfo = async (formInfo) => {
try {
let data = {
valid_number: formInfo.validNumber,
valid_address: formInfo.validAddress
}
let addUserUrl = process.env.REACT_APP_URL +'/verify'
let addUserData = await fetch(
addUserUrl,
{
method: "POST",
headers: {
"Content-type": "application/json",
"x-api-key": process.env.REACT_APP_KEY
},
body: JSON.stringify(data)
}
)
if (addUserData.status !== 200) {
throw 'Error adding User'
}
let addUserDataJson = addUserData.json()
let ret = {
added: true,
}
return ret
} catch (error) {
console.log('Error')
let ret = {
added: false,
}
return ret
}
}
const onFinish = async (values: any) => {
console.log('Transaction verified');
let addStatus = await addInfo({
validNumber: "123434",
validAddress: "D74DS8JDSF",
})
if (promoStatus.added) {
setState({
...state,
showPage: false
})
} else {
setState({
...state,
showPage: true
})
}
};
return (
{!state.showPage &&
<>
<div>
<p>
DONE WITH FORM
</p>
<div>
</>
}
)
Here's what I've tried in App.test.js:
it('DONE WITH FORM APPEARS', async() =>{
// Render App
const { getByPlaceholderText, queryByText, getByText } = render(<App />);
// Entering Valid Number
const validNumberInputBox = getByText('Enter Valid Number);
fireEvent.change(validNumberInputBox, { target: { value: "123434" } });
expect(validNumberInputBox).toHaveValue("123434");
// Entering Valid Address
const validAddressInputBox = getByText('Enter Valid Address');
fireEvent.change(validAddressInputBox, { target: { value: "D74DS8JDSF" } });
expect(validAddressInputBox).toHaveValue("D74DS8JDSF");
// Button Click
userEvent.click(screen.getByRole('button', {name: /Submit/i}));
//Check if the DONE WITH FORM is shown
expect(await waitFor(() => getByText('DONE WITH FORM'))).toBeInTheDocument();
});
I've tried almost everything I could find through other stack overflow posts and web articles. so I'd really appreciate any help on how to implement this unit test.
The first step would be to mock the async function performing the POST request (addInfo). You never want to try real HTTP requests in unit tests (this won't work since Jest runs in a Node environment where fetch or XMLHttpRequest APIs are not implemented). Beside this, component/unit tests should be independent from any other system like a backend exposing APIs.
To do so, your async function should be in a separate file (so a JS module), then you could mock this module using Jest :
// api.js
export const addInfo = () => {...}
// App.test.js
import * as Api from 'api.js';
// here you can define what your mock will return for this test suite
const addInfoSpy = jest.spyOn(Api, 'addInfo').mockResolvedValue({ ret: true });
describe('...', () => {
test('...', async () => {
// perform user interactions that should trigger an API call
expect(addInfoSpy).toHaveBeenCalledWith('expected addInfos parameter');
// now you can test that your component displays "DONE WITH FORM" or whatever
// UI it should be displaying after a successful form submission
});
});
https://jestjs.io/docs/mock-function-api#mockfnmockresolvedvaluevalue
https://jestjs.io/docs/jest-object#jestspyonobject-methodname
https://jestjs.io/docs/expect#tohavebeencalledwitharg1-arg2-

Categories