Is there any way to abort a fetch request on react-native app ?
class MyComponent extends React.Component {
state = { data: null };
componentDidMount = () =>
fetch('http://www.example.com')
.then(data => this.setState({ data }))
.catch(error => {
throw error;
});
cancelRequest = () => {
//???
};
render = () => <div>{this.state.data ? this.state.data : 'loading'}</div>;
}
i tried the abort function from AbortController class but it's not working !!
...
abortController = new window.AbortController();
cancelRequest = () => this.abortController.abort();
componentDidMount = () =>
fetch('http://www.example.com', { signal: this.abortController.signal })
....
Any help please !
You don't need any polyfill anymore for abort a request in React Native 0.60 changelog
Here is a quick example from the doc of react-native:
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* #format
* #flow
*/
'use strict';
const React = require('react');
const {Alert, Button, View} = require('react-native');
class XHRExampleAbortController extends React.Component<{}, {}> {
_timeout: any;
_submit(abortDelay) {
clearTimeout(this._timeout);
// eslint-disable-next-line no-undef
const abortController = new AbortController();
fetch('https://facebook.github.io/react-native/', {
signal: abortController.signal,
})
.then(res => res.text())
.then(res => Alert.alert(res))
.catch(err => Alert.alert(err.message));
this._timeout = setTimeout(() => {
abortController.abort();
}, abortDelay);
}
componentWillUnmount() {
clearTimeout(this._timeout);
}
render() {
return (
<View>
<Button
title="Abort before response"
onPress={() => {
this._submit(0);
}}
/>
<Button
title="Abort after response"
onPress={() => {
this._submit(5000);
}}
/>
</View>
);
}
}
module.exports = XHRExampleAbortController;
I've written quite a bit actually about this subject.
You can also find the first issue about the OLD lack of AbortController in React Native opened by me here
The support landed in RN 0.60.0 and you can find on my blog an article about this and another one that will give you a simple code to get you started on making abortable requests (and more) in React Native too. It also implements a little polyfill for non supporting envs (RN < 0.60 for example).
You can Actually achieve this by installing this polyfill abortcontroller-polyfill
Here is a quick example of cancelling requests:
import React from 'react';
import { Button, View, Text } from 'react-native';
import 'abortcontroller-polyfill';
export default class HomeScreen extends React.Component {
state = { todos: [] };
controller = new AbortController();
doStuff = () => {
fetch('https://jsonplaceholder.typicode.com/todos',{
signal: this.controller.signal
})
.then(res => res.json())
.then(todos => {
alert('done');
this.setState({ todos })
})
.catch(e => alert(e.message));
alert('calling cancel');
this.controller.abort()
}
render(){
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
<Button title="Do stuff" onPress={() => { this.doStuff(); }} />
</View>
)
}
}
So basically in this example, once you click the 'doStuff' button, the request is immediately cancelled and you never get the 'done' alert. To be sure, it works, try and comment out these lines and click the button again:
alert('calling cancel');
this.controller.abort()
This time you will get the 'done' alert.
This is a simple example of hoe you can cancel a request using fetch in react native, feel free to adopt this to your own use case.
Here is a link to a demo on snackexpo https://snack.expo.io/#mazinoukah/fetch-cancel-request
hope it helps :)
the best solution is using rxjs observables + axios/fetch instead of promises, abort a request => unsubscribe an observable :
import Axios from "axios";
import {
Observable
} from "rxjs";
export default class HomeScreen extends React.Component {
subs = null;
doStuff = () => {
let observable$ = Observable.create(observer => {
Axios.get('https://jsonplaceholder.typicode.com/todos', {}, {})
.then(response => {
observer.next(response.data);
observer.complete();
})
});
this.subs = observable$.subscribe({
next: data => console.log('[data] => ', data),
complete: data => console.log('[complete]'),
});
}
cancel = () =>
if (this.subs) this.subs.unsubscribe()
componentWillUnmount() {
if (this.subs) this.subs.unsubscribe();
}
}
That is it :)
Related
I'm new to react native and in my sample app I can't handle properly results returned from ImagePicker of this component https://github.com/react-native-image-picker/react-native-image-picker
I'm running react 0.65 and below is my code:
import * as ImagePicker from 'react-native-image-picker';
export class App extends Component {
constructor() {
super();
this.state = { imageSource: null };
}
selectImage = () => {
const selectImageFromGallery = async () => {
const response = await ImagePicker.launchImageLibrary('library', {
selectionLimit: 1,
mediaType: 'photo',
includeBase64: true,
});
const {img64base} = response.assets[0];
this.setState({img64base});
};
selectImageFromGallery();
// console.log(resp);
}
render() {
return (
<SafeAreaView style={{justifyContent: 'center', alignItems: 'center'}}>
<Button title='Select from gallery' onPress={() => this.selectImage()} />
<Image source={this.state.imageSource} />
</SafeAreaView>
);
};
}
Upon run of application I can press button and select image, but whenever I confirm my selection it is throwing error in console and on the screen of Android device:
Uncaught Error:
'Type Error: callback is not a function'
This call stack is not sybmolicated.
I do understand that I miss to handle correctly promise or callback but I cant figure out correct syntax. Could you please help? Tried zillion of times with 'await', without await, etc. The only thing I need to stay with component class and I won't change to function class - have single calls in componentDidMount functions to make sure some specific hardware is called only once. Pease help
selectImage = async () {
const response = await ImagePicker.launchImageLibrary('library', {
selectionLimit: 1,
mediaType: 'photo',
includeBase64: true,
});
const {img64base} = response.assets[0];
this.setState({img64base});
}
I'm building an android app using React-native and using PermissionsAndroid from react-native to get user permission.
import {PermissionsAndroid} from 'react-native'
Now i'm writing unit test and i need to verify the component behaviour based on the Permission.
hence i need to mock PermissionsAndroid.
Is there a way to do this?
jest.mock('react-native//Libraries/PermissionsAndroid/PermissionsAndroid', () => {
const PermissionsAndroid = jest.requireActual(
'react-native//Libraries/PermissionsAndroid/PermissionsAndroid',
);
console.log(PermissionsAndroid);
return {
...PermissionsAndroid,
check: jest.fn(() => new Promise(resolve => resolve(true))),
request: jest.fn(() => new Promise(resolve => resolve(true))),
};
});
This worked for me in 2022
jest.mock('react-
native//Libraries/PermissionsAndroid/PermissionsAndroid', () => {
return {
...jest.requireActual('react- native//Libraries/PermissionsAndroid/PermissionsAndroid'),
request: jest.fn(() => new Promise(resolve => resolve('granted')))
}
})
Because it is async you have to later on await it e.g. with react testing library const element = await findByText(...)
Simply mocking
jest.doMock('react-native', () => ({ PermissionsAndroid: {... did not work for me. Here is how I got it to work specifically mocking requestMultiple and check.
let fineLocationPermissionResult: String = PermissionsAndroid.RESULTS.GRANTED;
let courseLocationPermissionResult: String = PermissionsAndroid.RESULTS.GRANTED;
let fineLocationPermissionGranted = true;
let coarseLocationPermissionGranted = true;
const permissionsAndroidModule = jest.requireActual('react-native/Libraries/PermissionsAndroid/PermissionsAndroid.js');
jest.doMock('react-native/Libraries/PermissionsAndroid/PermissionsAndroid', () => ({
...permissionsAndroidModule,
requestMultiple: () => {
return {
[PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION]: fineLocationPermissionResult,
[PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION]: courseLocationPermissionResult,
};
},
check: () => {
return fineLocationPermissionGranted && coarseLocationPermissionGranted;
},
}));
I've included some of the variables I used in my tests to manipulate the results of the mock but essentially you need to mock the entire module path ('react-native/Libraries/PermissionsAndroid/PermissionsAndroid') and then include the rest of the module that you are not mocking via jest.requireActual.
Solution below:
jest.mock(
'react-native//Libraries/PermissionsAndroid/PermissionsAndroid',
() => ({
PermissionsAndroid: {
request: () => {
true;
},
check: () => {
true;
},
},
})
);
You can mock this from react-native directly, just like:
jest.doMock('react-native', () => ({
PermissionsAndroid: {
request: (permission: string) => {
//whatever you want
},
},
}))
Notice that you might see some issues with the components you are using for that unit test, i.e. it might show an error if you are using <View> from React Native and not mocking it. Given that case, you have to import <View> and then include it in your mock.
import { View } from 'react-native'
...
jest.doMock('react-native', () => ({
View,
PermissionsAndroid: {
request: (permission: string) => {
//whatever you want
},
},
}))
I want to send sms to multiple numbers without opening to default messaging app.
I try to use react-native-sms-x but its not maintained and my project just stuck at compiling.
Also I used react-native-sms but it open default Messaging App filled with one user number and message body and had to click send button of it too.
import { Linking,Platform } from "react-native";
const url = (Platform.OS === 'android')
? 'sms:919999999999?body=your message'
: 'sms:919999999999'
Linking.canOpenURL(url).then(supported => {
if (!supported) {
console.log('Unsupported url: ' + url)
} else {
return Linking.openURL(url)
}
}).catch(err => console.error('An error occurred', err))
After a lot of research and trials in the react app...
I have found this library working fine and reached the goals to send a message without going into the default message environment.
var phoneNumbers = {
"addressList": ["+911212121212", "+911212121212"]
};
var message = "This is automated test message"
SmsAndroid.autoSend(
phoneNumbers,
message,
(fail) => {
console.log('Failed with this error: ' + fail);
},
(success) => {
console.log('SMS sent successfully');
},
);
I hope it helps you. Do not forget to upvote
From Now Just For Android I use react-native-sms-android
Here is my Code for Sending sms to multiple users:
import Asms from "react-native-sms-android";
type Props = {};
export default class App extends Component<Props> {
constructor(Props) {
super(Props);
this.state = { FileNumbers: ['687867867867','8575774433'], Message:
"gjjgjgj" };
}
sendingSms = (Receivers, Messagex) => {
try {
Receivers.map(
async Numbers =>
await Asms.sms(Numbers, Messagex, "sendDirect", (err,message)
=> {
if (err) {
console.log(err);
} else {
console.log(message);
}
})
);
} catch (e) {
alert("" + e);
}
};
render() {
return (
<View style={styles.container}>
<TextInput
style={{
height: 40,
borderColor: "gray",
borderWidth: 1,
width: "90%"
}}
onChangeText={Message => this.setState({ Message })}
value={this.state.Message}
/>
<Button
title="SEND"
onPress={() =>
this.sendingSms(this.state.FileNumbers, this.state.Message)
}
/>
</View>
);
}
}
Fairly new to React native and its concepts. I have been playing with RN for a while to create an application to fetch API data from
http://jsonplaceholder.typicode.com/photos
I have been looking into the documentation of AsyncStorage to implement how i can cache the API data so that upon terminating the application, it doesn't have to deal with fetching the data from web again and again, but wasn't successfully able to implement it.
It will be great if you can provide me help/suggestion based on it. I have included my source code for the 2 important files in my application, along with the a Test.js file with how i was trying to achieve.
import React, {Component} from 'react';
import { FlatList, View, Text, AsyncStorage, ActivityIndicator } from 'react-native';
import axios from 'axios';
import GalleryDetail from './GalleryDetail';
class GalleryList extends Component {
state = { photos: []};
componentDidMount() {
axios.get('http://jsonplaceholder.typicode.com/photos')
.then(response => this.setState({ photos: response.data }))
.catch((error)=> console.warn("fetch Error: ", error));
}
getPhotos = async()=> {
try {
photos = await AsyncStorage.getItem('GalleryPhotos');
}
catch (error) {
console.error(error);
}
}
savePhotos(){
AsyncStorage.setItem('GalleryPhotos', this.state.photos);
console.log('works !');
}
renderPhoto = ({item})=> {
return <GalleryDetail photo={item}/>
}
keyExtractor = (photo, index) => photo.id;
render () {
if(!this.state.photos){
return <ActivityIndicator/>;
}
return (
<FlatList
data = {this.state.photos}
keyExtractor={this.keyExtractor}
renderItem={this.renderPhoto}
/>
);
}
}
export default GalleryList;
and GalleryDetail linked with GalleryList-
import React, {Component} from 'react';
import { Text, View, Image } from 'react-native';
import Card from './Card';
import CardSection from './CardSection';
const GalleryDetail = (props)=> {
return (
<Card>
<CardSection style = {styles.headerContentStyle}>
<Image
style={styles.thumbnailStyle}
source = {{ uri: props.photo.thumbnailUrl}}/>
<Text style= {styles.textStyle}>{props.photo.title} </Text>
</CardSection>
</Card>
);
};
const styles = {
headerContentStyle: {
flexDirection: 'column',
justifyContent: 'space-around'
},
thumbnailStyle: {
height: 60,
width: 60
},
textStyle: {
fontSize: 12,
//textAlign: 'right',
flexDirection: 'row',
justifyContent: 'flex-end',
flex: 1,
flexWrap: 'wrap',
marginLeft: 5,
marginRight: 5,
}
}
export default GalleryDetail;
My method of trying was that-
Upon launching the application, it will first look in asyncStorage, if it finds the data- it fetches from async otherwise going to the web,fetching and storing again for later use.
I tried to implement somewhat like this in a separate file since i dint wanted to breakdown my already running app. The weird broken syntax is
State = {
photos: []
}
componentDidMount() {
// just a variable acting to fetch data from the stored keyvalue pair
check = AsyncStorage.getItem("PhotosKey").then((response) => {
this.setState({"PhotosKey": response});
}).done();
if(check) {
console.log('Data was fetched!!!!!');
check();
}
else {
console.log("Data was not fetched!");
var Data = axios.get('http://jsonplaceholder.typicode.com/photos').
then(response => this.setState({ photos: response.data })).
catch((error)=> console.warn("fetch Error: ", error));
}
}
Thanks in advance!
async componentDidMount() {
const photoStorage = await AsyncStorage.getItem('GalleryPhotos')
if(photoStorage) {
try {
const photoResp = await axios.get('http://jsonplaceholder.typicode.com/photos')
const photoData = await JSON.stringify(photoResp.data)
await AsyncStorage.setItem('GalleryPhotos', photoData);
} catch(e) {
console.warn("fetch Error: ", error)
}
.then(response => this.setState({ photos: response.data }))
}
}
later
getPhotos = async()=> {
try {
photos = JSON.parse(await AsyncStorage.getItem('GalleryPhotos'));
}
catch (error) {
console.error(error);
}
}
The approach from Subramanya is basically all you need to get started, I'm just going to introduce a state management approach with redux-persist where you can definitely appreciate when your app grows.
Redux Persist is performant, easy to implement, and easy to extend.
Let say you have your app hooked up with redux and implemented a fairly organised state tree, redux-persist stores the entire app state with AsyncStorage or any storage engine of your choice.
For instance, let's assume that your API endpoint returns a collection of photos, all you need to do is update the store, and your users can expect their data is safe and saved with redux-persist.
I have not tested all the code below
Let's define the store first,
import { AsyncStorage } from 'react-native';
import { createStore, compose, applyMiddleware, } from "redux";
import { persistStore } from "redux-persist";
import ReduxThunk from "redux-thunk";
import reducers from "../reducers"
const middleWare = [ReduxThunk]
const store = createStore(
reducers,
{},
compose(applyMiddleware(...middleWare))
)
// you can define more parameters, like blacklist or whitelist a reducer
// also, specify storage engine
persistStore(store, { storage: AsyncStorage });
export default store;
At your app's entry point,
import React, { Component } from "react";
import { Provider } from "react-redux";
import Router from "./Router";
import store from './store';
export default class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<Provider store={store}>
<Router /> // navigator
</Provider>
);
}
}
Finally, your API logic.
// action creator
export storePhoto = photos => {
return {
type: 'STORE_PHOTOS',
payload: photos
}
}
// photos reducer
import { REHYDRATE } from 'redux-persist/constants';
export default (state = {}, action) => {
switch (action.type) {
case STORE_PHOTOS:
return { ...state, photos: action.payload }
// this is where `redux-persist` handles caching
case REHYDRATE:
var incoming = action.payload;
if(incoming) return { ...state, ...incoming }
return state;
default:
return state;
}
};
To retrieve data, you will see that redux abstracts away all the excess logics and there is no more setItem, getItem because redux-persist does that automagically for your already.
import { connect } from "react-redux";
import { storePhotos } from "./actions";
class GalleryList extends Component {
async componentDidMount() {
const photos = await axios.get('http://jsonplaceholder.typicode.com/photos');
storePhoto(photos)
}
renderPhoto = ({ item }) => <GalleryDetail photo={item}/>
keyExtractor = (photo, index) => photo.id;
render () {
return (
<FlatList
data = {this.props.photos}
keyExtractor={this.keyExtractor}
renderItem={this.renderPhoto}
/>
);
}
}
// pull data from photos reducer
const mapStateToProps = ({ photos }) => {
return {
photos: photos.photos
}
}
export default connect(mapStateToProps, { storePhotos })(GalleryList);
To summarise,
Install redux-persist in you project.
Import persistStore and autoRehydrate form redux-persist.
Add autoRehydrate to your store.
Pass your store to persistStore.
Listen to the persist/REHYDRATE action on your reducer and populate state accordingly.
Hope my answer helps!
Answer
Caching the data for a specific period of time
const cacheIntervaInHours = 24
const cacheExpiryTime = new Date()
cacheExpiryTime.setHours(cacheExpiryTime.getHours() + cacheIntervalInHours)
const lastRequest = await AsyncStorage.getItem("lastRequest")
if (lastRequest == null || lastRequest > cacheExpiryTime) {
fetch(`${apiUrl}/blogPosts/recent`)
.then(async (response) => {
return await response.json()
})
.then(async (json) => {
if (!json || json.length == 0) {
throw new Error()
}
AsyncStorage.setItem("lastRequest", new Date());
return await AsyncStorage.setItem('blogPosts', JSON.stringify(json))
})
.catch(error => {
console.error(error)
})
}
I'm using react-native-fs to download a file(pdf, word, excel, png etc.) and I need to open it in other application. Is it possible to open downloaded file with Linking or better open a dialog with possible apps like when using Sharing? Linking in the code below tries to open the file but it closes immediately without any notification, but my app is still working fine. Is there some special way to build URLs for deep linking for a specific file type? Any ideas for the best solution?
I see that there is old package react-native-file-opener but it's no longer maintained. This solution would be great.
Simplified code for download component:
import React, { Component } from 'react';
import { Text, View, Linking, TouchableOpacity } from 'react-native';
import { Icon } from 'react-native-elements';
import RNFS from 'react-native-fs';
import { showToast } from '../../services/toasts';
class DownloadFile extends Component {
state = {
isDone: false,
};
handleDeepLinkPress = (url) => {
Linking.openURL(url).catch(() => {
showToast('defaultError');
});
};
handleDownloadFile = () => {
RNFS.downloadFile({
fromUrl: 'https://www.toyota.com/content/ebrochure/2018/avalon_ebrochure.pdf',
toFile: `${RNFS.DocumentDirectoryPath}/car.pdf`,
}).promise.then(() => {
this.setState({ isDone: true });
});
};
render() {
const preview = this.state.isDone
? (<View>
<Icon
raised
name="file-image-o"
type="font-awesome"
color="#f50"
onPress={() => this.handleDeepLinkPress(`file://${RNFS.DocumentDirectoryPath}/car.pdf`)}
/>
<Text>{`file://${RNFS.DocumentDirectoryPath}/car.pdf`}</Text>
</View>)
: null;
return (
<View>
<TouchableOpacity onPress={this.handleDownloadFile}>
<Text>Download File</Text>
</TouchableOpacity>
{preview}
</View>
);
}
}
export default DownloadFile;
After some research, I decided to use react-native-fetch-blob. From version 0.9.0 it's possible to open downloaded file with Intent and use Download Manager. It also has API for iOS for opening documents.
Code now:
...
const dirs = RNFetchBlob.fs.dirs;
const android = RNFetchBlob.android;
...
handleDownload = () => {
RNFetchBlob.config({
addAndroidDownloads: {
title: 'CatHat1.jpg',
useDownloadManager: true,
mediaScannable: true,
notification: true,
description: 'File downloaded by download manager.',
path: `${dirs.DownloadDir}/CatHat1.jpg`,
},
})
.fetch('GET', 'http://www.swapmeetdave.com/Humor/Cats/CatHat1.jpg')
.then((res) => {
this.setState({ path: res.path() });
})
.catch((err) => console.log(err));
};
...
render() {
...
<Icon
raised
name="file-pdf-o"
type="font-awesome"
color="#f50"
onPress={() => android.actionViewIntent(this.state.path, 'image/jpg')}
...
}