Getting a payload after calling 'update' method of CartEntryConnector - spartacus-storefront

I used 'updateEntry' method of ActiveCartService for updating the entry of the Cart. After then 'updateEntry$' effect from the CartEntryEffects class was triggered that returned new action.
updateEntry$: Observable = this.actions$.pipe(
ofType(CartActions.CART_UPDATE_ENTRY),
map((action: CartActions.CartUpdateEntry) => action.payload),
concatMap(payload =>
this.cartEntryConnector
.update(payload.userId, payload.cartId, payload.entry, payload.qty)
// should be my logic with payload
.pipe(
map(() => {
return new CartActions.CartUpdateEntrySuccess({
userId: payload.userId,
cartId: payload.cartId,
});
}),
Which is the proper way to get this payload?
Or can I override this effect or add my logic to it?

It depends what you are trying to do with the payload.
If you want to process it before the details get updated into Commerce, then that is generally handled by connectors & adapters. See https://sap.github.io/spartacus-docs/connecting-to-other-systems/
If you want to reference the updated entry & display it, then you can get hold of an Observable via the ActiveCartService. See for example the AddToCart component (https://sap.github.io/spartacus/components/AddToCartComponent.html#source) which declares cartEntry$: Observable<OrderEntry> and then in ngOnInit() does this.cartEntry$ = this.activeCartService.getEntry(this.productCode)

Related

Understanding then() in Cypress

I am reading through the documentation in Cypress and I think I have an idea as to what then() does. It works like promises, where a promise returns another promise, but with then(), we are returning a new subject.
If we look at the code example below, we are using then() because we are returning a new variable, which in this case is called target.
Am I understanding this correctly? If not, can someone correct me?
it.only('Marks an incomplete item complete', () => {
//we'll need a route to stub the api call that updates our item
cy.fixture('todos')
.then(todos => {
//target is a single todo, taken from the head of the array. We can use this to define our route
const target = Cypress._.head(todos)
cy.route(
"PUT",
`api/todos/${target.id}`,
//Here we are mergin original item with an object literal
Cypress._.merge(target, {isComplete: true})
)
})
.then is used to receive the results from cy.fixture('todos'). The variable target is not significant in this code.
In your code sample, the variable that is returned from cy.fixture is named todos - the spacing of the code may be throwing you off here? The .then call is attached to the cy.fixture() call
// These 2 code blocks are the same - just different spacing
cy.fixture('todos')
.then(todos => {});
cy.fixture('todos').then(todos => {});
https://docs.cypress.io/api/commands/fixture.html#Usage
cy.fixture('logo.png').then((logo) => {
// load data from logo.png
})
Using .then() allows you to use the yielded subject in a callback function and should be used when you need to manipulate some values or do some actions.
To put it simply, it is used to play around with the yield of the previous command and work around with it in that case. THEN() command is handy and helpful in debugging the yield of the previous command.
const baseURL = "https://jsonplaceholder.typicode.com";
describe("Get Call-Expect+ normal req", () => {
it("GetPostById-Expect", () => {
cy.request(baseURL + "/posts/1").as("GetPostById");
cy.get("#GetPostById").then((response) => {
//response: status
expect(response.status).to.equal(200);
expect(response.status).to.eq(200);
});
});
Refer: https://docs.cypress.io/api/commands/then#Promises

Vue, wait data is assign to run a function

I have a method in my Vue instance that do the following:
submitForm(confirmation) {
//set price confirmation
this.price_confirmation = confirmation
//proceed
var form = this.getForm()
}
Price confirmation is the v-model of an input.
Then the method getForm serialize (with jquery) the form. The thing is that my form is being serialized before this.price_confirmation = confirmation is run.
How can I run this.getForm() after Vue assign the data?
You probably need to use the nextTick method to wait until the next update cycle:
submitForm(confirmation) {
//set price confirmation
this.price_confirmation = confirmation
//proceed
this.$nextTick(() => {
var form = this.getForm();
});
}
From what you've shared, it's not clear how the confirmation is set in your component and at what point the submitForm is called. submitForm method seems to be correct, and should work correctly (i.e. this.getForm() is called after this.price_confirmation is set). What you could do is, look where the confirmation variable is set and add an async function to it. e.g:
async confirmation() {
await getConfirmations();
}
If you need more help please share the relevant code from your component.

Apollo readFragment of optimistic response

What I try to do
I have an app which should work offline.
There is an Item-list. I can add an Item to this list with a mutation.
The update function of the mutation adds the Item to the Item-list. (Optimistic Response)
When I click on an Item, I want to see the details.
My Implementation
Content of Mutation update function:
const queryData = cache.readQuery<{ items: Item[] }>({
query: MY_QUERY,
variables: {
filter
}
});
if (!queryData?.items) {
return;
}
const newData = [...queryData.items, newItem];
cache.writeQuery({
query: MY_QUERY,
data: { items: newData },
variables: {
filter
}
});
Get details of the item in the vue-file:
apolloProvider.clients.defaultClient
.readFragment<Item>({
fragment: ITEM_FRAGMENT,
id:id
});
The problem
Adding the item to the Query-result works fine.
When I try to read the fragment:
I get null for items which were added by the Mutation update function
I get the expected object for items which were fetched from the backend
There is also the optimistic attribute in readFragment, but that doesn't make a difference.
Other observations
When I write and immediately read the fragment in the Mutation update function, I am able to get it.
cache.writeFragment({
fragment: ITEM_FRAGMENT,
data: item,
id: item._id,
});
const data = cache.readFragment({
fragment: ITEM_FRAGMENT,
id: item._id,
});
console.log({ data }); // This logs my item Object
Package versions:
{
"#nuxtjs/apollo": "^4.0.1-rc.3",
"apollo-cache-persist": "^0.1.1",
"nuxt": "^2.0.0",
}
Summary
apollo.readFragement doesn't work for values from an optimistic response.
Maybe someone here has an idea of what I am missing, or a different approach to implement this functionality
To get optimistic values from apollo cache you need to add true as second parameter in your readFragment call.
readFragment(options, optimistic)
(optimistic is false by default.)
In your case:
apolloProvider.clients.defaultClient
.readFragment<Item>({
fragment: ITEM_FRAGMENT,
id:id
}, true);
When creating an optimistic response for a resource that is in the process of being created via a mutation (e.g. your item), we need to assign some kind of temporary id to the optimistic data (see example in the docs). This is because the real resource hasn’t been created yet and we don’t know it’s id.
Given this, to read the optimistic response from the cache we need to use that same temporary id (as well as the __typename). When the mutation completes and we have the real response in the cache the optimistic response is discarded and we can use the real id.
I ran into this recently as I was assigning a temporary id, but not using it to retrieve the optimistic response from the cache to render the updated UI in that brief window where I was waiting for the mutation to complete.

How to populate the store and sequentially await return using Redux Observable?

I am attempting to use Redux Observable to call an action to fetch some data, wait for its return, then fetch some more data that relies on it.
I have an epic which populates a store from a fetch FetchTodos. This listens for the FETCH_TODOS action and then calls my todos API and populates {todos: [] } =
I also have a comments section in my store todoComments. However, I would like to only populate todoComments once FETCH_TODOS has returned and populated the store.
In imperative code, this might look like:
let todos = await api.get('/todos');
await dispatch("FETCH_TODO_COMPLETE", todos)
let firstId = getState().todos[0].id
let comments = await api.get(`/todos/${firstId}/comments')
await dispatch("FETCH_COMMENTS_COMPLETE", { todo_id: firstId, comments})
The closest I saw to this was this issue in the Redux Observable Repo, but I could not understand how to do this efficiently. This is a pretty common scenario for me.
I would like to reuse as much code as possible. In this example, I may dispatch FETCH_TODOS from multiple components.
How would i accomplish this with Redux-Observable?
Based on our conversation in the comments:
In redux-observable, you can sequence things in numerous ways. You could do it all in one epic using normal RxJS, or you could split them into multiple ones. If you split them, the subsequent epic would listen for the signal that the previous one has completed its task. Something like this:
// this assumes you make your `api.get` helper return an Observable
// instead of a Promise which is highly advisable.
// If it doesn't, you could do:
// Observable.from(api.get('/url'))
// but Promises are not truly cancellable which can cause max
// concurrent connections issues
const fetchTodosEpic = action$ =>
action$.ofType('FETCH_TODOS')
.switchMap(() =>
api.get('/todos')
.map(todos => ({
type: 'FETCH_TODOS_COMPLETE',
todos
}))
);
const fetchComments = action$ =>
action$.ofType('FETCH_TODOS_COMPLETE')
.switchMap(({ todos }) =>
api.get(`/todos/${todos[0].id}/comments`)
.map(comments => ({
type: 'FETCH_COMMENTS_COMPLETE',
comments
}))
);

How to use store.filter / store.find with Ember-Data to implement infinite scrolling?

This was originally posted on discuss.emberjs.com. See:
http://discuss.emberjs.com/t/what-is-the-proper-use-of-store-filter-store-find-for-infinite-scrolling/3798/2
but that site seems to get worse and worse as far as quality of content these days so I'm hoping StackOverflow can rescue me.
Intent: Build a page in ember with ember-data implementing infinite scrolling.
Background Knowledge: Based on the emberjs.com api docs on ember-data, specifically the store.filter and store.find methods ( see: http://emberjs.com/api/data/classes/DS.Store.html#method_filter ) I should be able to set the model hook of a route to the promise of a store filter operation. The response of the promise should be a filtered record array which is a an array of items from the store filtered by a filter function which is suppose to be constantly updated whenever new items are pushed into the store. By combining this with the store.find method which will push items into the store, the filteredRecordArray should automatically update with the new items thus updating the model and resulting in new items showing on the page.
For instance, assume we have a Questions Route, Controller and a model of type Question.
App.QuestionsRoute = Ember.Route.extend({
model: function (urlParams) {
return this.get('store').filter('question', function (q) {
return true;
});
}
});
Then we have a controller with some method that will call store.find, this could be triggered by some event/action whether it be detecting scroll events or the user explicitly clicking to load more, regardless this method would be called to load more questions.
Example:
App.QuestionsController = Ember.ArrayController.extend({
...
loadMore: function (offset) {
return this.get('store').find('question', { skip: currentOffset});
}
...
});
And the template to render the items:
...
{{#each question in controller}}
{{question.title}}
{{/each}}
...
Notice, that with this method we do NOT have to add a function to the store.find promise which explicitly calls this.get('model').pushObjects(questions); In fact, trying to do that once you have already returned a filter record array to the model does not work. Either we manage the content of the model manually, or we let ember-data do the work and I would very much like to let Ember-data do the work.
This is is a very clean API; however, it does not seem to work they way I've written it. Based on the documentation I cannot see anything wrong.
Using the Ember-Inspector tool from chrome I can see that the new questions from the second find call are loaded into the store under the 'question' type but the page does not refresh until I change routes and come back. It seems like the is simply a problem with observers, which made me think that this would be a bug in Ember-Data, but I didn't want to jump to conclusions like that until I asked to see if I'm using Ember-Data as intended.
If someone doesn't know exactly what is wrong but knows how to use store.push/pushMany to recreate this scenario in a jsbin that would also help too. I'm just not familiar with how to use the lower level methods on the store.
Help is much appreciated.
I just made this pattern work for myself, but in the "traditional" way, i.e. without using store.filter().
I managed the "loadMore" part in the router itself :
actions: {
loadMore: function () {
var model = this.controller.get('model'), route = this;
if (!this.get('loading')) {
this.set('loading', true);
this.store.find('question', {offset: model.get('length')}).then(function (records) {
model.addObjects(records);
route.set('loading', false);
});
}
}
}
Since you already tried the traditional way (from what I see in your post on discuss), it seems that the key part is to use addObjects() instead of pushObjects() as you did.
For the records, here is the relevant part of my view to trigger the loadMore action:
didInsertElement: function() {
var controller = this.get('controller');
$(window).on('scroll', function() {
if ($(window).scrollTop() > $(document).height() - ($(window).height()*2)) {
controller.send('loadMore');
}
});
},
willDestroyElement: function() {
$(window).off('scroll');
}
I am now looking to move the loading property to the controller so that I get a nice loader for the user.