How to create a computed based on another computed from composable - vue-composition-api

I'm learning composition API doing a simple todo app but then with multiple swim-lanes (todo, doing, done).
in useTasks.js composable I create "global" sourceTasks reactive, that later gets filled with data pulled from an API. Then it is reduced in tasks computed property, like this:
// useTasks.js
const sourceTasks = reactive({
list: []
});
export default function useTasks() {
const tasks = computed(() => {
return sourceTasks.list.reduce(divideIntoSwimLanes, [])
});
...
return {
tasks,
loadTasks,
createTask
}
}
Nothing too complicated.
Then I've got this SwimLane component, that well... uses the tasks :)
// SwimLane.vue - setup
async setup(props) {
const { status } = toRefs(props);
const { tasks, createTask } = useTasks();
return {
tasks,
label,
createTask
}
}
// SwimLane.vue - template
<single-task class="m-3" v-for="task in tasks[status]" :title="task.title" :id="task.id"/>
This works, but I don't find it elegant. I would prefer to create a new computed inside of SwimLane's setup, that holds the value of tasks for the given SwimLane. Putting it in the template obscures the logic.
I would expect this to work, but it does not, I think it loses the reactivity but I cant wrap my head around why:
// SwimLane.vue - alternative setup
const currentContextTasks = computed(() => {
return tasks.value[status]
});
return {
currentContextTasks
}
The problem feels a bit ridiculous, but my main concern is that I have misunderstood some core concept, hence this lengthy post.

This is like a biggest blunders ever. So right, the post was really helpful as a form of talking to the rubber duck.
What I did forgot to do is use the value of the status. Since it is a ref, I had to use it as follows:
const currentContextTasks = computed(() => {
return tasks.value[status.value] // 'status.value' and not just 'status'
});

Related

Cypress returning Synchronous value within Async command?

So I think this is probably me mixing up sync/async code (Mainly because Cypress has told me so) but I have a function within a page object within Cypress that is searching for customer data. I need to use this data later on in my test case to confirm the values.
Here is my function:
searchCustomer(searchText: string) {
this.customerInput.type(searchText)
this.searchButton.click()
cy.wait('#{AliasedCustomerRequest}').then(intercept => {
const data = intercept.response.body.data
console.log('Response Data: \n')
console.log(data)
if (data.length > 0) {
{Click some drop downdowns }
return data < ----I think here is the problem
} else {
{Do other stuff }
}
})
}
and in my test case itself:
let customerData = searchAndSelectCustomerIfExist('Joe Schmoe')
//Do some stuff with customerData (Probably fill in some form fields and confirm values)
So You can see what I am trying to do, if we search and find a customer I need to store that data for my test case (so I can then run some cy.validate commands and check if the values exist/etc....)
Cypress basically told me I was wrong via the error message:
cy.then() failed because you are mixing up async and sync code.
In your callback function you invoked 1 or more cy commands but then
returned a synchronous value.
Cypress commands are asynchronous and it doesn't make sense to queue
cy commands and yet return a synchronous value.
You likely forgot to properly chain the cy commands using another
cy.then().
So obviously I am mixing up async/sync code. But since the return was within the .then() I was thinking this would work. But I assume in my test case that doesn't work since the commands run synchronously I assume?
Since you have Cypress commands inside the function, you need to return the chain and use .then() on the returned value.
Also you need to return something from the else branch that's not going to break the code that uses the method, e.g an empty array.
searchCustomer(searchText: string): Chainable<any[]> {
this.customerInput.type(searchText)
this.searchButton.click()
return cy.wait('#{AliasedCustomerRequest}').then(intercept => {
const data = intercept.response.body.data
console.log('Response Data: \n')
console.log(data)
if (data.length) {
{Click some drop downdowns }
return data
} else {
{Do other stuff }
return []
}
})
}
// using
searchCustomer('my-customer').then((data: any[]) => {
if (data.length) {
}
})
Finally "Click some drop downdowns" is asynchronous code, and you may get headaches calling that inside the search.
It would be better to do those actions after the result is passed back. That also makes your code cleaner (easier to understand) since searchCustomer() does only that, has no side effects.
you just need to add return before the cy.wait
here's a bare-bones example
it("test", () => {
function searchCustomer() {
return cy.wait(100).then(intercept => {
const data = {text: "my data"}
return data
})
}
const myCustomer = searchCustomer()
myCustomer.should("have.key", "text")
myCustomer.its("text").should("eq", "my data")
});

How do I refactor a traditional synchronous loop with RxJS?

I'm new to RxJS and trying to wrap my brain around how I should be writing my code. I'm trying to write a function that extends an existing http which returns an observable array of data. I'd like to then loop over the array and make an http request on each object and return the new array with the modified data.
Here's what I have so far:
private mapEligibilitiesToBulk(bulkWarranties: Observable<any[]>): Observable<IDevice[]> {
const warranties: IDevice[] = [];
bulkWarranties.subscribe((bulk: any[]) => {
for (let warranty of bulk) {
// Check if another device already has the information
const foundIndex = warranties.findIndex((extended: IDevice) => {
try {
return warranty.device.stockKeepingId.equals(extended.part.partNumber);
} catch (err) {
return false;
}
});
// Fetch the information if not
if (foundIndex > -1) {
warranty.eligibilityOptions = warranties[foundIndex];
} else {
this.getDevices(warranty.device.deviceId.serialNumber).subscribe((devices: IDevice[]) => {
warranty = devices[0];
}); // http request that returns an observable of IDevice
}
warranties.push(warranty);
}
});
return observableOf(warranties);
}
Currently, my code returns an observable array immediately, however, its empty and doesn't react the way I'd like. Any advice or recommended reading would be greatly appreciated!
Without knowing a lot more about your data and what would make sense, it is impossible to give you the exact code you would need. However, I made some assumptions and put together this StackBlitz to show one possible way to approach this. The big assumption here is that the data is groupable and what you are actually trying to achieve is making only a single http call for each unique warranty.device.stockKeepingId.
I offer this code as a starting point for you, in the hopes it gets you a little closer to what you are trying to achieve. From the StackBlitz, here is the relevant method:
public mapEligibilitiesToBulk(bulk: Warranty[]): Observable<IDevice[]> {
return from(bulk).pipe(
tap(warranty => console.log('in tap - warranty is ', warranty)),
groupBy(warranty => warranty.device.stockKeepingId),
mergeMap(group$ => group$.pipe(reduce((acc, cur) => [...acc, cur], []))),
tap(group => console.log('in tap - group is ', group)),
concatMap(group => this.getDevices(group[0].device.deviceId.serialNumber)),
tap(device => console.log('in tap - got this device back from api: ', device)),
toArray()
)
}
A couple of things to note:
Be sure to open up the console to see the results.
I changed the first parameter to an array rather than an observable, assuming you need a complete array to start with. Let me know if you want this to extend an existing observable, that is quite simple to achieve.
I put in some tap()s so you can see what the code does at two of the important points.
In the StackBlitz currently the getDevices() returns the same thing for every call, I did this for simplicity in mocking, not because I believe it would function that way. :)

Rxjs Observable.take(1) vs Subscription.unsubscribe()

Is there any differences between
Observable.pipe(take(1)).subscribe(...)
vs
const subscription = Observable.subscribe(() => {
// Do something, then
subscription.unsubscribe()
})
The take(1) approach has a number of advantages over subscribe:
Code readability (and elegance).
The second approach requires that you hold and manage extra variables.
The second approach will not invoke the complete handler. This is because .take(1) actually create a new observable which potentially yields a single item and completes.
The second approach will work for the trivial case of taking a single element, but if you need to take more then 1, take(4) will stay simple while the second approach will become hard to code.
The 3rd item is the rxjs related one, the others relate to coding style.
Have a look at a sample here.
In Angular2, I find myself using both paradigms.
The first makes the most sense inside of a method, where as the second is better used in a constructor, with a cleanup in the deconstructor.
doThing(){
this.store.select('thing').pipe(take(1))
.subscribe(item => {
otherMethod(item)
});
}
vs
class SomeClass{
public val;
private sub;
constructor(){
this.sub = this.store.select('thing')
.subscribe(item => {
this.val = item
});
}
ngDestroy() {
this.sub.unsubscribe()
}
}

Converting callback hell to observable chain

I have been working with a convention where my functions return observables in order to achieve a forced sequential series of function calls that each pass a returned value to their following "callback" function. But After reading and watching tutorials, it seems as though I can do this better with what I think is flatmap. I think I am close with this advice https://stackoverflow.com/a/34701912/2621091 though I am not starting with a promise. Below I have listed and example that I am hoping for help in cleaning up with advice on a nicer approach. I am very grateful for help you could offer:
grandparentFunction().subscribe(grandparentreturnobj => {
... oprate upon grandparentreturnobj ...
});
grandparentFunction() {
let _self = this;
return Observable.create((observer) => {
...
_self.parentFunction().subscribe(parentreturnobj => {
...
_self.childFunction( parentreturnobj ).subscribe(childreturnobj => {
...
observer.next( grandparentreturnobj );
observer.complete();
});
});
});
}
parentFunction() {
let _self = this;
return Observable.create((observer) => {
...
observer.next( parentreturnobj );
observer.complete();
}
}
childFunction() {
let _self = this;
return Observable.create((observer) => {
...
observer.next( childreturnobj );
observer.complete();
}
}
The general rule-of-thumb in RxJS is that you should really try to avoid creating hand-made, custom Observables (i.e., using Observable.create()) unless you know what you're doing, and can't avoid it. There are some tricky semantics that can easily cause subtle problems if you don't have a firm grasp of the RxJS 'contract', so it's usually better to try to use an existing Observable creation function. Better yet, create Observables via applying operators on an existing Observable, and return that.
In terms of specific critiques of your example code, you're right that you should be using .flatMap() to create Observable function chains. The nested Observable.create()s you currently have are not very Rx-like, and suffer from the same problems 'callback hell'-style code has.
Here's an example of doing the same thing your example does, but in a more idiomatic Rx style. doStuff() is our asynchronous function that we want to create. doStuff() needs to call the asynchronous function step1(), chain its result into the asynchronous function step2(), then do some further operations on the result, and return the final result to doStuff()'s caller.
function doStuff(thingToMake) {
return step1(thingToMake)
.flatMap((step1Result) => step2(step1Result))
.map((step2Result) => {
let doStuffResult = `${step2Result}, and then we're done`;
// ...
return doStuffResult;
});
}
function step1(thingToMake) {
let result = `To make a ${thingToMake}, first we do step 1`;
// ...
return Rx.Observable.of(result);
}
function step2(prevSteps) {
let result = `${prevSteps}, then we do step 2`
// ...
return Rx.Observable.of(result);
}
doStuff('chain').subscribe(
(doStuffResult) => console.log(`Here's how you make a chain: ${doStuffResult}`),
(err) => console.error(`Oh no, doStuff failed!`, err),
() => console.debug(`doStuff is done making stuff`)
)
Rx.Observable.of(x) is an example of an existing Observable creator function. It just creates an Observable that returns x, then completes.

Sequelize correctly executing multiple creates + updates

I have a cron job that scrapes a list of items on a website and then inserts or updates records in a database. When I scrape the page, I want to create records for new ones that haven't been created yet, otherwise update any existing ones. Currently I'm doing something like this:
// pretend there is a "Widget" model defined
function createOrUpdateWidget(widgetConfig) {
return Widget.find(widgetConfig.id)
.then(function(widget) {
if (widget === null) {
return Widget.create(widgetConfig);
}
else {
widget.updateAttributes(widgetConfig);
}
});
}
function createOrUpdateWidgets(widgetConfigObjects) {
var promises = [];
widgetConfigObjects.forEach(function(widgetConfig) {
promises.push(createOrUpdateWidget(widgetConfig));
});
return Sequelize.Promise.all(promises);
}
createOrUpdateWidgets([...])
.done(function() {
console.log('Done!');
});
This seems to work fine, but I'm not sure if I'm doing this "correctly" or not. Do all promises that perform DB interactions need to run serially, or is how I have them defined ok? Is there a better way to do this kind of thing?
What you're doing is pretty idiomatic and perfectly fine, the only room for improvement is to utilize the fact Sequelize uses Bluebird for promises so you get .map for free, which lets you convert:
function createOrUpdateWidgets(widgetConfigObjects) {
var promises = [];
widgetConfigObjects.forEach(function(widgetConfig) {
promises.push(createOrUpdateWidget(widgetConfig));
});
return Sequelize.Promise.all(promises);
}
Into:
function createOrUpdateWidgets(widgetConfigObjects) {
return Sequelize.Promise.map(widgetConfig, createOrUpdateWidget)
}
Other than that minor improvement - you're chaining promises correctly and seem to have the correct hang of it.

Resources