Reading Data

Working with JSON:API data is split into two parts to follow Vuex conventions:

  • Actions are used to request data from the server or update data on the server, storing the results into the module's state.
  • Getters are used to access data from the module's state.

loadAll action / all getter

So, for example, to retrieve all of the records for a resource, dispatch the loadAll action to save them into the store. They can then be accessed using all getter:

this.$store.dispatch('widgets/loadAll').then(() => {
  const widgets = this.$store.getters['widgets/all'];
  console.log(widgets);
});

If you're accessing these from within a Vue component, you can use Vuex's mapActions and mapGetters as usual:

import { mapActions, mapGetters } from 'vuex';

export default {
  // ...
  methods: {
    ...mapActions({
      loadWidgets: 'widgets/loadAll',
    }),
  },
  computed: {
    ...mapGetters({
      widgets: 'widgets/all',
    }),
  },
  // ...
};

loadById action / byId getter

To retrieve a single record by ID, dispatch the loadById action, then access the byId getter:

this.$store.dispatch('widgets/loadById', { id: 42 }).then(() => {
  const widget = this.$store.getters['widgets/byId']({ id: 42 });
  console.log(widget);
});

Note that the ID needs to be a string, as specified by the JSON:API spec.

If you know the record has already been retrieved, you don't need to load it again. For example, if you've loaded all records on a list screen, and then you click to view the details for a single record, you can just use the getter directly:

const widget = this.$store.getters['widgets/byId']({ id: '42' });
console.log(widget);

loadWhere action / where getter

To filter/query for records based on certain criteria, use the loadWhere action, passing it an object of filter keys and values to send to the server, then pass those same filters to the where getter:

const filter = {
  category: 'whizbang',
};
this.$store.dispatch('widgets/loadWhere', { filter }).then(() => {
  const widgets = this.$store.getters['widgets/where']({ filter });
  console.log(widgets);
});

This doesn’t perform any filtering logic on the client side; it simply keeps track of which IDs were returned by the server side request and retrieves those records.

loadPage action / page getter

If your API supports pagination, you can request paginated data using the loadPage action. JSON:API reserves the page query parameter for pagination parameters, but doesn't define which specific parameters are used; pass whichever keys your server expects. You can access the returned page of data via the page getter:

const options = {
  'page[size]': 10,
  'page[number]': 2,
};
this.$store.dispatch('widgets/loadPage', { options }).then(() => {
  const widgets = this.$store.getters['widgets/page'];
  console.log(widgets);
});

Servers can optionally return next and prev pagination links. If they are available, the hasNext and hasPrevious getters respectively will return true. You can load the next or previous page using the loadNextPage or loadPreviousPage actions:

this.$store.dispatch('widgets/loadNextPage').then(() => {
  const widgets = this.$store.getters['widgets/page'];
  console.log(widgets);
  this.$store.dispatch('widgets/loadPreviousPage').then(() => {
    const widgets = this.$store.getters['widgets/page'];
    console.log(widgets);
  });
});

To load records related via JSON:API relationships, use the loadRelated action. A nested resource URL is constructed like categories/27/widgets. (In the future we will look into using HATEOAS to let the server tell us the relationship URL).

const parent = {
  type: 'category',
  id: '27',
};

this.$store.dispatch('widgets/loadRelated', { parent }).then(() => {
  const widgets = this.$store.getters['widgets/related']({ parent });
  console.log(widgets);
});

By default, the name of the relationship on parent is assumed to be the same as the name of the other model: in this case, widgets. In cases where the names are not the same, you can explicitly pass the relationship name:

const parent = {
  type: 'categories',
  id: '27',
};

const relationship = 'purchased-widgets';

this.$store
  .dispatch('widgets/loadRelated', { parent, relationship })
  .then(() => {
    const widgets = this.$store.getters['widgets/related']({
      parent,
      relationship,
    });
    console.log(widgets);
  });

Meta Information

When a load response from the server contains a meta key, it is exposed via the meta getter. One way the meta information is sometimes used is to provide pagination information, such as the total number of pages.

Loading and Error States

The status of a loading action is reported in two getters: isLoading and isError.

If the load action errors out, the error response is available in the error getter. Note that only load actions currently expose their error in the error getter; errors for write actions can be accessed by catching the promise returned by the write action.

Options

All actions take an optional options property, consisting of an object of additional options to pass. Each key/value pair in the object is translated into a query string parameter key/value pair:

this.$store.dispatch('widgets/loadAll', {
  options: {
    'fields[widgets]': 'title,description',
  },
});

// requests to widgets?fields[widgets]=title,description

Sometimes you don't want to make separate loadRelated calls for each relationship. For cases like this, Reststate/Vuex supports JSON:API's include property to eagerly load related data. It can be passed to any load action:

this.$store.dispatch('posts/loadAll', {
  options: {
    include: 'category,comments,comments.user',
  }
});

Note that include allows you to specify multiple relationship using the format JSON:API defines: each relationship is separated via a comma, and chained relationships are specified using dots.

Included data will be stored in the Vuex module with the name corresponding to each resource. For example, if the above query returns records of types posts, categories, comments, and users, you would need to have modules like so to store them:

mapResourceModules({
  names: [
    'posts',
    'categories',
    'comments',
    'users',
  ],
  httpClient: api,
}),

Related data loaded via include can be accessed with the related getter, just like related data loaded via loadRelated:

const comments = this.$store.getters['comments/related']({ parent: post });

As with data loaded via loadRelated, if the relationship name is not the same as the name of the resource, you can pass it as an argument:

this.$store.dispatch('posts/loadAll', {
  options: {
    include: 'secretComments',
  }
}).then(() => {
  const secretComments = this.$store.getters['comments/related']({
    parent: post,
    relationship: 'secretComments',
  });
  console.log(widgets);
});