React Native - How to use Promises with async - react-native

I'm pretty new of RN but I don't really understand Promises with async.
I got how works but I don't use them correctly cause they don't work in my code.
Example
const getAnimationTime = () => {
let res = meditationTime;
return new Promise(resolve => resolve(res));
};
and then:
useEffect(() => {
(async function fetchData() {
const fetcher = await getAnimationTime();
console.log('fetcher', fetcher);
setAnimationTime(fetcher);
})();
console.log('anitime useEffect', animationTime);
}, []);
I did what I saw in tutorials and doesn't work. In this case, I always get undefined
Can someone explain to me how works?
Thanks!

Try to move variable declared inside the Promise block.
const getAnimationTime = () => {
return new Promise((resolve, reject) => {
let res = 100;
resolve(res);
})
};

I solved like adding this:
if (isLoading || animationTime === null) {
return <PulseIndicator color="green" size={50} />;
} else {
return (
<TouchableOpacity
onPress={handlePause}
onLongPress={longPress}
style={styles.container}>
...
It simply waits for the changing state

Related

How to mock expo-camera requestCameraPermissionsAsync() response?

I am trying to do a unit test to one of my react native components.
One scenario requires me to mock expo-camera requestCameraPermissionsAsync() method but don't know how. What I'm trying to do is to mock the status to always have the granted value.
Initial approach, below:
jest.mock('expo-camera', () => {
const PermissionsCamera = jest.requireActual('expo-camera');
return {
...PermissionsCamera,
requestCameraPermissionsAsync: () =>
new Promise(resolve => resolve({granted: true, status: 'granted'})),
};
});
But that doesn't work. Need help, is there something wrong with the code above? Thank you
Update:
How I implemented in the component:
import {Camera} from 'expo-camera'
useEffect(() => {
(async () => {
const {status} = await Camera.requestCameraPermissionsAsync();
// additional logic when status is equal to 'granted'
})();
}, []);

Updating useState array from callback

I am building a React Native (Expo) app that scans for Bluetooth devices. The Bluetooth API exposes a callback for when devices are detected, which I use to put non-duplicate devices into an array:
const DeviceListView = () => {
const [deviceList, setDeviceList] = useState([]);
const startScanning = () => {
manager.startDeviceScan(null, null, (error, device) => {
// Add to device list if not already in list
if(!deviceList.some(d => d.device.id == device.id)){
console.log(`Adding ${device.id} to list`);
const newDevice = {
device: device,
...etc...
};
setDeviceList(old => [...old, newDevice]);
}
});
}
// map deviceList to components
componentList = deviceList.map(...);
return <View> {componentList} </View>
}
The problem is that the callback is called many many times faster than setDeviceList updates, so the duplicate checking doesn't work (if I log deviceList, it's just empty).
If I use an additional, separate regular (non-useState) array, the duplicate checking works, but the state doesn't update consistently:
const DeviceListView = () => {
const [deviceList, setDeviceList] = useState([]);
var deviceList2 = [];
const startScanning = () => {
manager.startDeviceScan(null, null, (error, device) => {
// Add to device list if not already in list
if(!deviceList2.some(d => d.device.id == device.id)){
console.log(`Adding ${device.id} to list`);
const newDevice = {
device: device,
...etc...
};
deviceList2.push(newDevice);
setDeviceList(old => [...old, newDevice]);
}
});
}
// map deviceList to components
componentList = deviceList.map(...);
return <View> {componentList} </View>
}
This code almost works, but the deviceList state doesn't update correctly: it shows the first couple of devices but then doesn't update again unless some other component causes a re-render.
What do I need to do to make this work as expected?
I would suggest wrap your duplicate check within the state set function itself, and then return the same device list if no new devices have been found. This offloads race condition handling to the underlying react implementation itself, which I've found to be good enough for most cases.
Thus it would look something like this:
const DeviceListView = () => {
const [deviceList, setDeviceList] = useState([]);
const startScanning = () => {
manager.startDeviceScan(null, null, (error, device) => {
// Add to device list if not already in list
setDeviceList(old => {
if(!old.some(d => d.device.id == device.id)){
console.log(`Adding ${device.id} to list`);
const newDevice = {
device: device,
// ...etc...
};
return [...old, newDevice]
}
return old
});
});
}
// map deviceList to components
componentList = deviceList.map(...);
return <View> {componentList} </View>
}
Since old is unchanged if no new unique devices are found it will also skip next re-render according to the docs ( which is a neat optimisation :) )
This is the preferred way to implement state updates that are dependant on previous state according to the docs
https://reactjs.org/docs/hooks-reference.html#functional-updates
convert your callback to promise so that until you get completed device list, checkout below code (PS. not tested, please change as you need)
const [deviceList, setDeviceList] = useState([]);
const [scanning, setScanning] = useState(false);
useEffect(() => {
if(scanning) {
setDeviceList([]);
startScanning();
}
}, [scanning]);
const subscription = manager.onStateChange(state => {
if (state === "PoweredOn" && scanning === false) {
setCanScan(true);
subscription.remove();
}
}, true);
const fetchScannedDevices = () => {
return new Promise((resolve, reject) => {
manager.startDeviceScan(null, null, (error, device) => {
// Add to device list if not already in list
if (!deviceList.some(d => d.device.id == device.id)) {
console.log(`Adding ${device.id} to list`);
const newDevice = {
device: device,
// ...etc...
};
resolve(newDevice);
}
if (error) {
reject({});
}
});
});
};
const startScanning = async () => {
try {
const newDevice = await fetchScannedDevices();
setDeviceList(old => [...old, newDevice]);
} catch (e) {
//
}
};
const handleScan = () => {
setScanning(true);
};
// map deviceList to components
componentList = deviceList.map(() => {});
return (
<View>
<Button
onPress={() => handleScan()}>
Scan
</Button>
<View>{componentList}</View>
</View>
);
};

Why my function return always _u 0 _v 0 _w null _x null?

How can I return the value from an async function in React Native ?
I am trying to compress images which was given by user.
Here is my compress function where compress each image
const compressFunction = async (item, format = SaveFormat.JPEG) => {
const result = await manipulateAsync(
item.uri,
[],
{ compress: 0, format }
)
return result
};
Here is the function I try to call it. This is not working, console.log return Promise { _u 0, _v 0, _w null, _x null }. compressFunction is working and i can console.log and see the result for each return of "compressFunction(item)" but i can't see them in array list.
const compressImages = (items) => {
const list = items.map( async (item) => ( await compressFunction(item)) )
console.log(list)
}
But for example when i try to call like that it is working.
const compressImages = async (items) => {
const list= await compressFunction(items[0])
console.log(list)
}
I couldn't catch what im doing wrong, I'm working on it for hours. If someone can help me I would be grateful
try await Promise.all(items.map(compressFunction))
Calling list.map(async function) will transform each element in list into a Promise.
Edit: if you make this much more verbose you can see what's going on here
const promises = items.map(item => {
return new Promise((res) => {
compressFunction(item).then(result => {
return res(result);
});
});
});
// Now with the list of promises we have to use the Promise api
// to wait for them all to finish.
Promise.all(promises).then(results => {
// do stuff with results
});
Async/await is just shorthand for this syntax

Updating a state variable in React Native Expo

I dont really understand how setState and state variables update and work in React Native. Im trying to figure out what I did wrong in the code below, because I'm updating my tokenArray variable, but when I console log it in another function it is empty. Please help.
constructor() {
super()
this.state = {
tokenArr: []
}
}
componentDidMount() {
this.grabToken()
}
firebaseInformation = async () => {
var tokens = []
firebase.database().ref(`tokens/`).once('value', snapshot => {
const token = Object.values(snapshot.val());
token.map((item) => {
tokens.push(item.data)
})
return this.setState({
tokenArr: tokens
})
})
}
grabToken = async () => {
this.firebaseInformation()
console.log(this.state.tokenArr)
}
The fix was just to call the grabToken function in my render method instead (I was only calling it from my componentDidMount and didn't understand why it wasn't updating my state variable properly.
Return the array and set the state in componentDidMount() like this
componentDidMount() {
this.firebaseInformation()
.then((arr) => this.setState({ tokenArr: arr }))
.then(this.state.tokenArr);
}
firebaseInformation = async () => {
var tokens = []
firebase.database().ref(`tokens/`).once('value', snapshot => {
const token = Object.values(snapshot.val());
token.map((item) => {
tokens.push(item.data)
})
return tokenArr;
})
}

Return result of VersionCheck.getLatestVersion() as string

I'm trying to output the version number of my app using react-native-version-check, am I missing something here? Console logging latestVersion works but not this.
renderAppVersion = () => {
VersionCheck.getLatestVersion({
provider: 'appStore'
})
.then(latestVersion => {
return latestVersion;
});
}
<Text>App Version: {this.renderAppVersion()}</Text>
VersionCheck.getLatestVersion is an Async function so you wont get value when you use this.renderAppVersion() because it is also an Async function use state for storing the version which got from function and render it
code:
const App = () => {
const [version, setVersion] = useState();
useEffect(() => {
VersionCheck.getLatestVersion().then(v => setVersion(v))
}, [])
return (
<View>
{version && <Text>{version}</Text>}
</View>
);
}