Search code examples
ember.jsember-data

Ember - object related item lost on page reload


I have Order and PaymentMethod models in Ember with a many-to-one relationship like so:

order.js

export default DS.Model.extend({
    paymentStatus: DS.attr('number', { defaultValue: 1 }),
    paymentMethod: DS.belongsTo('payment-method'),
})

payment-method.js

export default DS.Model.extend({
    name: DS.attr('string'),
    orderPaymentMethods: DS.hasMany('order')
})

The order is saved to the database - all fine. I then have a route review-order, which obtains the order model (my API is only returning order models for the current logged in user)

review-order.js

export default Route.extend({
    model() {
        const order = this.store.findAll('order', { include: 'payment_method' }).then((orders) => {
           return orders.filterBy('paymentStatus', 1).get('lastObject');
       });
       return order;
   }
});

In my review-order template, I am printing out the payment method relationship like so

{{model.paymentMethod.name}}

This works fine I transition to the review-order route after the order is created - it prints the name of the payment method. However, if I refresh the review-order page, this is lost, and I have no idea why!


Solution

  • thanks for the full and detailed question! I am going to attempt an answer but I don't have 100% of the information that I would need to be sure this will answer the issue for your exact situation but hopefully it will still be useful and you can extract what you need from it.

    The overall problem that you mentioned, that the data is not fully loaded correctly on a refresh, is likely caused by the API response not having all of the information that it needs. If you check the Web Dev Tools on chrome you will be able to verify that this is the case.

    If you open dev tools, open the network tab and find the query that is loading your data then we can verify this. I don't know how familiar you are with dev tools but if you open them and refresh the page you will see all of the requests that the browser made. You can then filter by XHR to find the API query:

    Chrome Dev Tools Network tab

    You will then be able to click on the request in question and use the preview tab to explore the data that is available:

    Chrome Dev Tools Data Preview

    By looking at this data you will be able to verify if the required relationship is set up, and you will also be able to very if the correct data is also included in the includes section of the JSON:API response. For an example of what this should look like you can check out the examples page for the official JSON:API documentation

    We spent a little bit of time trying to replicate your issue using Mirage so that we could show the exact problem that you were having. It is a bit of a toy example but it does replicate the error and shows how the method I have described above fixes the issue.

    Firstly I replicated your models exactly as you described them, then I created a Mirage endpoint to handle requests to get a single order:

      this.get('/orders/:id', (schema, request) => {
        let responseData = {
          data: {
            id: request.params.id,
            type: 'orders',
            attributes: { 'payment-status': 1 },
            relationships: {
              "payment-method": {
                data: { id: "42", type: "payment-methods" }
              }
            }
          }
        };
    
        if(request.queryParams.include === "payment_method") {
          responseData.included = [{
            type: "payment-methods",
            id: "42",
            attributes: { name: "Credit Card" }
          }];
        }
    
        return responseData;
      });
    

    You will see that if the queryParams includes ?include=payment_method it makes sure that the payment method is included in the response JSON.


    One other thing that I noticed about your example was that your model() hook in the review-order.js route seems a bit unconventional 🤔When I was going through this example I made use of a dynamic segment on the review-order route and made sure that I passed the order object when transitioning to it. Here is my Router definition:

    Router.map(function() {
      this.route('review-order', { path: '/review/:id'});
    });
    

    I then created a simple button with a createOrder() action to simulate creating an order:

      actions: {
        createOrder() {
          let paymentMethod = this.store.createRecord('payment-method', {
            name: 'Credit Card'
          });
    
          let order = this.store.createRecord('order', {
            id: 'not-null',
            paymentStatus: 1,
            paymentMethod: paymentMethod,
          });
    
          // we don't actually save because I don't have a backend setup correctly
          // order.save().then(() => { })
    
          // when we're done transition to review route with the model
          this.transitionToRoute('review-order', order);
        }
      }
    

    As you can see, I am including the order object in the transitionToRoute() call.

    Then we can simplify the model() hook in your route a little bit:

      model(params) {
        return this.store.findRecord('order', params.id, { include: 'payment_method'})
      }
    

    I hope that helps you figure out your issue 😄


    This question was answered as part of "May I Ask a Question" Season 2 Episode 4. If you would like to see us discuss this answer in full you can check out the video here: https://www.youtube.com/watch?v=W1udsGt8F9g