how to use expo-sqlite execute sql with typescript? - react-native

Hi all I want to use expo-sqlite to transaction object to execute an sql statement.
However, I got a problem in define the return value of the error function.
Here is the example code:
tx.executeSql(
// sql statement (ok)
"...",
// input arguments (ok)
[...],
// success case: since I use it in a promise, so I use: resolve(...) (ok)
() => {
resolve()
},
// failed case: I want to reject it, use reject()
// But I got an Error here! (Wrong!!!)
// Here ask me to return a boolean value, but how??? true or false???
(_, err) => {
reject(err) // not enough???
}
)
From the type definition file, I know, I need to return a boolean value for the error callback function, but which one? true or false???
Do you have some idea how to do it???
PS. Here is official doc about the expo-sqlite: https://docs.expo.io/versions/latest/sdk/sqlite/

I don't know why the error callback function does need a return type of boolean. Since we resolve/reject the promise anyways I think we can savely ignore the return type.
Below you can find my typescript synchronous example:
export const fetchTypeSaveSql = async <T>(sqlStatement: string, args: any[] | undefined): Promise<T[]> => {
return new Promise((resolve) => {
db.transaction(tx => {
tx.executeSql(
sqlStatement, args,
(_, result) => {
resolve(Array.from(result.rows as any) as T[])
},
(_, error): boolean => {
console.warn(error)
resolve([])
return false
})
})
})
}

The return type of SQLStatementErrorCallback is boolean (and not void) because it's used to indicate whether the error was handled or not.
If the error is handled (ie return true), the whole transaction doesn't fail. If it's not handled (ie return false), then it does. You should only return true if you've been able to suitably recover from the error.
Remember that executeSql is only used within a transaction (which is created via db.transaction or db.readTransaction). A transaction accepts it's own success and error callbacks.
You can check the this in the source code by working backwards from this: https://github.com/nolanlawson/node-websql/blob/7b45bf108a9cffb1c7e16b9a7dfec47be8361850/lib/websql/WebSQLTransaction.js#L64-L68
if (batchTask.sqlErrorCallback(self, res.error)) {
// user didn't handle the error
self._error = res.error;
return onDone();
}

Related

Cypress test for element existence conditionally with retry

I read the caveats in the docs on Cypress conditional testing, but nevertheless need to apply it to a particular test for certain reasons.
I have a function to do it, but there are certain selectors that do not work due to lack of retry in this function.
How can I implement retry in conditional testing and avoid flaky tests?
Is it even possible, or does one thing cancel out the other?
export function elementExists(selector: string): boolean {
try {
return Cypress.$(selector).length > 0;
} catch (error) {
return false;
}
The "standard" way to test existence of an element is pretty simple, but it does not return true/false. It fails the test if element is not found.
cy.get(selector).should('exist')
Internally the .should() retries the element until command timeout is finished - then fails the test.
If you make your function recursive, you can do the same but instead of failing, return true/false.
function elementExists(selector, attempt = 0) {
const interval = 100; // 100ms between tries
if (attempt * interval > Cypress.config('defaultCommandTimeout')) {
cy.log(selector, 'not found')
return cy.wrap(false, {log:false})
}
return cy.get('body', {log:false}).then(($body) => {
const element = $body.find(selector)
if (element.length) {
cy.log(selector, 'found')
return cy.wrap(true, {log:false})
} else {
cy.wait(interval, {log:false})
return elementExists(selector, ++attempt)
}
})
}
elementExists(selector).then(exists => {
if (exists) {
...
}
})
It's even easier now with the cypress-if package.
But retry is implemented asynchronously, so you will have to return a Chainable.
export function elementExists(selector: string): Chainable<boolean> {
return cy.get(selector)
.if('exist')
.then(true)
.else()
.then(false)
}
elementExists('span#123').then((result: boolean) =>
if (result) {
...
}
})
The above uses the full API and is very readable, but this should also work for you
export function elementExists(selector: string): Chainable<JQuery<HTMLElement>|undefined> {
return cy.get(selector).if()
}
elementExists('span#123').then((result: JQuery<HTMLElement>|undefined) =>
if(result.length) {
...
}
})

Cypress conditional statement depending on whether the input field is disabled or not

I have some input fields on a page. They can be disabled or not. So I need to type the text in this field just in case when it's not disabled. I try to do this way:
fillPickUpTown(pickUpTown: string) {
cy.get(`[data-testid="pick-up-town"]`)
.should('not.be.disabled')
.then(() => {
cy.get(`[data-testid="pick-up-town"]`).type(pickUpTown);
});
}
But I have failed test with error "Timed out retrying after 10000ms: expected '' not to be 'disabled'".
How can I type to this field just when it's not disabled and do nothing when it is?
Drop the should, this will only succeed for the one condition. Instead, test inside then()
fillPickUpTown(pickUpTown: string) {
cy.get(`[data-testid="pick-up-town"]`)
.then($el => {
if (!$el.is(':disabled')) {
cy.wrap($el).type(pickUpTown);
}
});
}
You can use JQuery :enabled to check and implement If-Else.
fillPickUpTown(pickUpTown: string) {
cy.get(`[data-testid="pick-up-town"]`).then(($ele) => {
if ($ele.is(":enabled")) {
cy.get(`[data-testid="pick-up-town"]`).type(pickUpTown)
}
})
}

Vue VeeValidate - How to handle exception is custom validation

I have a custom validation in VeeValidate for EU Vat Numbers. It connects to our API, which routes it to the VIES webservice. This webservice is very unstable though, and a lot of errors occur, which results in a 500 response. Right now, I return false when an error has occured, but I was wondering if there was a way to warn the user that something went wrong instead of saying the value is invalid?
Validator.extend('vat', {
getMessage: field => 'The ' + field + ' is invalid.',
validate: async (value) => {
let countryCode = value.substr(0, 2)
let number = value.substr(2, value.length - 2)
try {
const {status, data} = await axios.post('/api/euvat', {countryCode: countryCode, vatNumber: number})
return status === 200 ? data.success : false
} catch (e) {
return false
}
},
}, {immediate: false})
EDIT: Changed code with try-catch.
You can use:
try {
your logic
}
catch(error) {
warn user if API brokes (and maybe inform them to try again)
}
finally {
this is optional (you can for example turn of your loader here)
}
In your case try catch finally block would go into validate method
OK, first of all I don't think that informing user about broken API in a form validation error message is a good idea :-| (I'd use snackbar or something like that ;) )
any way, maybe this will help you out:
I imagine you are extending your form validation in created hook so maybe getting message conditionaly to variable would work. Try this:
created() {
+ let errorOccured = false;
Validator.extend('vat', {
- getMessage: field => 'The ' + field + ' is invalid.',
+ getMessage: field => errorOccured ? `Trouble with API` : `The ${field} is invalid.`,
validate: async (value) => {
let countryCode = value.substr(0, 2)
let number = value.substr(2, value.length - 2)
const {status, data} = await axios.post('/api/euvat', {countryCode: countryCode, vatNumber: number})
+ errorOccured = status !== 200;
return status === 200 ? data.success : false;
},
}, {immediate: false})
}
After searching a lot, I found the best approach to do this. You just have to return an object instead of a boolean with these values:
{
valid: false,
data: { message: 'Some error occured.' }
}
It will override the default message. If you want to return an object with the default message, you can just set the data value to undefined.
Here is a veeValidate v3 version for this:
import { extend } from 'vee-validate';
extend('vat', async function(value) {
const {status, data} = await axios.post('/api/validate-vat', {vat: value})
if (status === 200 && data.valid) {
return true;
}
return 'The {_field_} field must be a valid vat number';
});
This assumes your API Endpoint is returning json: { valid: true } or { valid: false }

Fetching data as reaction to observable array change in MobX

Suppose we have an observable main object array, and observable data about that array (e.g. suppose we have selectedReports and reportParameters) . Now suppose we emit action to either add report to the array or remove report from that array. How do we run an action to fetch the data for reportParameters, as reaction?
Thus far, my attempt, which isn't working, looks like this:
// report parameters stuff
async fetchAllReportParameters() {
reaction(
() => this.selectedReports,
async (reports) => {
// reset the report parameters
this.reportParameters = {}
// fetch the parameters for all the reports
await reports
.forEach((report) => {
this.fetchReportParameters(report.Id)
})
}
)
}
/**
* fetches report parameters for a reportId
* #param {number} reportId
*/
fetchReportParameters = (reportId) => {
this.reportParameters[reportId] = []
const onSuccess = (reportParameters) => {
this.reportParameters[reportId] = reportParameters
}
this.api.GetReportParameters(reportId)
.then(onSuccess, this.fetchReportParametersError)
}
fetchReportParametersError = (error) => {
// TODO: output some error here
}
Are you ever actually calling fetchAllReportParameters? If you don't, the reaction will never be created. You may instead like to create the reaction from the constructor, assuming you always want it to be run. One example:
class SomeStore {
constructor() {
this.disposeReportsReaction = reaction(
() => this.selectedReports.slice(),
reports => {
// ...
}
)
}
}
Call storeInstanceName.disposeReaction() whenever you're done with the reaction.
Notice that I've used .slice() here. This is because if you simply pass the array reference, the reaction will never be called. See reaction docs: you have to actually use the value in some way.
You also need to tweak the async code a bit. This:
async (reports) => {
await reports.forEach((report) => {
// ...
})
}
won't do what you hope, because forEach returns undefined. Even if you shift the async keyword to the forEach callback, all the API requests will be sent in quick succession. Consider using something like this instead, depending on whether you want to wait for the preceding request before sending the next one:
try {
for (const report of reports) {
await this.fetchReportParameters(report.id)
}
} catch (e) {
// handle error
}
This isn't always the right answer: sometimes it's fine to send a bunch of requests in quick succession (perhaps especially if it's a small batch, and/or in the context of HTTP/2). If that's ok with you, you could use:
reports => {
// ...
reports.forEach(report => this.fetchReportParameters(report.id))
}

Convert variable stored in AsyncStorage then state to Number

How can I convert an AsyncStorage stored number (stored as a string) to a Number in react native (I am using Expo).
It works converting the string to a number like this:
myNum = Number('3'); // THIS WORKS
But this does not work:
myNum = Number(this.state.myStateNum); // This does not work, returns zero
myNum = Math.floor(this.state.myStateNum); // This does not work, returns zero
I am getting my variable from AsyncStorage and then storing as a state but I can't figure out why I can't convert it to a number.
If it helps this is how I set using async:
async function setItem(key, value) {
try {
await AsyncStorage.setItem(key, value);
console.log(key, value);
return value;
} catch (error) {
}
}
setVar = setItem('myStateNum', '3');
This is how I get using async:
async componentWillMount() {
const myStateNumGet = await AsyncStorage.getItem('myStateNum');
if (myStateNumGet) {
this.setState({ myStateNum: myStateNumGet });
} else {
this.setState({ myStateNum: false });
}
}
Any suggestions/help would be great.
Try this...
myNum = parseInt(this.state.myStateNum);
You can try other methods too, but the above should work for you...
https://coderwall.com/p/5tlhmw/converting-strings-to-number-in-javascript-pitfalls
This is because ASyncStorage.getItem() returns a Promise object, not the string that you are expecting.
You can find some more info about that here: https://facebook.github.io/react-native/releases/0.23/docs/asyncstorage.html
In terms of your problem, i'd structure your code to accept a callback to getItem, that way you can ensure a returned value before you do your computation. The code would look something like this.
AsyncStorage.getItem("myStateNum").then((result) => {
console.log(result);
this.setState({myStateEnum: result});
}).done();
You could also try using .value to get your result, eg let myStateNum = result.value. Am fairly rusty on the subject but these methods should be able to help you
Let me know if this helps.
this is a duplicate of the question here https://forums.expo.io/t/convert-variable-stored-in-asyncstorage-then-state-to-number/3430