React PWA application blank page when open it with android/ios - android

After creating a PWA with react every thing works perfectly except when i want to open the app with a mobile device android/ios....it displays a white screen with no informations.
/////
The app is deployed with IIS manager
my service **service-worker.ts
**
`
/// <reference lib="webworker" />
/* eslint-disable no-restricted-globals */
// This service worker can be customized!
// See https://developers.google.com/web/tools/workbox/modules
// for the list of available Workbox modules, or add any other
// code you'd like.
// You can also remove this file if you'd prefer not to use a
// service worker, and the Workbox build step will be skipped.
import { clientsClaim } from 'workbox-core';
import { ExpirationPlugin } from 'workbox-expiration';
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
declare const self: ServiceWorkerGlobalScope;
clientsClaim();
// Precache all of the assets generated by your build process.
// Their URLs are injected into the manifest variable below.
// This variable must be present somewhere in your service worker file,
// even if you decide not to use precaching. See https://cra.link/PWA
precacheAndRoute(self.__WB_MANIFEST);
// Set up App Shell-style routing, so that all navigation requests
// are fulfilled with your index.html shell. Learn more at
// https://developers.google.com/web/fundamentals/architecture/app-shell
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
registerRoute(
// Return false to exempt requests from being fulfilled by index.html.
({ request, url }: { request: Request; url: URL }) => {
// If this isn't a navigation, skip.
if (request.mode !== 'navigate') {
return false;
}
// If this is a URL that starts with /_, skip.
if (url.pathname.startsWith('/_')) {
return false;
}
// If this looks like a URL for a resource, because it contains
// a file extension, skip.
if (url.pathname.match(fileExtensionRegexp)) {
return false;
}
// Return true to signal that we want to use the handler.
return true;
},
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
);
// An example runtime caching route for requests that aren't handled by the
// precache, in this case same-origin .png requests like those from in public/
registerRoute(
// Add in any other file extensions or routing criteria as needed.
({ url }) =>
url.origin === self.location.origin && url.pathname.endsWith('.png'),
// Customize this strategy as needed, e.g., by changing to CacheFirst.
new StaleWhileRevalidate({
cacheName: 'images',
plugins: [
// Ensure that once this runtime cache reaches a maximum size the
// least-recently used images are removed.
new ExpirationPlugin({ maxEntries: 50 }),
],
})
);
// This allows the web app to trigger skipWaiting via
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
// Any other custom service worker logic can go here.
const CACHE_NAME = 'cache_sample';
const urlsToCache = ['index.html', 'offline.html'];
const version = 'v0.0.1';
//install sw at first time
//place to cache assets to speed up the loading time of web page
self.addEventListener('install', (event: any) => {
console.log('sw install event');
event.waitUntil(
caches.open(version + CACHE_NAME).then((cache) => {
console.log('opened cache');
return cache.addAll(urlsToCache);
})
);
});
//Activate the sw after install
//Place where old caches are cleared
self.addEventListener('activate', (event: any) => {
console.log('sw activate event');
event.waitUntil(
caches.keys().then((cacheNames) =>
Promise.all(
cacheNames
.filter((cacheName) => {
return cacheName.indexOf(version) !== 0;
})
.map(function (cachName) {
return caches.delete(cachName);
})
)
)
);
});
//listen for requests
self.addEventListener('fetch', (event: any) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
// Any other custom service worker logic can go here.
`
PS : My start_url : "/" in the manifest.json and i've already set the basename attribute in my Router

Related

Create a custom notification with Actions Buttons

import BackgroundService from 'react-native-background-actions';
const sleep = (time) => new Promise((resolve) => setTimeout(() => resolve(), time));
// You can do anything in your task such as network requests, timers and so on,
// as long as it doesn't touch UI. Once your task completes (i.e. the promise is resolved),
// React Native will go into "paused" mode (unless there are other tasks running,
// or there is a foreground app).
const veryIntensiveTask = async (taskDataArguments) => {
// Example of an infinite loop task
const { delay } = taskDataArguments;
await new Promise( async (resolve) => {
for (let i = 0; BackgroundService.isRunning(); i++) {
console.log(i);
await sleep(delay);
}
});
};
const options = {
taskName: 'Example',
taskTitle: 'ExampleTask title',
taskDesc: 'ExampleTask description',
taskIcon: {
name: 'ic_launcher',
type: 'mipmap',
},
color: '#ff00ff',
linkingURI: 'yourSchemeHere://chat/jane', // See Deep Linking for more info
parameters: {
delay: 1000,
},
};
await BackgroundService.start(veryIntensiveTask, options);
await BackgroundService.updateNotification({taskDesc: 'New ExampleTask description'}); // Only Android, iOS will ignore this call
// iOS will also run everything here in the background until .stop() is called
await BackgroundService.stop();
I am using react-native-background-actions. I need 2 buttons in this. But I don’t have any good experience with native code in react native so let me know how can I add custom buttons In it and changes buttons according condition. How can I make changes in the native android for notification buttons.

React PWA detect new content on iOS is not working as expected

I use CRA PWA and add toast that's show when onupdatefound and onstatechange. it's working well in android and pc but in iOS it's not shown and content not refresh till close tab and open it again.
I searching long time and not found any good answer for this issue.
I have ServiceWorkerWrapper.ts
import React, { FC, useEffect } from 'react';
import { Snackbar, Button } from '#material-ui/core';
import * as serviceWorker from '../serviceWorkerRegistration';
const ServiceWorkerWrapper: FC = () => {
const [showReload, setShowReload] = React.useState(false);
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
let timer:any;
if (counter >= 0 && showReload) {
setTimeout(() => setCounter(counter - 1), 1000)
} else if (showReload) {
reloadPage();
}
return () => clearTimeout(timer);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [counter]);
const onSWUpdate = (registration: ServiceWorkerRegistration) => {
if (registration && registration.waiting) {
setShowReload(true);
setCounter(2)
} else {
setShowReload(false);
}
};
useEffect(() => {
serviceWorker.register({ onUpdate: onSWUpdate });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const reloadPage = () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.getRegistration()
.then((reg:any) => {
reg.waiting.postMessage({ type: 'SKIP_WAITING' });
window.location.reload();
})
.catch((err) => console.log('Could not get registration: ', err));
}
};
return (
<Snackbar
open={showReload}
message="A new version is available!"
onClick={reloadPage}
ContentProps={{
className: "reload-snackbar"
}}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
action={
<Button
color="inherit"
size="small"
onClick={reloadPage}
>
Reload{showReload ? ` (${counter + 1})`:''}
</Button>
}
/>
);
}
export default ServiceWorkerWrapper;
serviceWorkerRegistration.ts
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://cra.link/PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
);
type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void;
onUpdate?: (registration: ServiceWorkerRegistration) => void;
};
export function register(config?: 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
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
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, visit https://cra.link/PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
if (navigator.vendor === 'Apple Computer, Inc.') {
console.log('Safari!!!!');
if (registration.waiting) {
if (config && config.onUpdate) {
config.onUpdate(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.'
);
// 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: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.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();
})
.catch((error) => {
console.error(error.message);
});
}
}
service-worker.ts
/// <reference lib="webworker" />
/* eslint-disable no-restricted-globals */
// This service worker can be customized!
// See https://developers.google.com/web/tools/workbox/modules
// for the list of available Workbox modules, or add any other
// code you'd like.
// You can also remove this file if you'd prefer not to use a
// service worker, and the Workbox build step will be skipped.
import { clientsClaim } from 'workbox-core';
import { ExpirationPlugin } from 'workbox-expiration';
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
declare const self: ServiceWorkerGlobalScope;
clientsClaim();
// Precache all of the assets generated by your build process.
// Their URLs are injected into the manifest variable below.
// This variable must be present somewhere in your service worker file,
// even if you decide not to use precaching. See https://cra.link/PWA
precacheAndRoute(self.__WB_MANIFEST);
// Set up App Shell-style routing, so that all navigation requests
// are fulfilled with your index.html shell. Learn more at
// https://developers.google.com/web/fundamentals/architecture/app-shell
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
registerRoute(
// Return false to exempt requests from being fulfilled by index.html.
({ request, url }: { request: Request; url: URL }) => {
// If this isn't a navigation, skip.
if (request.mode !== 'navigate') {
return false;
}
// If this is a URL that starts with /_, skip.
if (url.pathname.startsWith('/_')) {
return false;
}
// If this looks like a URL for a resource, because it contains
// a file extension, skip.
if (url.pathname.match(fileExtensionRegexp)) {
return false;
}
// Return true to signal that we want to use the handler.
return true;
},
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
);
// An example runtime caching route for requests that aren't handled by the
// precache, in this case same-origin .png requests like those from in public/
registerRoute(
// Add in any other file extensions or routing criteria as needed.
({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'),
// Customize this strategy as needed, e.g., by changing to CacheFirst.
new StaleWhileRevalidate({
cacheName: 'images',
plugins: [
// Ensure that once this runtime cache reaches a maximum size the
// least-recently used images are removed.
new ExpirationPlugin({ maxEntries: 50 }),
],
})
);
// This allows the web app to trigger skipWaiting via
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
// Any other custom service worker logic can go here.

Appstate keep on getting change in React native in Android

I am working on React native project and there I am taking location permissions. Also I have to track location permissions always like if user has given permission access after install the application and then after sometime user goes to the app settings in device settings and disable/revoked the permissions. Again once app comes from background to foreground, I have to check permission based on that, Needs to show the messages.
So that, I am using Appstate. But, In Android strangely, After installed the application, If user denied the permission with "Dont show again" checkbox, Then Appstate getting keep on changing with background and active always.
It is keep on loop.
componentDidMount = async () => {
AppState.addEventListener('change', this.handleAppStateChange);
};
componentWillUnmount() {
AppState.removeEventListener('change', this.handleAppStateChange);
Geolocation.clearWatch(this.watchID);
}
handleAppStateChange = async nextAppState => {
const {appState} = this.state;
console.log('nextAppState -->', nextAppState);
console.log('appState -->', appState);
if (appState === 'active') {
// do this
this.showLoader();
await this.requestAndroidLocationPermission();
} else if (appState === 'background') {
// do that
} else if (appState === 'inactive') {
// do that other thing
}
this.setState({appState: nextAppState});
};
requestAndroidLocationPermission = async () => {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{},
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
this.getLatitudeLongitude();
} else if (granted === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) {
this.hideLoader();
this.setState({
errorMessage: 'Location permission is denied',
isLoading: false,
});
} else {
this.hideLoader();
this.requestAndroidLocationPermission();
}
} catch (err) {
console.warn(err);
}
};
It is keep on printing (loop) after denied permission with Don't show again
appState --> active
nextAppState --> background
appState --> active
nextAppState --> background
appState --> active
nextAppState --> background
appState --> active
It goes on and never stop.
How to handle this? Any suggestions?
I had the same problem. Do not use AppState. Is faulty.
the problem lies within RN's definition of "background". react-native uses android's activity (the holder of the UI thread and where your UI lives) onPause callback as the trigger for sending the "background" signal. But, onPause is called everytime SOMETHING comes in front of your activity's view hierachy, like Dialogs (like the permission box), other activities (like a file picker), etc; for android react-native, "background" means "shadowed by a foreign UI element/android task" rather than "paused and sent to background to do something else", thus causing the loops you see. The shortest solution is to override onPause in your ReactActivity, and add control conditions to make sure super.onPause is only called when you are actually going to background, like checking your task stack, or if the permission dialog is being called, so you avoid this kind of loop/faulty call. A second option would be to provide your own app lifecycle event instead, with clear triggering conditions.
today I had a similar problem.
I could solve it using "focus" in android and "change" in ios.
I have a custom hook like this:
import { useEffect } from 'react';
import { AppState, Platform } from 'react-native';
const focusEvent = Platform.OS === 'ios' ? 'focus' : 'change';
const useLocationListener = () => {
useEffect(() => {
AppState.addEventListener(focusEvent, handleAppStateChange);
getLocationAsync();
return () => {
AppState.removeEventListener(focusEvent, handleAppStateChange);
};
}, []);
const handleAppStateChange = (nextAppState: string) => {
if (nextAppState === 'active') {
getLocationAsync();
}
};
const getLocationAsync = async () => {
const { canAskAgain, status } = await Permissions.getAsync(
Permissions.LOCATION
);
if (canAskAgain) {
const response = await Permissions.askAsync(Permissions.LOCATION);
// handle location
}
// handle location with "status"
};
};
export default useLocationListener;
You can use a flag that check whether app should handle background or it's just a permission call.
const shouldHandleBackground = useRef(true)
const handler = (state) => {
if (state === 'active' && shouldHandleBackground.current) {
doStuff()
}
}
// when calling for permisson make the flag false
shouldHandleBackground.current = false
await Android.permission.LocationPermission()
shouldHandleBackground.current = true
and after permission request you can make flag true

Linking.getInitialURL() is not being cleared after used for deeplink

I've had this problem for like 2 weeks. I used Wix's Navigation for navigating around the app. I followed this tutorial for implementing the deeplink/universal link.
I have a base class called BaseScreen where I keep all the deeplink handler like in the tutorial. This BaseScreen would looks like this:
componentDidMount(){
// this handles the case where the app is closed and is launched via Universal Linking.
Linking.getInitialURL()
.then((url) => {
if (url) {
// Alert.alert('GET INIT URL','initial url ' + url)
this.resetStackToProperRoute(url)
}
})
.catch((e) => {})
// This listener handles the case where the app is woken up from the Universal or Deep Linking
Linking.addEventListener('url', this.appWokeUp);
}
componentWillUnmount(){
// Remove the listener
Linking.removeEventListener('url', this.appWokeUp);
}
appWokeUp = (event) => {
// this handles the use case where the app is running in the background and is activated by the listener...
// Alert.alert('Linking Listener','url ' + event.url)
this.resetStackToProperRoute(event.url)
}
resetStackToProperRoute = (url) => {
// grab the trailing portion of the url so we can use that data to fetch proper information from the server
let trailing = url.slice(url.lastIndexOf('=') + 1, url.length)
// go to the desired screen with the trailing token grabbed from the url
this.props.navigator.resetTo({
screen: 'NewPassword',
overrideBackPress: true,
passProps: {
token: trailing
},
animated: true,
animationType: 'fade',
navigatorStyle: {
navBarHidden: true,
}
})
}
When the app launch, it'll show the screen LoginScreen which extends the BaseScreen above. After killing the app, click the url from the mail, the app launches LoginScreen first, then it'll redirect to the screen NewPassword, and after everything has done, I'll redirect back to LoginScreen by:
this.props.navigator.resetTo({
screen: 'LoginScreen',
animated: true,
overrideBackPress: true,
animationType: 'fade',
navigatorStyle: {
navBarHidden: true,
}
})
But the Linking.getInitialURL() of the LoginScreen still receive the old url, so it'll redirect to NewPassword again, and it's a loop.
I've also tried to pass: passProps: {} option when resetTo the LoginScreen but no luck.
I guess the only way to fix it is to clear the initialUrl manually after everything's done in NewPassword screen. The listener for the BaseScreen should be there because if I don't kill the app (just minimize it), the listener should be running to navigate to NewPassword.
Wix's navigation has a doc for Deeplink, I tried putting method onNavigatorEvent(event) into the BaseScreen but it doesn't get called. I don't know if I miss something.
Thank you for your time. Any idea would be appreciated
Linking.getInitialURL() gives us the same Url when we come back to the same page again, to Overcome this we can do a simple condition of not to call the DeepLink function. Something like...
Step 1: First init a dummyDeepLinkedUrl String .
var dummyDeepLinkedUrl;
Step 2: Check for the condition like, if deeplinkUrl is coming from Linking.getInitialURL() and deeplinkUrl is not equal to the dummyDeepLinkedUrl .
if (url && url != dummyDeepLinkedUrl) {}
Step 3: If not same call the Deeplink Function and assign the deeplinkUrl to dummyDeepLinkedUrl.
this.navigateToRespectivePage(url);
dummyDeepLinkedUrl = url;
Finally this will look like :
Linking.getInitialURL().then(url => {
if (url && url != dummyDeepLinkedUrl) {
this.navigateToRespectivePage(url);
dummyDeepLinkedUrl = url;
}
});
There are two ways to handle URLs that open your app.
If the app is already open, the app is foregrounded and a Linking event is fired You can handle these events with
Linking.addEventListener(url, callback).
If the app is not already open, it is opened and the url is passed in as the initialURL You can handle these events with
Linking.getInitialURL(url) -- it returns a Promise that resolves to
the url, if there is one.
You can read more detail
Here is the example
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
initialised: false
}
}
componentDidMount() {
AppState.addEventListener('change', this._handleAppStateChange);
Linking.addEventListener('url', event => {
console.log('deep link from background', event.url)
})
}
_handleAppStateChange = async (nextAppState) => {
const url = await Linking.getInitialURL();
if (url !== null && !this.state.initialised) {
this.setState({ initialised: true })
console.log('deep link from init app', url)
}
}
componentWillUnmount() {
AppState.removeEventListener('change', this._handleAppStateChange);
Linking.removeEventListener('url')
}
}

React native How to fetch from api every x minutes

I want to run fetch every x Minutes on android (using React native)
function getMoviesFromApiAsync() {
return fetch('https://facebook.github.io/react-native/movies.json').then((response) => response.json()) .then((responseJson) => {
return responseJson.movies; }) .catch((error) => { console.error(error); });
}
I went to use example from documentaion as i am not still sure how would i do this kind of fetch.
Im not sure on how should i even start this(Make a android native Handler maybe?). I only found a component but that is for ios iOS Background Fetch API Implementation
componentDidMount(){
this.timer = setInterval(()=> this.getMovies(), 1000)
}
async getMovies(){
fetch('https://facebook.github.io/react-native/movies.json', {method: "GET"})
.then((response) => response.json())
.then((responseData) =>
{
//set your data here
console.log(responseData);
})
.catch((error) => {
console.error(error);
});
}
If you want run your service in background then there is only one option you have that is headless js https://facebook.github.io/react-native/docs/headless-js-android.html or npm based on headless js like https://github.com/jamesisaac/react-native-background-task
headless base service can have minimum periodic frequency 15 not less than that
if need to run service lesser than that eg 5 minutes then you can use combination of headless js and setinterval like
BackgroundTask.define(async () => { //headless based runs every 15 minute
console.log('headless js service start')
this._interval = setInterval(() => {
console.log('setinterval for 5 minute start')
// Your code
}, 300000);
BackgroundTask.finish()
})
then inside your any component method like componentwillmount you need to schedule background task like
componentDidMount() {
BackgroundTask.schedule({
period: 900, // Aim to run every 15mins
})
}
you can use setinterval alone without headless JS but will be stop by android to save battery/resource
Note : to test these things you have to use real device as physical device go in doze mode or do things like stop background app to save battery/resource unlike emulators
I'm a little late to the party but I had to do something similar. I was making an API request to a server that fetches a bunch of locations, does some calculations and passes the data to a callback function in another part of the app to update a Map. I wanted this to run every "x" seconds. This is what I did:
I created a custom hook that I call from the map screen. I had two states I wanted to return (businessLocations, error). I created a timer at the top of the file that looks like this:
const [render, setRender] = useState(false);
useEffect(() => {
setTimeout(() => {
setRender(!render);
}, 20000);
}, [isScreenFocused, render]);
This was recreated every time the screen was focused or the render state changed. As you can see I trigger the render state to change every time 20 seconds is up causing the timer to re-start.
Below the useEffect call I created a second useEffect call that looks like this:
const fetching = useRef(false);
useEffect(() => {
const searchForLocations = async () => {
fetching.current = true;
await requestAllLocations()
.then((locations) => {
const results = locations.map(loc => {
return createBusinessFromAPI(loc);
});
setBusinessLocations(results);
locationsCallback(results);
fetching.current = false;
}).catch((error) => {
fetching.current = false;
throw error;
});
};
if (!fetching.current) {
searchForLocations().catch(error => setError(error));
}
}, [isScreenFocused, render]);
As you can see here, I use the hook useRef (which doesn't change from render to render) to determine if the last request is still processing. If it is, I don't call searchForLocations() on this render cycle and simply wait for the next one. You can also see that this entire useEffect call is triggered when the timer is up because I use the render state as a dependency here too.
It may not be a perfect solution. I'm currently trying to refine it a little. But, it has worked for me so far.
Here is the entire file:
import React, {useEffect, useRef, useState} from 'react';
import {requestAllLocations} from '../api/requests';
import {createBusinessFromAPI} from '../model/BusinessCreator';
export default (isScreenFocused, locationsCallback) => {
const [businessLocations, setBusinessLocations] = useState([]);
const [error, setError] = useState(null);
const [render, setRender] = useState(false);
const fetching = useRef(false);
useEffect(() => {
setTimeout(() => {
setRender(!render);
}, 20000);
}, [isScreenFocused, render]);
useEffect(() => {
const searchForLocations = async () => {
fetching.current = true;
await requestAllLocations()
.then((locations) => {
const results = locations.map(loc => {
return createBusinessFromAPI(loc);
});
setBusinessLocations(results);
locationsCallback(results);
fetching.current = false;
}).catch((error) => {
fetching.current = false;
throw error;
});
};
if (!fetching.current) {
searchForLocations().catch(error => setError(error));
}
}, [isScreenFocused, render]);
return [businessLocations, error];
};
You have to set the time on that place where you get callback .
ComponentWillMount() {
AsyncStorage.getItem('accessToken').then((token) => {
this.setState({
isLoading: false,
time :5000
});
});
},
You can make a AsyncTask(background thread) where it gets the json from your url and call it in a Timer loop like so:
Asynctask:
private class FetchAPI extends AsyncTask<String, String, String> {
BufferedReader reader;
HttpURLConnection conn;
#Override
protected String doInBackground(String... params) {
String data = null;
try {
// Defined URL where to send data
URL url = new URL(YOURURL);
conn = (HttpURLConnection) url.openConnection();
conn.connect();
// Get the server response
reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
// conn.disconnect();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
protected void onPostExecute(String result) {
try {
StringBuilder sb = new StringBuilder();
String line = null;
while((line = reader.readLine()) != null)
{
sb.append(line + "\n");
}
String serverResponse = sb.toString(); //Your JSON
} catch (IOException e) {
e.printStackTrace();
}
}
}
Your timer segment:
new Timer().schedule(new TimerTask() {
#Override
public void run() {
// do your task here
FetchAPI fetchAPI = new FetchAPI();
fetchAPI.execute("");
}
}, 0, 5000);
This code will call the FetchAPI method which uses the background thread to fetch the json from your URL. When you've gotten the JSON you can do what you want with it. The method gets called every 5 seconds. Edit the '5000'(interval in miliseconds) to your needs
I hope this can help you and/or steer you in the right direction

Categories

Resources