expo SMS.sendSMSAsync inside a loop pops sms modal and immediately closing - reactjs

I'm trying to send a text message using expo-sms, the problem is when the sms modal is open to send the message - it immediately closing and the result of SMS.sendSMSAsync is cancelled.
my code is this:
<TouchableOpacity onPress={() => handleSend(contacts, text)}>{...}</TouchableOpacity>
async function handleSend(contacts, text) {
try {
const numbersArr = contacts.map(c => c.number)
while (numbersArr.length > 0) {
const number = numbersArr.pop()
const { result } = await SMS.sendSMSAsync(number, text)
// 'result' here is 'canceled' for each number
}
showMessagesSuccessfullySentMessage()
} catch (e) {
console.error(e)
showSmsSendingProblemError()
}
}
SMS sending is available, of course, I do the check in advanced using useEffect when the component is mounted:
useEffect(() => {
;(async function() {
const isAvailable = await SMS.isAvailableAsync()
setIsSmsAllowed(isAvailable)
})()
}, [])
what am I doing wrong? what did I miss?
Moreover, when I write a brand new app from scratch with the following code, everything works just perfect:
const numbersArr = ['1234', '12345', '123456', '1234567'] // dummy numbers here
export default function App() {
async function handleSend () {
const isAvailable = await SMS.isAvailableAsync()
if (isAvailable) {
while (numbersArr.length > 0) {
const n = numbersArr.pop()
const result = await SMS.sendSMSAsync(n, 'My sample HelloWorld message')
console.log('results', result)
}
} else {
// misfortune... there's no SMS available on this device
}
}
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<StatusBar style="auto" />
<Button title="Send" onPress={handleSend}/>
</View>
)
}
some technical data and versions:
tested on iPhone 11 Pro
"expo": "~36.0.0"
"expo-sms": "~8.0.0"

We don't have any known issues with Expo SMS at the moment. I wrote a small example Snack, you can see it here. I've tested the dialog on both Android and iOS.
If you think this is an issue with Expo, please open an issue at expo/expo.

the problem was that my selected dummy contacts was accidentally created with the wrong phone number field, eventually resulted with number=undefined being called on SMS.sendSMSAsync(undefined, 'My sample HelloWorld message')
Obviously I created those dummy contacts to avoid testing the app on my real contacts...
(sorry for bothering the network)
Opened an issue to expo to discuss the option to throw error or at least a warning in dev stage to warn for undefined/null value being passed to SMS.sendSMSAsync

Related

Invalid value for stripe.confirmPayment(): elements should have a mounted Payment Element

I'm trying to integrate a Stripe payments page using the React "Elements". I'm following the tutorial from https://stripe.com/docs/payments/accept-a-payment?platform=web&ui=elements#web-submit-payment and I've gotten to step 5, "Submit the payment to Stripe". My code doesn't look much different from the example, but whenever I try to submit a payment this error:
Invalid value for stripe.confirmPayment(): elements should have a mounted Payment Element.
Thrown from stripe.confirmPayment. I've included the <Elements/> and <PaymentElement/> on the page, and passed the return value from useElements() so I'm really not sure what I'm missing.
Here's my checkout form:
function StripeCheckoutForm({paymentIntent,booking}: StripeCheckoutFormProps) {
const stripe = useStripe();
const elements = useElements();
const [confirming,setConfirming] = useState(false)
const handleSubmit = async (ev: FormEvent<HTMLFormElement>) => {
ev.preventDefault();
if (!stripe || !elements) {
notifyWarning("Still loading. Please wait a few seconds and then try again.");
return;
}
setConfirming(true)
try {
const {error} = await stripe.confirmPayment({
//`Elements` instance that was used to create the Payment Element
elements,
confirmParams: {
return_url: resolveRoute('customerPaymentReceived',{key:booking.key}),
},
})
if(error) {
notifyError(error.message)
setConfirming(false)
}
} catch(error) {
setConfirming(false)
if(error?.message) {
notifyError(error.message) // <-- error shown here
} else {
notifyError("Something went wrong");
}
}
}
return (
<form onSubmit={handleSubmit}>
<PaymentElement/>
<BlockSpacer height="1rem"/>
<ActionButton disabled={!stripe || !elements || confirming} type="submit" className="btn-phone btn-fullwidth">Pay {paymentIntent.amountFormatted}</ActionButton>
</form>
)
}
And that form is inside this component, similar to the example shown in step 4:
import {Elements as StripeElements} from '#stripe/react-stripe-js';
import {useStripe, useElements, PaymentElement} from '#stripe/react-stripe-js';
function StripePaymentForm({stripeAccountId,paymentIntent,booking}: StripePaymentFormProps) {
const options = {
clientSecret: paymentIntent.clientSecret,
loader: 'always',
}
return (
<StripeElements stripe={getStripeConnect(stripeAccountId)} options={options}>
<StripeCheckoutForm paymentIntent={paymentIntent} booking={booking}/>
</StripeElements>
)
}
The only thing I can see that's different is that I'm using a Connect account, so I'm passing in account ID in when I load Stripe. getStripeConnect is basically
loadStripe(STRIPE_PUBLIC_KEY, {stripeAccount: CONNECTED_ACCOUNT_ID})
You can see the React component tree here if it helps:
What am I missing?
I'm guessing this stems from useElements() is not finding any elements:
But I still don't know why.
I believe it's a recent bug in react-stripe-js:
https://github.com/stripe/react-stripe-js/issues/296
I believe your loadStripe() promise isn't resolved at the time it is passed to the Elements provider, thus useElements returns null. Either load it earlier or await it and it should resolve your issue.

How to use PostMessage in a React Native webview?

I'm trying to use the postmessage to a page opened in a webview inside a React Native App. I tried many times, but still wasn't able to send it.
I can listen to messages from the webpage normally. I just can't send anything back.
I'm currently using react-native-webview 11.6.5
export default function WebPage() {
const webviewRef = useRef();
const onMessage = (event) => {
//receive message from the web page. working here until here
const data = JSON.parse(event.nativeEvent.data);
//reply the message
webviewRef.current.postMessage(
JSON.stringify({reply: 'reply'}),
'*'
)
}
return <View>
<WebView
ref={webviewRef}
originWhitelist={['*']}
source={{ uri: 'https://robertnyman.com/html5/postMessage/postMessage.html' }}
domStorageEnabled
javaScriptEnabled
onMessage={onMessage}
/>
</View>
}
Any ideas what am I doing wrong?
UPDATE:
Thanks to #Ahmed Gaber help I was able to find this issue https://github.com/react-native-webview/react-native-webview/issues/809 and discover they're changing postMessage to injectJavaScript.
So I updated my code onMessage to the following:
const onMessage = (event) => {
const data = JSON.parse(event.nativeEvent.data);
//reply the message
webviewRef.current.injectJavaScript(
`window.postMessage(
{
reply: 'reply'
}
);`
)
}
To send data from app to webview, use injectedJavaScript
To send data from webview to app, use postMessage
To receive data in webview sent by postMessage, use onMessage
// This Js function will be injected into the web page after the document finishes loading.
// This function will Post a message to WebView.
const INJECTED_JAVASCRIPT = `(function() {
window.ReactNativeWebView.postMessage(JSON.stringify({key : "value"}));
})();`;
<WebView
source={{ uri: 'https://reactnative.dev' }}
injectedJavaScript={INJECTED_JAVASCRIPT}
onMessage={(event) => {
const data = JSON.parse(event.nativeEvent.data);
alert(data.key);
}}
/>;
React native code
function getInjectableJSMessage(message) {
return `
(function() {
document.dispatchEvent(new MessageEvent('message', {
data: ${JSON.stringify(message)}
}));
})();
`;
}
function sendDataToWebView() {
webviewRef.current?.injectJavaScript(
getInjectableJSMessage("Hello")
);
}
React web app code
React.useEffect(() => {
function handleEvent(message) {
console.log(message.data);
}
document.addEventListener("message", handleEvent);
return () =>
document.removeEventListener("message", handleEvent);
}, []);

Using Experimental Chrome API in React?

I am trying to use Chrome nfc api (WEB NFC) inside of react, so when you open the react page on mobile with nfc enabled and press a button it check to see if nfc scan.
I was reading through the doc and the sample code to do this is,
scanButton.addEventListener("click", async () => {
log("User clicked scan button");
try {
const ndef = new NDEFReader();
await ndef.scan();
log("> Scan started");
ndef.addEventListener("readingerror", () => {
log("Argh! Cannot read data from the NFC tag. Try another one?");
});
ndef.addEventListener("reading", ({ message, serialNumber }) => {
log(`> Serial Number: ${serialNumber}`);
log(`> Records: (${message.records.length})`);
});
} catch (error) {
log("Argh! " + error);
}
});
My react page is currently,
import { Button } from "#material-ui/core";
import React from "react";
const nfcpage = () => {
return (
<>
<Button
id = "scanButton"
>
Press to test NFC
</Button>
</>
);
};
export default nfcpage;
I want the scanButton code to run when the button is pressed. In other words to make scanButton into a function that can be called by my button via onClick{}.
Thank you
I was able to fix this by creating an async function from the sample code and passing that to onClick in the button component

How to implement barcode scanner in reactjs application?

I am developing an Order Scan web application, and the challenge I'm facing is that, if barcode matches, where I'm going to receive data in my react.js application. I'll be using a barcode scanner device, so can I use this npm package https://www.npmjs.com/package/react-barcode-reader. If so, how to implement it in my react app?
I have searched the web for answers. I couldn't find anything to my specific problem in react.js.
A barcode scanner is simply an input device. Just think of it as a keyboard.
You may receive its inputs via any TextInputs component.
If your barcode scanner is programmed to terminate with "\n", then the TextInput will also trigger onSubmit event.
I would recommend using the React Barcode Scanner NPM package. A simple demonstration is on that site. You can use their Barcode Scanner component:
<BarcodeReader
onError={handleError}
onScan={handleScan}
/>
You can apply that component in where you want the user to perform the scan. You will just need to make a handleScan and handleError function.
Examples:
const [scanData, setScanData] = useState()
const handleScan = (data) => {
setScanData(data)
}
const handleError = (err) => console.error(err)
The data will be stored in the scanData variable. Hope you find this helpful.
This is a very simple example for barcode reading in React, using simple vanilla javascript:
This works using timeframe to detect barcode readings. You can easily change it for endline codes (you won't need the timeout)
import { useEffect, useState } from "react";
import "./App.css";
function App() {
const [barcodeRead, setBarcodeRead] = useState("");
const barcode = {
timing: 1000,
data: "",
};
const barcodeReaded = () => {
if (barcode.data.length > 1) {
setBarcodeRead(barcode.data);
} else {
barcode.data = "";
}
};
let timeout = setTimeout(barcodeReaded, 500);
useEffect(() => {
window.addEventListener("keypress", (e) => {
console.log(e.key);
console.log(e.timeStamp);
if (barcode.data.length === 0 || e.timeStamp - barcode.timing < 200) {
barcode.data += e.key;
barcode.timing = e.timeStamp;
clearTimeout(timeout);
timeout = setTimeout(barcodeReaded, 500);
} else {
barcode.data = "";
}
console.log(barcode);
});
}, []);
return (
<div className="App">
<div>Readed: {barcodeRead}</div>
</div>
);
}
export default App;

Best way to make exactly one API call in react-native (fetch, axios etc.)?

I am currently creating an app that includes a barcode scanner. Unfortunately, the only company I found - that seems to own 90+% of all food barcodes - provides an API starting at 500$/ year. So I tried to create a workaround which works but fires so many API calls that I get blocked immediately after one try. I inserted a console.warn to see how many calls are fired everytime I call the API and it's about 20 to 35 times. Here is the code:
async getApiData() {
let formData = new FormData();
formData.append('keyValue', 4015000961547);
formData.append('requestTradeItemType', 'ownership');
formData.append('keyCode', 'gtin');
formData.append('someRandomToken', '1');
const response = await fetch('http://gepir.gs1.org/index.php?option=com_gepir4ui&view=getkeylicensee&format=raw', {
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': 'someRandomCookie'
},
body: formData
});
if (response.status == 200) {
const responseJson = JSON.parse(response._bodyText);
const productName = responseJson.gepirParty.partyDataLine.gS1KeyLicensee.partyName;
this.setState({ productName });
}
}
If you try it with this keyValue of 4015000961547 you will get Henkel AG in case you want to test (http://gepir.gs1.org/index.php/search-by-gtin). What I don't understand is: why is my function firing so many requests to the API once the barcode is read altough I am using async/ await? I read that axios is the better method but it didn't really work in my case ... is there a third/ fourth method I could try? It is very crucial that once I have the scan data I only send one request to the API otherwise I can't test it.
Just to provide all the information needed this is my code for getting the barcode data after the scan. I am using react-native-camera:
import { RNCamera } from 'react-native-camera';
... some more code ...
export default class Scanner extends Component {
... some code ...
async onBarCodeRead(scanResult) {
console.warn(scanResult.type);
if (scanResult.data != null) {
console.warn(scanResult.data);
const eanCode = await scanResult.data;
}
return;
}
...some code here...
render() {
return (
<View>
<RNCamera
ref={ref => {
this.camera = ref;
}}
barcodeFinderVisible={this.state.camera.barcodeFinderVisible}
defaultTouchToFocus
mirrorImage={false}
onBarCodeRead={this.onBarCodeRead.bind(this)}
onFocusChanged={() => {}}
onZoomChanged={() => {}}
type={this.state.camera.type}
/>
<View>
<Text>Please scan the barcode.</Text>
</View>
</View>
);
}
}
For simplicity, I removed any styling and unused props in the RNCamera tag.
This appears to be a feature -- that it continuously scans for a barcode.
There is an issue thread on the repo where it is suggested to set a flag the first time the event is emitted, like so:
onBarCodeRead = (...someArgs) => {
if (!this.isBarcodeRead) {
this.isBarcodeRead = true;
// Do your work
}
}

Resources