I have been using React for a few months now and am diving deeper in to application structure and design patterns. Of particular interest to me is handling multiple asynchronous calls that may depend on each other, dependant on the logic of user journeys.
I am currently building an app where I have multiple asynchronous operations that include using local storage, connecting and querying Firebase for phone number authentication and also a WebRTC React Native video calling platform.
In trying to figure out how to handle data received from these apis, I have come across a few examples of code where the authors are using plain classes, not class components to help build their application. My question is, what is this design pattern called and how should I approach using it? What are the best resources to read about its use?
For example, with Firebase phone number authentication a developer I found on Github writes this:
class FirebaseService {
user = ''
uid = ''
userStatusDatabaseRef = ''
userStatusFirestoreRef = ''
constructor() {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
const { uid, phoneNumber} = firebase.auth().currentUser._user
UserStore.user.setUID(uid)
UserStore.user.setPhoneNumber(phoneNumber)
let ref = firebase.database().ref(`/Users/${UserStore.user.uid}`)
ref.on('value', this.gotUserData)
}
this.setListenConnection()
})
}
confirmPhone = async (phoneNumber) => {
const phoneWithAreaCode = phoneNumber.replace(/^0+/,'+972');
return new Promise((res, rej) => {
firebase.auth().verifyPhoneNumber(phoneWithAreaCode)
.on('state_changed', async (phoneAuthSnapshot) => {
switch (phoneAuthSnapshot.state) {
case firebase.auth.PhoneAuthState.AUTO_VERIFIED:
await this.confirmCode(phoneAuthSnapshot.verificationId, phoneAuthSnapshot.code, phoneAuthSnapshot)
res(phoneAuthSnapshot)
break
case firebase.auth.PhoneAuthState.CODE_SENT:
UserStore.setVerificationId(phoneAuthSnapshot.verificationId)
res(phoneAuthSnapshot)
break
.......
Here he creates FirebaseService and there is also UserStore referenced. However, I just have a FirebaseService.js file, and pass in a callback to it authorizeWithToken from my container component to store a result to state via Redux in the component.
import firebase from 'react-native-firebase';
export const authorizeFirebase = (phoneNumber, authorizeWithToken) => {
try {
firebase.auth()
.verifyPhoneNumber(phoneNumber)
.on('state_changed', (phoneAuthSnapshot) => {
// console.log(phoneAuthSnapshot);
switch (phoneAuthSnapshot.state) {
case firebase.auth.PhoneAuthState.CODE_SENT: // or 'sent'
console.log('code sent');
break;
case firebase.auth.PhoneAuthState.ERROR: // or 'error'
console.log('verification error');
console.log(phoneAuthSnapshot.error);
break;
case firebase.auth.PhoneAuthState.AUTO_VERIFY_TIMEOUT: // or 'timeout'
console.log('auto verify on android timed out');
break;
// '-------------------------------------------------------------'
case firebase.auth.PhoneAuthState.AUTO_VERIFIED: // or 'verified'
console.log('auto verified on android');
const { verificationId, code } = phoneAuthSnapshot;
const credential = firebase.auth.PhoneAuthProvider.credential(verificationId, code);
firebase.auth().signInWithCredential(credential).then(result => {
const user = result.user;
user.getIdToken().then(accessToken => {
authorizeWithToken(accessToken);
My service is a function, and his is a class, neither of them are components.
To give another example - here is a class 'service' from a code example that is provided by video messaging platform ConnectyCube that I'm using:
import ConnectyCube from 'connectycube-reactnative'
class ChatService {
// Chat - Core
connect(user) {
return new Promise((resolve, reject) => {
if (!user) reject()
ConnectyCube.chat.connect(
{
userId: user.id,
password: user.password,
},
(error, contacts) => {
if (!error && contacts) {
resolve(contacts)
} else {
reject(error)
}
},
)
})
}
disonnect() {
ConnectyCube.chat.disconnect()
}
}
// create instance
const Chat = new ChatService()
// lock instance
Object.freeze(Chat)
export default Chat
I haven't come across classes as services until now but I would really like to know how I should use them and what patterns I should follow. They look super useful and at the moment I have a spaghetti junction of local state, Redux and services that are connected via callbacks and complex async await setups. Any ideas greatly appreciated.
Related
I have two cloud functions
One cloud function is for set or updating existing scheduled job
Canceling an existing scheduled job
I am using import * as the schedule from 'node-schedule'; to manage Scheduling a jobs
The problem is cus createJob function is triggered and jobId is returned, but later when I triger cancelJob function all prev scheduled cron jobs do not exist cus node-schedule lives in memory and I can't access the jobs:
this will return empty object: const allJobs = schedule.scheduledJobs;
Does anyone have some solution on this situation?
UTILS this is the main logic that is called when some of my cloud functions are triggered
enter code here
// sendgrid
import * as sgMail from '#sendgrid/mail';
import * as schedule from 'node-schedule';
sgMail.setApiKey(
'apikey',
);
import {
FROM_EMAIL,
EMAIL_TEMPLATE_ID,
MESSAGING_SERVICE_SID,
} from './constants';
export async function updateReminderCronJob(data: any) {
try {
const {
to,
...
} = data;
const message = {
to,
from: FROM_EMAIL,
templateId: EMAIL_TEMPLATE_ID,
};
const jobReferences: any[] = [];
// Stop existing jobs
if (jobIds && jobIds.length > 0) {
jobIds.forEach((j: any) => {
const job = schedule.scheduledJobs[j?.jobId];
if (job) {
job.cancel();
}
});
}
// Create new jobs
timestamps.forEach((date: number) => {
const job = schedule.scheduleJob(date, () => {
if (selectedEmail) {
sgMail.send(message);
}
});
if (job) {
jobReferences.push({
jobId: job.name,
});
}
});
console.warn('jobReferences', jobReferences);
return jobReferences;
} catch (error) {
console.error('Error updateReminderCronJob', error);
return null;
}
}
export async function cancelJobs(jobs: any) {
const allJobs = schedule.scheduledJobs;
jobs.forEach((job: any) => {
if (!allJobs[job?.jobId]) {
return;
}
allJobs[job.jobId].cancel();
});
}
node-schedule will not work effectively in Cloud Functions because it requires that the scheduling and execution all be done on a single machine that stays running without interruption. Cloud Functions does not fully support this behavior, as it will dynamically scale up and down to zero the number of machines servicing requests (even if you set min instances to 1, it may still reduce your active instances to 0 in some cases). You will get unpredictable behavior if you try to schedule this way.
The only way you can get reliable scheduling using Cloud Functions is with pub/sub functions as described in the documentation. Firebase scheduled functions make this a bit easier by managing some of the details. You will not be able to dynamically control repeating jobs, so you will need to build some way to periodically run a job and check to see if it should run at that moment.
Is there a way during webdriverio runtime to simulate an actioncable receive?
I am using a fork of the package action-cable-react called actioncable-js-jwt for Rails actioncable js connections. Both of these packages are no longer maintained, but actioncable-js-jwt was the only actioncable for react package I could find that supported jwt authentication. I am building an app in my company's platform and jwt authentication is required.
The problem I am running into is that I have a react component which dispatches a redux action to call an api. The api returns a 204, and the resulting data is broadcasted out from Rails to be received by the actioncable connection. This triggers a dispatch to update the redux store with new data. The component does actions based on new data compared to the initial value on component load, so I cannot simply just set initial redux state for wdio - I need to mock the actioncable receive happening.
The way the actioncable subscription is created is:
export const createChannelSubscription = (cable, receivedCallback, dispatch, channelName) => {
let subscription;
try {
subscription = cable.subscriptions.create(
{ channel: channelName },
{
connected() {},
disconnected(res) { disconnectedFromWebsocket(res, dispatch); },
received(data) { receivedCallback(data, dispatch); },
},
);
} catch (e) {
throw new Error(e);
}
return subscription;
};
The receivedCallback function is different for each channel, but for example the function might look like:
export const handleUpdateRoundLeaderWebsocket = (data, dispatch) => {
dispatch({ type: UPDATE_ROUNDING_LEADER, round: data });
};
And the redux state is used here (code snippets):
const [currentLeader, setCurrentLeader] = useState(null);
const userId = useSelector((state) => state.userId);
const reduxStateField = useSelector((state) => state.field);
const onChange = useCallback((id) => {
if (id !== currentLeader) {
if (id !== userId && userId === currentLeader) {
setShow(true);
} else {
setCurrentLeader(leaderId);
}
}
}, [currentLeader, userId]);
useEffect(() => {
onChange(id);
}, [reduxStateField.id, onChange]);
Finally, my wdio test currently looks like:
it('has info dialog', () => {
browser.url('<base-url>-rounding-list-view');
$('#my-button').click();
$('div=Continue').click();
// need new state after the continue click
// based on new state, might show an info dialog
});
Alternatively, I could look into manually updating redux state during wdio execution - but I don't know how to do that and can't find anything on google except on how to provide initial redux state.
I am integrating payment api, I'm using firebase cloud functions as my backend and react-native in frontend, so right now i have the following code:
In react-native side:
const PaymentScreen = () => {
setLoading(true);
const onPay = () => {
const onPaymentCall = firebase.functions().httpsCallable('onPayment');
onPaymentCall(productsData)
.then(res => {
setLoading(false);
if (res?.data?.statusCode === 200) {
const paymentReceivedData = {
transactionId: res?.data?.transactionId,
paymentDate: new Date().now(),
totalPrice: res?.data?.totalPrice
}
navigator.navigate('ReceiptScreen', { paymentReceivedData });
}
})
}
return (
<TouchableOpacity onPress={onPay}>PAY</TouchableOpacity>
)
}
firebase function to handle payment:
export const onPayment = functions
.runWith({ timeoutSeconds: 300 })
.https.onCall(async (data: any, context: any) => {
if (!context.auth) {
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called while authenticated.'
);
}
try {
const paymentResult = await paymentThroughApi(data);
await admin.firestore().collection('wallet').add({...paymentResult});
const response = {
statusCode: 200,
totalPrice: paymentResult.totalPrice,
transactionId: paymentResult.transactionId,
}
return response;
} catch (error) {
const response = {
statusCode: 400,
message: 'Payment unsuccessful',
transactionId: null,
}
return response;
}
So the question is, how can I handle long-time response/timeout error/network-loss etc in react-native side, like how should I make it more robust? especially handling long-time responses, timeout errors, payment failures etc? Which packages should I use? like NetInfo to check internet connectivity but what is the best way to make payment flow robust?
So can anyone guide me or suggest any code snippets to add on to my current code?
Thank you!
I don't think there is one definite answer to your question, But here is what I think:
For managing network connection status in React Native you can use
NetInfo for information about network connection and Axios to make network requests to a public API as per documentation and for more information check with this Stackoverflow Link.
For timeout errors you can check the Article from Dan Abromov, which states Making setInterval Declarative with React Hooks and with this link
There are different answers for long-time responses in react-native.
I keep getting the statement "do not save non-serializable variables in your state" in almost every google search result - But what happens when I really should?
Progect: I am building an app for deviceS connected via SerialPort (using SerialPort WebAPI).
I wish to save the connection instance since I use it throughout all my application and I am honestly tired of passing the instance down and up whenever I need it without react knowing to re-render data and display new data - which is important for me too.
Steps that I have done:
It was easy to ignore the non-serializable error using serializableCheck: false:
export default configureStore({
reducer: {
serialport: SerialPortDevicesReducer,
bluetooth: BluetoothDevicesReducer,
},
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
thunk,
serializableCheck: false
}).concat(logger),})
But now I am facing the big problem:
Whenever I create a connection I get the object that handles that specific SerialPort device object that is connected.
deviceReducer: {
id: 1,
instance: SerialPort{[attr and methods here]},
...
}
Whenever I use methods like open(), write() or read() it changes the main connection instance object and breaks with that known error:
Error: Invariant failed: A state mutation was detected between
dispatches, in the path 'serialport.0.instance.readable'. This may
cause incorrect behavior
Since It's not serializable I cannot clone it (which I think is the reason?) and then re-assign it + I think cloning a connection instance will cause other device-connection issues.
I ended up writing the connect method case directly in the state with a "promise" new variable to handle the result.
// click in a react component
const handleConnect = () => {
try {
if ( dispatch(connect(device)) ) {
setActiveStep((prevActiveStep) => prevActiveStep + 1)
return true
}
}
catch (e) {
console.error("Device cannot connect: ", e)
}
}
// In a file that trigges dispatch() to the reduces
const connect = (deviceId) => async (dispatch, getState) => {
try {
dispatch({
type: "serialport/connect",
payload: deviceId
})
} catch(e) {
console.log(e)
}
}
// in reducer
const SerialPortDevicesReducer = (state = initialState, action) => {
switch (action.type) {
case 'serialport/connect':
try {
return {
...state,
[action.payload]: {
...state[action.payload],
promise: state[action.payload].instance.open({baudRate: 115200})
}
}
} catch (e) {
console.error("Cannot run promise inside reducer: ", e)
}
This is the only workaround I currently found. And this basically forces me to handle (maybe some complex) things in the reducer instead of just passing data to it. I tried applying the same for the write method:
// click in component
const handleExecute = (command) => {
try {
dispatch(writeToSP(device1.device, command))
} catch (e) {
console.log(e)
}
}
// In file which trigges the dispatch()
const writeToSP = (deviceId, command = "Z !\n") => async (dispatch) => {
let startTime = new Date().getTime()
let encoder = new TextEncoder()
try {
dispatch({
type: "serialport/write",
payload: {
id: deviceId,
// cmd: encoder.encode(command),
// startTime
}
})
} catch (e) {
console.error("error writing: ", e)
}
}
// in reducer
...
case 'serialport/write':
try {
const writer = state[action.payload.id].instance.writable.getWriter()
} catch (e) {
console.error("Cannot run promise inside reducer: ", e)
}
and again, get the error of "Error: Invariant failed: A state mutation was detected..." which I am guessing a result of it changing other attributes in the SerialPort instance.
Having packages like redux-promise-middleware are awesome, but it seems like an object in my state is the one responsible for its own promise and changes.
How do I handle this specific situation?
Simple: don't put it into Redux. Redux is made for data, not for arbirtary external libraries/dependency injection.
If that value will never change after initialization and you do the initialization outside of React, just put it into a global variable that is exported.
If that value will change over time, you should use the Dependency Injection mechanism of React for it: Context. This is really what context is made for - not sharing state values, but global dependencies.
I have a big application in React-Native and I have a lot of duplicate functions in the code.
So I created a file called function.js which could contain all my duplicate functions. Like queries on the local database, remote data base...
So I pretty much got the job done. However I have a problem.
This two functions must be used one after the other.
The first one does an update of the state to get a user id from local database.
The second one, asks information from the remote database with the user id retrieved by the first function in parameters.
When both calls are in the componentdidmount element, unfortunately it doesn't work !!
The update time of the state by the first function is too slow compared to the execution of the second function.
The second function gets an "undefined" parameter when it is executed. because the state is not updated by the first function for the moment.
If I put the second function in componentDidUpdate() it works but it runs in a loop so it's not a solution either.
I also don't want to trigger the execution of the second function at the end of the first one in the external file. It would make the functions not autonomous from each other.
And I think that the solution of a timeout() is not very good either, even if we could work with it.
Example code :
It's the content of my App.js file that imports the Function.js file containing all my functions
import React, { Component } from 'react';
import { fetchUser, prepareUserData } from 'bitcoin/Functions/Function'
export default class Profile extends Component {
state = {
user_id: "",
}
componentDidMount() {
fetchUser.call(this);
prepareUserData.call(this, this.state.user_id)
}
render{
return (<View></View>)
}
This is the content of my Function.js file which contains functions that are duplicated in my application.
import * as SQLite from "expo-sqlite";
import axios from "axios";
const db = SQLite.openDatabase("db.db");
/* #############################################################################
User data retrieval function in the local database
##############################################################################*/
export function fetchUser () {
let query = "select * from ?";
let params = [];
db.transaction(tx => {
tx.executeSql(
query,
params,
(_, { rows: { _array } }) => {
this.setState({user_id: _array[0].user_id})
},
function(tx, err) {
console.log("Erreur" + err);
}
);
});
}
/* #############################################################################
User data retrieval function in the remote database
##############################################################################*/
export function prepareUserData(userID) {
let userConnect = new FormData();
userConnect.append("id", userID);
console.log(userConnect)
const url =
"https://*************/rest_api/React_native_api/appmobile_profile";
axios
.post(url, userConnect)
.then(res => {
console.log(res.data)
if(res.status === 200){
this.setState(
{
user_pseudo: res.data.pseudo,
[ ... ]
user_lastName: res.data.last_name,
},);
}
})
.catch(err => {
console.log("Erreuur", err);
});
}
I've tried a lot of things with async componentDidMount(), await myfunc(), creating asynchronous functions in my function file ...
But I can't find solutions. I could do otherwise but I find the problem really interesting.
I think there is a way to optimize my use of react native.
Thank you for your various feedbacks. Have a nice day.
Okay, so I've come up with a effective solution to this problem.
I don't know much about promise and yet it seems to be a key for a lot element in react-native and javascript in general.
I'll share my code. Hopefully it can be useful to someone in the future! thank you for your prompt return and see you soon!
To make it work I used:
The Promise Object new Promise(function(resolve,reject)
Creation of a dynamic SQL query by adding parameters when calling the function.
File App.js
import React, { Component } from 'react';
import { selectFromTable, prepareUserData } from 'bitcoin/Functions/Function'
export default class Profile extends Component {
state = {
user_data: "",
}
componentDidMount() {
selectFromTable('user', ['*']).then((result) => {
this.setState({ user_data: result.rows._array[0] },() =>
{prepareUserData.call(this, this.state.user_data.user_id)})
})
}
render{
return (<View></View>)
}
File Function.js
export function selectFromTable(table_name, selected_columns, conditions, callback) {
return new Promise(function(resolve,reject) {
let db_cmd = `SELECT ${selected_columns} FROM ${table_name}`;
db.transaction((tx, err) => {
tx.executeSql(db_cmd, [], (tx, res) => {
resolve(res);
},
function(tx, err) {
console.log("Error" + err);
}
);
});
});
}
export function prepareUserData(userID) {
let userConnect = new FormData();
userConnect.append("id", userID);
const url =
"https://**********/**********/appmobile_profile";
axios
.post(url, userConnect)
.then(res => {
console.log(res.data)
if(res.status === 200){
this.setState(
{
user_pseudo: res.data.pseudo,
user_cellphone: res.data.cellphone,
user_callingCode: res.data.calling_code,
});
};
})
.catch(err => {
console.log("Error", err);
});
}