Multiple useEffect in react-native to achieve mentioned functionality - react-native

I need help with the async nature of Async storage and axios api. Here's the functionality that I am trying to achieve ->
send request to two separate api to get some data.
display that data on the screen with some additional text
api request are authenticated so a token is passed as Authentication Header
I have attached the current implementation, I am having the a number of errors in this
Errors:
Login_token not set in state after fetching from Async Storage.
Data not set in state after api call
both resulting in either failed api calls or undefined state errors on render
This is my code.
import React, { FunctionComponent, useEffect, useCallback, useState} from 'react';
import { StyleSheet, View} from 'react-native';
// chat
import { GiftedChat } from 'react-native-gifted-chat';
// navigation
import { RootStackParamList } from '../../navigators/RootStack';
import { StackScreenProps } from '#react-navigation/stack';
export type Props = StackScreenProps<RootStackParamList, "Chat">;
// api
import { Convo_details, Send_Msg, List_Msg, Expert_Public_Profile } from '../../api/UserApi';
import Spinner from 'react-native-loading-spinner-overlay';
import AsyncStorage from '#react-native-async-storage/async-storage';
import uuid from 'react-native-uuid';
const Chat: FunctionComponent<Props> = ({ navigation, route, ...props }) => {
// console.log(props.route.params);
const [login_token, setlogin_token] = useState('')
const [conversation_id, setconversation_id] = useState('')
const [conversation_details, setconversation_details] = useState({})
const [currentuser, setcurrentuser] = useState({})
const [loading, setLoading] = useState(false);
const [expertuid, setexpertuid] = useState('')
const [ExpertProfile, setExpertProfile] = useState({})
const [messages, setMessages] = useState([]);
useEffect(() => {
getlogintoken()
console.log("####################################","getlogintoken");
}, [])
/* conversationid */
useEffect(() => {
if (route.params != null) {
setconversation_id(route.params[0])
}
console.log("####################################","conversation id");
}, [])
/* expert uid */
useEffect(() => {
if (route.params != null) {
setexpertuid(route.params[1])
}
console.log("####################################","expert uid");
}, [])
/* expert public profile */
useEffect(() => {
getexpertpublicprofile()
getConvo_details()
console.log("####################################","convo_details");
}, [])
useEffect(() => {
// get current user
AsyncStorage.getItem("currentuser").then(res => {
if (res != null) setcurrentuser(res)
else alert("Current user not found")
})
console.log("####################################","current user");
}, [])
// set welcome msg
useEffect(() => {
if (Object.keys(conversation_details).length != 0 && Object.keys(ExpertProfile).length != 0)
setwelcomemsg()
}, [])
const onSend = useCallback(async (messages = []) => {
// console.log(messages[0].text);
setMessages(previousMessages => GiftedChat.append(previousMessages, messages))
const data = {
conversation_id: "f98d6851-a713-4f58-9118-77a779ff175f",//conversation_id,
message_type: "TEXT",
body: messages[0].text
}
const res: any = await Send_Msg(data, login_token)
.catch(error => {
alert(`Send_Msg -> ${error}`)
console.log(error);
return
})
if (res.status == 200) {
console.log(res.data);
} else console.log(res);
}, [])
const getexpertpublicprofile = async () => {
setLoading(true)
const res: any = await Expert_Public_Profile(expertuid, login_token)
.catch(error => {
setLoading(false)
console.log("Expert public profile ->");
alert(`Expert public profile ->${error.message}`)
console.log(error);
return
})
setLoading(false)
if (res.status === 200) setExpertProfile(res.data)
else {
alert(`get expert public profile${res.data.message}`)
console.log("getexpertpublicprofile -->");
console.log(res.data);
}
}
const getlogintoken = () => {
AsyncStorage.getItem("login_token").then(res => {
if (res != null) {
setLoading(false)
setlogin_token(res)
}
else alert("No login token found")
})
}
const getConvo_details = async () => {
setLoading(true)
const res: any = await Convo_details(conversation_id, login_token)
.catch(error => {
setLoading(false)
alert(`Convo_details-->${error.message}`)
console.log("Convo_details -->");
console.log(error);
return
})
setLoading(false)
if (res.status === 200) setconversation_details(res.data)
else {
alert(`get convo details-> ${res.data.message}`)
console.log("getConvo_details -->");
console.log(res.data);
}
}
const setwelcomemsg = () => {
try {
let user = JSON.parse(currentuser)
let messages = [
{
_id: uuid.v4().toString(),
conversation_id: conversation_details.conversation_id,
created_at: new Date(),
from: conversation_details.recipient.user_uid,
type: "TEXT",
text: `About Me - ${ExpertProfile.bio}`,
user: {
_id: conversation_details.recipient.user_uid,
}
},
{
_id: uuid.v4().toString(),
conversation_id: conversation_details.conversation_id,
created_at: new Date(),
from: conversation_details.recipient.user_uid,
type: "TEXT",
text: `My name is ${conversation_details.recipient.name}`,
user: {
_id: conversation_details.recipient.user_uid,
}
},
{
_id: uuid.v4().toString(),
conversation_id: conversation_details.conversation_id,
created_at: new Date(),
from: conversation_details.recipient.user_uid,
type: "TEXT",
text: `Hi ${user.full_name}`,
user: {
_id: conversation_details.recipient.user_uid,
}
}]
setMessages(previousMessages => GiftedChat.append(previousMessages, messages))
} catch (error) {
console.log("try -> set welcome msg");
console.log(error);
return
}
}
return (
<View style={styles.maincontainer}>
<Spinner
visible={loading}
textContent={'Loading...'}
textStyle={{ color: '#FFF' }}
/>
<GiftedChat
messages={messages}
onSend={messages => onSend(messages)}
user={{
_id: currentuser.user_uid,
}}
isTyping={false}
scrollToBottom={true}
showAvatarForEveryMessage={true}
renderAvatar={() => null}
/>
</View>
);
}
export default Chat;
const styles = StyleSheet.create({
maincontainer: {
flex: 1,
},
});

When axios returns, it usually give the response as res.data, so in your case, try either res.data or res.data.yourToken (I'm not sure how it's your object).

Gurav,
As far as your code above, The api call's will trigger even before you get currentuser or loginToken. You have to handle the api call after getting the currentuser and loginToken. This can be gracefully handled with async, await.
example code:
useEffect(() => {
getData()
}, [])
useEffect(() => {
if(login_token && currentuser) {
//The api call goes here after you get the logintoken andcurrentuser.
// The above condition is just an example but will vary based on your requirements
}
}, [login_token, currentuser])
const getData = async () => {
await getlogintoken()
await getcurrentuser()
}
const getlogintoken = async () => {
await AsyncStorage.getItem("login_token").then(res => {
if (res != null) {
setLoading(false)
setlogin_token(res)
}
else alert("No login token found")
})
}
const getcurrentuser = async () => {
await AsyncStorage.getItem("currentuser").then(res => {
if (res != null) setcurrentuser(res)
else alert("Current user not found")
})
}

Related

React-native: How to change the audio speed in expo-av

I'm having trouble changing the prop: 'rate' to change the speed of the audio being played.
I'm using expo-av (https://docs.expo.dev/versions/latest/sdk/av/).
Here's my code:
import {Text, View, Alert } from 'react-native'
import * as MediaLibrary from 'expo-media-library';
import { DataProvider } from 'recyclerlistview';
import {Audio} from 'expo-av';
import { play, pause, resume, playNext } from "../misc/AudioController";
export const AudioContext = createContext()
export class AudioProvider extends Component {
constructor(props) {
super(props);
this.state = {
audioFiles: [],
permissionError: false,
dataProvider: new DataProvider((r1, r2) => r1 !== r2),
playbackObj: null,
soundObj: null,
currentAudio: {},
isPlaying: false,
currentAudioIndex: null,
playbackPosition: null,
playbackDuration: null,
rate: 2.0,
};
this.totalAudioCount = 0;
}
permissionAlert = () => {
Alert.alert("Permission Required", "This app needs to read audio files", [
{ text: "I am ready", onPress: () => this.getPermission() },
{
text: "cancel",
onPress: () => this.permissionAlert(),
},
]);
};
getAudioFiles = async () => {
const { dataProvider, audioFiles } = this.state;
let media = await MediaLibrary.getAssetsAsync({
mediaType: "audio",
});
media = await MediaLibrary.getAssetsAsync({
mediaType: "audio",
first: media.totalCount,
});
this.totalAudioCount = media.totalCount;
this.setState({
...this.state,
dataProvider: dataProvider.cloneWithRows([
...audioFiles,
...media.assets,
]),
audioFiles: [...audioFiles, ...media.assets],
});
};
loadPreviousAudio = async () => {
let previousAudio = await AsyncStorageLib.getItem("previousAudio");
let currentAudio;
let currentAudioIndex;
if (previousAudio === null) {
currentAudio = this.state.audioFiles[0];
currentAudioIndex = 0;
} else {
previousAudio = JSON.parse(previousAudio);
currentAudio = previousAudio.audio;
currentAudioIndex = previousAudio.index;
}
this.setState({ ...this.state, currentAudio, currentAudio });
};
getPermission = async () => {
// {
// "canAskAgain": true,
// "expires": "never",
// "granted": false,
// "status": "undetermined",
// }
const permission = await MediaLibrary.getPermissionsAsync();
if (permission.granted) {
this.getAudioFiles();
}
if (!permission.canAskAgain && !permission.granted) {
this.setState({ ...this.state, permissionError: true });
}
if (!permission.granted && permission.canAskAgain) {
const { status, canAskAgain } =
await MediaLibrary.requestPermissionsAsync();
if (status === "denied" && canAskAgain) {
this.permissionAlert();
}
if (status === "granted") {
this.getAudioFiles();
}
if (status === "denied" && !canAskAgain) {
this.setState({ ...this.state, permissionError: true });
}
}
};
onPlaybackStatusUpdate = async (playbackStatus) => {
console.log("hier");
if (playbackStatus.isLoaded && playbackStatus.isPlaying) {
this.updateState(this, {
playbackPosition: playbackStatus.positionMillis,
playbackDuration: playbackStatus.durationMillis,
});
}
if (playbackStatus.didJustFinish) {
const nextAudioIndex = this.state.currentAudioIndex + 1;
if (nextAudioIndex >= this.totalAudioCount) {
this.state.playbackObj.unloadAsync();
this.updateState(this, {
soundObj: null,
currentAudio: this.state.audioFiles[0],
isPlaying: false,
currentAudioIndex: 0,
playbackPosition: null,
playbackDuration: null,
});
}
const audio = this.state.audioFiles[nextAudioIndex];
const status = await playNext(this.state.playbackObj, audio.uri);
this.updateState(this, {
soundObj: status,
currentAudio: audio,
isPlaying: true,
currentAudioIndex: nextAudioIndex,
});
}
};
componentDidMount() {
this.getPermission();
if (this.state.playbackObj === null) {
this.setState({ ...this.state, playbackObj: new Audio.Sound(), });
}
}
updateState = (prevState, newState = {}) => {
this.setState({ ...prevState, ...newState });
};
render() {
const {
audioFiles,
dataProvider,
permissionError,
playbackObj,
soundObj,
currentAudio,
isPlaying,
currentAudioIndex,
playbackPosition,
playbackDuration,
rate,
} = this.state;
if (permissionError)
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
}}
>
<Text>It looks like you haven't accepted the permission</Text>
</View>
);
return (
<AudioContext.Provider
value={{
audioFiles,
dataProvider,
playbackObj,
soundObj,
currentAudio,
isPlaying,
currentAudioIndex,
totalAudioCount: this.totalAudioCount,
playbackPosition,
playbackDuration,
rate,
updateState: this.updateState,
loadPreviousAudio: this.loadPreviousAudio,
onPlaybackStatusUpdate: this.onPlaybackStatusUpdate
}}
>
{this.props.children}
</AudioContext.Provider>
);
}
}
import {Component} from 'react';
import AsyncStorageLib from '#react-native-async-storage/async-storage';
export default AudioProvider;
and here's some more:
// play audio
// Import the react-native-sound module
import { PitchCorrectionQuality,shouldCorrectPitch, rate } from "expo-av/build/AV.types";
export const play = async (playbackObj, uri,) => {
try {
return await playbackObj.loadAsync(
{uri},
{shouldPlay: true},
);
} catch (error) {
console.log('error inside play helper method', error.message)
}
};
//pause
export const pause = async playbackObj => {
try {
// playbackObj.setRateAsync(rate = 2.0, shouldCorrectPitch = true, PitchCorrectionQuality= High);
return await playbackObj.setStatusAsync({
shouldPlay: false},
);
} catch (error) {
console.log('error inside pause helper method', error.message)
}
};
//resume
export const resume = async playbackObj => {
try {
return await playbackObj.playAsync(
);
} catch (error) {
console.log('error inside pause resume method', error.message)
}
};
//select next
export const playNext = async (playbackObj, uri) => {
try {
await playbackObj.stopAsync()
await playbackObj.unloadAsync();
return await play(playbackObj, uri);
} catch (error) {
console.log('error inside playNext helper method')
}
}
I've tried including 'rate: 2.0' inside this.state{audioFiles: [],
permissionError: false, etc.} but it didn't work.
Also I've tried doing: await playbackObj.setRateAsync() in the 2nd code snippet.
Any suggestions?
Nvm, I found the solution. Here's my updated code:
// play audio
// Import the react-native-sound module
import { PitchCorrectionQuality,shouldCorrectPitch, rate } from "expo-av/build/AV.types";
export const play = async (playbackObj, uri,) => {
try {
await playbackObj.loadAsync(
{uri},
{shouldPlay: true},
);
return await playbackObj.setStatusAsync({ rate: 0.9749090909 });
} catch (error) {
console.log('error inside play helper method', error.message)
}
};
//pause
export const pause = async playbackObj => {
try {
return await playbackObj.setStatusAsync({
shouldPlay: false,
rate: 0.9749090909,
});
} catch (error) {
console.log('error inside pause helper method', error.message)
}
};
//resume
export const resume = async playbackObj => {
try {
return await playbackObj.playAsync(
);
} catch (error) {
console.log('error inside pause resume method', error.message)
}
};
//select next
export const playNext = async (playbackObj, uri) => {
try {
await playbackObj.stopAsync()
await playbackObj.unloadAsync();
return await play(playbackObj, uri);
} catch (error) {
console.log('error inside playNext helper method')
}
}

Expo Location, Expo Task Manager with Context API react native

import React, { createContext, useContext, useState, useEffect } from 'react';
import {
authenticationService,
getUserProfileService,
} from '../services/user-service';
import { getDonationHistory } from '../services/donation-service';
import AsyncStorage from '#react-native-async-storage/async-storage';
import * as TaskManager from 'expo-task-manager';
import * as Location from 'expo-location';
import * as BackgroundFetch from 'expo-background-fetch';
const UserInfoContext = createContext();
const UserInfoProvider = ({ children }) => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [userProfile, setUserProfile] = useState({
donorId: '',
icNo: '',
fName: '',
lName: '',
bloodType: '',
});
const [errorMessage, setErrorMessage] = useState(null);
const [location, setLocation] = useState({ latitude: '', longitude: '' });
let updateLocation = (loc) => {
setLocation(loc);
};
console.log(location);
const sendBackgroundLocation = async () => {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status === 'granted') {
const { status } = await Location.requestBackgroundPermissionsAsync();
if (status === 'granted') {
await Location.startLocationUpdatesAsync('LocationUpdate', {
accuracy: Location.Accuracy.Balanced,
timeInterval: 10000,
distanceInterval: 1,
foregroundService: {
notificationTitle: 'Live Tracker',
notificationBody: 'Live Tracker is on.',
},
});
}
}
};
const _requestLocationPermission = async () => {
(async () => {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status == 'granted') {
let { status } = await Location.requestBackgroundPermissionsAsync();
if (status == 'granted') {
} else {
console.log('Permission to access location was denied');
}
} else {
console.log('Permission to access location was denied');
}
})();
};
sendBackgroundLocation();
const getUserProfile = () => {
return userProfile;
};
useEffect(() => {
(async () => await _requestLocationPermission())();
retrieveAuthTokens();
retrieveUserProfile();
});
let retrieveAuthTokens = async () => {
try {
const authTokens = await AsyncStorage.getItem('authTokens');
authTokens ? setIsLoggedIn(true) : setIsLoggedIn(false);
} catch (error) {
console.log(error.message);
}
};
let retrieveUserProfile = async () => {
try {
const userProfile = JSON.parse(await AsyncStorage.getItem('userProfile'));
userProfile
? setUserProfile({
donorId: userProfile.donorId,
icNo: userProfile.appUser.username,
fName: userProfile.fName,
lName: userProfile.lName,
bloodType: userProfile.bloodType,
})
: null;
} catch (error) {
console.log(error.message);
}
};
let loginUser = (values) => {
authenticationService(values)
.then(async (data) => {
if (data !== undefined && data !== null) {
const tokens = data.data;
await AsyncStorage.setItem('authTokens', JSON.stringify(tokens));
getProfile(values.icNo);
getHistories(userProfile.donorId);
setErrorMessage(null);
} else {
setErrorMessage('Wrong email/password!');
}
})
.catch((error) => console.log(error.message));
};
let logoutUser = async () => {
try {
await AsyncStorage.clear().then(console.log('clear'));
setIsLoggedIn(false);
} catch (error) {
console.log(error.message);
}
};
getProfile = (icNo) => {
getUserProfileService(icNo)
.then(async (res) => {
if (res !== undefined && res !== null) {
const profile = res.data;
await AsyncStorage.setItem('userProfile', JSON.stringify(profile));
setIsLoggedIn(true);
}
})
.catch((error) => console.log(error.message));
};
const getHistories = (userId) => {
getDonationHistory(userId)
.then(async (res) => {
if (res !== undefined && res !== null) {
const historyData = res.data;
await AsyncStorage.setItem(
'donationHistories',
JSON.stringify(historyData)
);
} else {
console.log('no data');
}
})
.catch((error) => console.log(error.message));
};
let contextData = {
loginUser: loginUser,
logoutUser: logoutUser,
isLoggedIn: isLoggedIn,
errorMessage: errorMessage,
userProfile: userProfile,
getHistories: getHistories,
};
return (
<UserInfoContext.Provider value={contextData}>
{children}
</UserInfoContext.Provider>
);
};
export const useUserInfo = () => useContext(UserInfoContext);
export default UserInfoProvider;
function myTask() {
try {
const backendData = 'Simulated fetch ' + Math.random();
return backendData
? BackgroundFetch.BackgroundFetchResult.NewData
: BackgroundFetch.BackgroundFetchResult.NoData;
} catch (err) {
return BackgroundFetch.BackgroundFetchResult.Failed;
}
}
async function initBackgroundFetch(taskName, interval = 60 * 15) {
try {
if (!TaskManager.isTaskDefined(taskName)) {
TaskManager.defineTask(taskName, ({ data, error }) => {
if (error) {
console.log('Error bg', error);
return;
}
if (data) {
const { locations } = data;
console.log(
locations[0].coords.latitude,
locations[0].coords.longitude
);
//-----------------doesnt work ----------------------------
UserInfoProvider.updateLocation({
latitude: locations[0].coords.latitude,
longitude: locations[0].coords.longitude,
});
//-----------------doesnt work ----------------------------
}
});
}
const options = {
minimumInterval: interval, // in seconds
};
await BackgroundFetch.registerTaskAsync(taskName, options);
} catch (err) {
console.log('registerTaskAsync() failed:', err);
}
}
initBackgroundFetch('LocationUpdate', 5);
I'm trying to update the location state in the UserInfoProvider and this location info will be sent to the firestore along with the userprofile details retrieved in this provider.
However, it seems that i cant access the
UserInfoProvider.updateLocation()
outside the UserInfoProvider component.
Is there anyway I can get both the userprofile info and the location info retrieved from background task together and send them to firestore?
At the moment, only the
console.log(
locations[0].coords.latitude,
locations[0].coords.longitude
);
in the background task seems to be working.
Error I got:
TaskManager: Task "LocationUpdate" failed:, [TypeError: undefined is
not a function (near '...UserInfoProvider.updateLocation...')] at
node_modules\react-native\Libraries\LogBox\LogBox.js:149:8 in
registerError at
node_modules\react-native\Libraries\LogBox\LogBox.js:60:8 in errorImpl
at node_modules\react-native\Libraries\LogBox\LogBox.js:34:4 in
console.error at
node_modules\expo\build\environment\react-native-logs.fx.js:27:4 in
error at node_modules\expo-task-manager\build\TaskManager.js:143:16 in
eventEmitter.addListener$argument_1 at
node_modules\regenerator-runtime\runtime.js:63:36 in tryCatch at
node_modules\regenerator-runtime\runtime.js:294:29 in invoke at
node_modules\regenerator-runtime\runtime.js:63:36 in tryCatch at
node_modules\regenerator-runtime\runtime.js:155:27 in invoke at
node_modules\regenerator-runtime\runtime.js:190:16 in
PromiseImpl$argument_0 at
node_modules\react-native\node_modules\promise\setimmediate\core.js:45:6
in tryCallTwo at
node_modules\react-native\node_modules\promise\setimmediate\core.js:200:22 in doResolve at
node_modules\react-native\node_modules\promise\setimmediate\core.js:66:11
in Promise at node_modules\regenerator-runtime\runtime.js:189:15 in
callInvokeWithMethodAndArg at
node_modules\regenerator-runtime\runtime.js:212:38 in enqueue at
node_modules\regenerator-runtime\runtime.js:239:8 in exports.async at
node_modules\expo-task-manager\build\TaskManager.js:133:57 in
eventEmitter.addListener$argument_1 at
node_modules\react-native\Libraries\vendor\emitter_EventEmitter.js:135:10 in EventEmitter#emit at
node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:414:4
in __callFunction at
node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:113:6
in __guard$argument_0 at
node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:365:10
in __guard at
node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:112:4
in callFunctionReturnFlushedQueue
By reviewing UserInfoProvider context value:
let contextData = {
loginUser: loginUser,
logoutUser: logoutUser,
isLoggedIn: isLoggedIn,
errorMessage: errorMessage,
userProfile: userProfile,
getHistories: getHistories,
};
updateLocation function not available and you can't access.
You need to use useUserInfo to consume UserInfoProvider value.

Cannot read property state

I try to test this action:
const getGameList = function(context) {
if(context.state.user.id){
let request_body = {
user_id : context.state.user.id
}
axios.post(`api/game_list_of_user`,request_body).then(response => {
context.commit('UpdateGameList',response.data);
}).catch(error => console.log(error));
}
};
My action is to get the list of game for a specific user.
This action has:
as input my user id .
as output my game of list.
My test:
import actions from '#/store/actions'
import state from '#/store/state'
import store from '#/store'
import axios from 'axios'
jest.mock('axios');
describe('getGameList', () => {
test('Success: should return the game list of the user and update gameList in the store', () => {
const state = { user: {id: 1} };
const mockFunction = jest.fn();
const response = {
data: [
{ id:1, name:"game_name1" },
{ id:2, name:"game_name2" }
]
};
axios.post.mockResolvedValue(response);
actions.getGameList({ mockFunction },{ state });
//expect(mockFunction).toHaveBeenCalledTimes(1);
//expect(mockFunction).toHaveBeenCalledWith('UpdateGameList',response.data);
});
test('Error: an error occurred', () => {
const errorMessage = 'Error';
axios.get.mockImplementationOnce(() =>
Promise.reject(new Error(errorMessage))
);
});
});
I declare my state (with my user id).
I declare my expected response
from my request (the game list = response.data).
I use jest.fn() to mock the function. (Should I do that ?)
I got this error:
I want to check:
My request has been called
The response of my request matches with my expected response
My mutation is then called
How can I solve that error?
Edit1: my test
jest.mock('axios');
describe('getGameList', () => {
test('Success: should return the game list of the user and update gameList in the store', () => {
const context = {
state : {
user: {
id: 1
}
}
};
const mockFunction = jest.fn();
const response = {
data: [
{ id:1, name:"game_name1" },
{ id:2, name:"game_name2" }
]
};
axios.post.mockResolvedValue(response);
actions.getGameList({ mockFunction, context });
expect({ mockFunction, context }).toHaveBeenCalledTimes(1);
expect(mockFunction).toHaveBeenCalledWith('UpdateGameList',response.data);
});
test('Error: an error occurred', () => {
const errorMessage = 'Error';
axios.get.mockImplementationOnce(() =>
Promise.reject(new Error(errorMessage))
);
});
});
this is my solution:
import actions from '#/store/actions'
import mutations from '#/store/mutations'
import state from '#/store/state'
import store from '#/store'
import axios from 'axios'
let url = ''
let body = {}
jest.mock("axios", () => ({
post: jest.fn((_url, _body) => {
return new Promise((resolve) => {
url = _url
body = _body
resolve(true)
})
})
}))
//https://medium.com/techfides/a-beginner-friendly-guide-to-unit-testing-the-vue-js-application-28fc049d0c78
//https://www.robinwieruch.de/axios-jest
//https://lmiller1990.github.io/vue-testing-handbook/vuex-actions.html#testing-actions
describe('getGameList', () => {
test('Success: should return the game list of the user and update gameList in the store', async () => {
const context= {
state: {
user: {
id:1
}
},
commit: jest.fn()
}
const response = {
data: [
{ id:1, name:"game_name1" },
{ id:2, name:"game_name2" }
]
};
axios.post.mockResolvedValue(response) //OR axios.post.mockImplementationOnce(() => Promise.resolve(response));
await actions.getGameList(context)
expect(axios.post).toHaveBeenCalledWith("api/game_list_of_user",{"user_id":1});
expect(axios.post).toHaveBeenCalledTimes(1)
expect(context.commit).toHaveBeenCalledWith("UpdateGameList", response.data)
});
test('Error: an error occurred', () => {
const errorMessage = 'Error';
axios.post.mockImplementationOnce(() =>
Promise.reject(new Error(errorMessage))
);
});
});

React Native app data storage to Google drive and iCloud

I am trying to store my app data on Google Drive and iCloud based on user device.I don't want to use Async Storage ,redux state neither I want to store data on my server cloud i.e. AWS. Basically I like the way how WhatsApp takes data backup on google drive for android devices and iCloud for IOS devices.This way I want to store my encrypted data's private keys on drive or iCloud so that if user changes his device I can get these keys from drive or iCloud and proceed for decryption mechanism.I found https://github.com/manicakes/react-native-icloudstore which serves my purpose for iCloud.But I haven't found anything on same line for google drive.Can you please suggest me better approach for above requirement?
For Google Drive Implementation You can Check this Package : react-native-google-drive-api-wrapper . You Have To Use Google SignIn With this package In order Get the Access Token , Install This Package Also react-native-google-signin/google-signin .
A Quick Example :
import { GoogleSignin } from "#react-native-google-signin/google-signin";
import {
GDrive,
ListQueryBuilder,
MimeTypes
} from "#robinbobin/react-native-google-drive-api-wrapper";
import React, {
useCallback,
useEffect,
useState
} from "react";
import {
AppRegistry,
Button,
SafeAreaView,
StyleSheet
} from "react-native";
import { name } from './app.json';
function App() {
const [gdrive] = useState(() => new GDrive());
const [ui, setUi] = useState();
const invoker = useCallback(async cb => {
try {
return await cb();
} catch (error) {
console.log(error);
}
}, []);
const createBinaryFile = useCallback(async () => {
console.log(await invoker(async () => (
await gdrive.files.newMultipartUploader()
.setData([1, 2, 3, 4, 5], MimeTypes.BINARY)
.setRequestBody({
name: "bin",
//parents: ["folder_id"]
})
.execute()
)));
}, [invoker]);
const createIfNotExists = useCallback(async () => {
console.log(await invoker(async () => (
await gdrive.files.createIfNotExists(
{
q: new ListQueryBuilder()
.e("name", "condition_folder")
.and()
.e("mimeType", MimeTypes.FOLDER)
.and()
.in("root", "parents")
},
gdrive.files.newMetadataOnlyUploader()
.setRequestBody({
name: "condition_folder",
mimeType: MimeTypes.FOLDER,
parents: ["root"]
})
)
)));
}, [invoker]);
const createFolder = useCallback(async () => {
console.log(await invoker(async () => (
await gdrive.files.newMetadataOnlyUploader()
.setRequestBody({
name: "Folder",
mimeType: MimeTypes.FOLDER,
parents: ["root"]
})
.execute()
)));
}, [invoker]);
const createTextFile = useCallback(async () => {
console.log(await invoker(async () => {
return (await gdrive.files.newMultipartUploader()
.setData("cm9iaW4=", MimeTypes.TEXT)
.setIsBase64(true)
.setRequestBody({
name: "base64 text",
})
.execute()).id;
}));
}, [invoker]);
const emptyTrash = useCallback(async () => {
if (await invoker(async () => {
await gdrive.files.emptyTrash();
return true;
}))
{
console.log("Trash emptied");
};
}, [invoker]);
const getWebViewLink = useCallback(async () => {
console.log(await invoker(async () => (
await gdrive.files.getMetadata(
"some_id", {
fields: "webViewLink"
}
)
)));
}, [invoker]);
const readFiles = useCallback(async () => {
console.log(await invoker(async () => (
await gdrive.files.getText("text_file_id")
)));
console.log(await invoker(async () => (
await gdrive.files.getBinary("bin_file_id", null, "1-1")
)))
}, [invoker]);
useEffect(() => {
GoogleSignin.configure({
scopes: [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/drive.appfolder"
]});
(async () => {
if (await invoker(async () => {
await GoogleSignin.signIn();
gdrive.accessToken = (await GoogleSignin.getTokens()).accessToken;
gdrive.files.fetchCoercesTypes = true;
gdrive.files.fetchRejectsOnHttpErrors = true;
gdrive.files.fetchTimeout = 1500;
return true;
}))
{
setUi([
["create bin file", createBinaryFile],
["create folder", createFolder],
["create if not exists", createIfNotExists],
["create text file", createTextFile],
["empty trash", emptyTrash],
["get webViewLink", getWebViewLink],
["read files", readFiles]
].map(([title, onPress], index) => (
<Button
key={index}
onPress={onPress}
title={title}
/>
)));
}
})();
}, [
createBinaryFile,
createFolder,
createIfNotExists,
createTextFile,
emptyTrash,
getWebViewLink,
readFiles,
invoker
]);
return (
<SafeAreaView
style={styles.container}
>
{ui}
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
backgroundColor: "cyan",
flex: 1,
justifyContent: "space-around",
padding: 25
}
});

Handling Refresh Token in React Native

I have an app authenticating fine and returning the access_token and refresh_token. I store them with AsyncStorage and save/get the access_token with redux. This is the very first app I am building and I am struggling with how and where to use the refresh_token.
This is the axios call in the component loginForm.js
axios({
url: `${base}/oauth/token`,
method: 'POST',
data: formData,
headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data',
}
})
.then(response => {
setStatus({ succeeded: true });
// console.log(response.data);
deviceStorage.saveKey("userToken", response.data.access_token);
deviceStorage.saveKey("refreshToken", response.data.refresh_token);
Actions.main();
})
.catch(error => {
if (error.response) {
console.log(error);
}
});
This is the service deviceStorage.js
import { AsyncStorage } from 'react-native';
const deviceStorage = {
async saveItem(key, value) {
try {
await AsyncStorage.setItem(key, value);
} catch (error) {
console.log('AsyncStorage Error: ' + error.message);
}
}
};
export default deviceStorage;
This is the token action file
import { AsyncStorage } from 'react-native';
import {
GET_TOKEN,
SAVE_TOKEN,
REMOVE_TOKEN,
LOADING_TOKEN,
ERROR_TOKEN
} from '../types';
export const getToken = token => ({
type: GET_TOKEN,
token,
});
export const saveToken = token => ({
type: SAVE_TOKEN,
token
});
export const removeToken = () => ({
type: REMOVE_TOKEN,
});
export const loading = bool => ({
type: LOADING_TOKEN,
isLoading: bool,
});
export const error = tokenError => ({
type: ERROR_TOKEN,
tokenError,
});
export const getUserToken = () => dispatch =>
AsyncStorage.getItem('userToken')
.then((data) => {
dispatch(loading(false));
dispatch(getToken(data));
})
.catch((err) => {
dispatch(loading(false));
dispatch(error(err.message || 'ERROR'));
});
export const saveUserToken = (data) => dispatch =>
AsyncStorage.setItem('userToken', data)
.then(() => {
dispatch(loading(false));
dispatch(saveToken('token saved'));
})
.catch((err) => {
dispatch(loading(false));
dispatch(error(err.message || 'ERROR'));
});
export const removeUserToken = () => dispatch =>
AsyncStorage.removeItem('userToken')
.then((data) => {
dispatch(loading(false));
dispatch(removeToken(data));
})
.catch((err) => {
dispatch(loading(false));
dispatch(error(err.message || 'ERROR'));
});
This is the token reducer file
import {
GET_TOKEN,
SAVE_TOKEN,
REMOVE_TOKEN,
LOADING_TOKEN,
ERROR_TOKEN
} from '../actions/types';
const INITIAL_STATE = {
token: {},
loading: true,
error: null
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_TOKEN:
return {
...state,
token: action.token
};
case SAVE_TOKEN:
return {
...state,
token: action.token
};
case REMOVE_TOKEN:
return {
...state,
token: action.token
};
case LOADING_TOKEN:
return {
...state,
loading: action.isLoading
};
case ERROR_TOKEN:
return {
...state,
error: action.error
};
default:
return state;
}
};
And this is the authentication file
import React from 'react';
import {
StatusBar,
StyleSheet,
View,
} from 'react-native';
import { connect } from 'react-redux';
import { Actions } from 'react-native-router-flux';
import { Spinner } from '../common';
import { getUserToken } from '../../actions';
class AuthLoadingScreen extends React.Component {
componentDidMount() {
this.bootstrapAsync();
}
bootstrapAsync = () => {
this.props.getUserToken().then(() => {
if (this.props.token.token !== null) {
Actions.main();
} else {
Actions.auth();
}
})
.catch(error => {
this.setState({ error });
});
};
render() {
return (
<View style={styles.container}>
<Spinner />
<StatusBar barStyle="default" />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
});
const mapStateToProps = state => ({
token: state.token,
});
const mapDispatchToProps = dispatch => ({
getUserToken: () => dispatch(getUserToken()),
});
export default connect(mapStateToProps, mapDispatchToProps)(AuthLoadingScreen);
I believe I need to create an action and reducer to get the refresh_token (is that correct?) but I do not know what to do with it and where to call it (perhaps in the authentication file?).
Any help with this possibly with code examples related to my code would be massively appreciated. Thanks
Below are the steps
Do Login , get accessToken , refreshToken from response and save it to AsyncStorage.
Make common function for API calling
async function makeRequest(method, url, params, type) {
const token = await AsyncStorage.getItem('access_token');
let options = {
method: method,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token,
},
};
if (!token) {
delete options['Authorization'];
}
if (['GET', 'OPTIONS'].includes(method)) {
url += (url.indexOf('?') === -1 ? '?' : '&') + queryParams(params);
} else {
Object.assign(options, {body: JSON.stringify(params)});
}
const response = fetch(ENV.API_URL+url, options);
return response;
}
Make one method in redux for getAceessTokenFromRefreshToken.
Use this method when session is expired
How do you know session is expired?
From each API calling if you get response like (440 response code) in
async componentWillReceiveProps(nextProps) {
if (nextProps.followResponse && nextProps.followResponse != this.props.followResponse) {
if (nextProps.followResponse.status) {
if (nextProps.followResponse.status == 440) {
// call here get acceesstokenfrom refresh token method and save again accesstoken in asyncstorage and continue calling to API
}
}
}
}