GithubHelp home page GithubHelp logo

reststate / reststate-vuex Goto Github PK

View Code? Open in Web Editor NEW
72.0 72.0 19.0 7.64 MB

Zero-configuration web service client for Vuex

Home Page: https://vuex.reststate.codingitwrong.com

License: Other

JavaScript 95.73% Shell 0.46% HTML 0.66% Vue 3.15%

reststate-vuex's People

Contributors

chilinot avatar codingitwrong avatar daryledesilva avatar dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

reststate-vuex's Issues

How to integrate this with e.g. `vuex-map-fields`?

vuex-map-fields is a popular vuex library which simplifies mapping to v-model to vuex for forms and nested data.

The library relies on each store implementing a supplied getter ('getField`) and mutation ('updateField').

With reststate creating it's own modular store, how would one integrate with a library like this? The only way I can think of is by intercepting the module created by reststate and injecting the additional getter and mutation before passing the reststate-created module to the Vuex store.

In light of your points about it being bad practise for modular stores to be externally manipulated (as discussed here: #270), how would you suggest one should implement such an integration?

Example for paging

Could you provide an example how to do proper pagination?
i tried the following which does not work.

           const page = {
              'size': '100',
            };              
            this.$store.dispatch('sensorDatas/loadWhere', { page })
              .then(() => {
                const sensorData = this.$store.getters['sensorDatas/byId']({ page });
                console.log(sensorData);
              });   

Placing an http response in the Vue store is an anti-pattern

A Vue instance is like a backend inside the frontend. The store represents the DOM of the Vue Single Page Application. The SPA practices CORS (Cross Origin Resource Sharing) with a true backend on whatever domain. The http service responds with a json:api document representing generic information to be rendered in the browser or for other purpose. In both cases it is an anti-pattern to put the json:api document from the http service in the Vue store because the store represents the DOM of the SPA. Instead of that implement a plain Node.js module outside Vue Components and the store to place the json:api response and implement CRUD AJAX procedures. I see the json:api document similar to html labels but representing information for whatever purpose. That document is loaded asynchronously by the SPA but: why to load the document in the store? The store is the DOM (Document Object Model) of the SPA. Components in the SPA may have a detailed DOM through computed properties and getters implemented in the store.
Using a json:api document as the data structure of a Single Page Application is, in itself, an anti-pattern as well.
The programmer of each SPA should chose the data structure that best fit in the Application. When the SPA make an asynchronous http request to a remote http service, the service will respond with an json:api document. But the http service doesn't know the data structure of the consumer Single Page Application. So, to provide a standard data-structure-agnostic response, the Content-Type json:api has been specified. The http service responds with a json:api document that is data-structure-agnostic (unopinionated) regarding data structure of the consuming Single Page Application. So the Content-Type json:api is just a standard agnostic response that every http service should implement.

Loading state concurrency

I notice you use one piece of loading state for multiple load calls (loadAll, loadById etc.). Is this safe when running multiple load calls in parallel? Shouldn't each load call track its own loading state?

A question about the data return format

Thanks for ur work, it's very nice! But I have a question about the data return format. In the Json:api, it has many top-level members, such as data, meta, jsonapi , links, i saw that it only return member data

 commit('STORE_RECORDS', result.data);        

how to use other top-level members. For example, i always return pagination information in the top-level member meta.

How to use another field as id for loadById?

Is there a way to use another field to retrieve a single record from vuex state? I can only use "id" field. Since I have a uuid-field i want to use this. Expected:
this.$store.dispatch('widgets/loadById', { uuid: xyz }).then(() => { const widget = this.$store.getters['widgets/byId']({ uuid: xyz }); console.log(widget); });
Thank You!

Can we have `loadWhere` and foreign field examples?

It would be great to have some examples or documentation that show how to use where and loadWhere. Also how about loadRelatedWhere?

Also lets say I have roles and people and I have a given person record but I want role.name for that person. How would I go about getting that?

E.g. I have people, enquiries, roles. There's a pivot table enquiries_contacts which links these three entities. Now I'm getting a list of enquiries related to a people record, which I can do via related getter but I want to group the results by roles using the role.name attribute. How could I achieve this?

Tutorial enhancement and bug: PATCH doesn't work

Hi,

The tutorial doesn't seem to cover PATCH. It would be great to see:

  1. a simple PATCH request that edits a restaurant
  2. a PATCH request that adds / removes related data to / from a restaurant

I notice that there's some information in at the end of the readme, but it doesn't seem to work. I get errors.
Here's my code:

computed: {
    restaurantById: "restaurants/byId"
},
methods:{
      ...mapActions({updateRestaurant: "restaurants/update"}),
      async handleUpdateRestaurant() {
        const restaurant = this.restaurantById({ id: this.id });
        restaurant.attributes.name = _.clone(this.name);
        restaurant.attributes.address = _.clone(this.address);
        const response = await this.updateRestaurant(restaurant);
        this.clearRestaurant();
        return response;
      },
}

This is as per the docs, however it generates the following errors:

[Vue warn]: Error in callback for watcher "function () { return this._data.$$state }": "Error: [vuex] do not mutate vuex store state outside mutation handlers."

(found in <Root>)
vue.runtime.esm.js?5593:1888 Error: [vuex] do not mutate vuex store state outside mutation handlers.
    at assert (vuex.esm.js?94e4:94)
    at Vue.store._vm.$watch.deep (vuex.esm.js?94e4:834)
    at Watcher.run (vue.runtime.esm.js?5593:4568)
    at Watcher.update (vue.runtime.esm.js?5593:4542)
    at Dep.notify (vue.runtime.esm.js?5593:730)
    at Object.reactiveSetter [as name] (vue.runtime.esm.js?5593:1055)
    at _callee4$ (poc.vue?e5e6:281)
    at tryCatch (runtime.js?ad1f:45)
    at Generator.invoke [as _invoke] (runtime.js?ad1f:274)
    at Generator.prototype.<computed> [as next] (runtime.js?ad1f:97)
xhr.js?e38e:160 PATCH https://sandbox.howtojsonapi.com/restaurants/285 400 (Bad Request)
vue.runtime.esm.js?5593:619 [Vue warn]: Error in v-on handler (Promise/async): "[object Object]"

found in

---> <POC> at src/pages/poc.vue
       <QPageContainer>
         <QLayout>
           <DefaultLoggedIn> at src/layouts/DefaultLoggedIn.vue
             <App> at src/App.vue
               <Root>
vue.runtime.esm.js?5593:1888 
{data: {…}, status: 400, statusText: "Bad Request", headers: {…}, config: {…}, …}
config: {transformRequest: {…}, transformResponse: {…}, timeout: 0, xsrfCookieName: "XSRF-TOKEN", adapter: ƒ, …}
data:
errors: Array(1)
0:
code: "105"
detail: "links is not allowed."
status: "400"
title: "Param not allowed"
__proto__: Object
length: 1
__proto__: Array(0)
__proto__: Object
headers:
cache-control: "no-cache"
content-type: "application/vnd.api+json"
__proto__: Object
request: XMLHttpRequest {readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, onreadystatechange: ƒ, …}
status: 400
statusText: "Bad Request"
__proto__: Object

I also tried it a different way; creating a new object without links or relationship (as both these properties are rejected by the HowToJSON server):

async handleUpdateRestaurant() {
        const rVuex = this.restaurantById({ id: this.id });
        const nuR = {
          attributes: {
            name: this.name,
            address: this.address
          },
          id: this.id,
          type: "restaurants"
        };
        const response = await this.updateRestaurant(nuR);
        this.clearRestaurant();
        return response;
      },

This actually works (i.e. the PATCH executes) but also results in a different error:

vuex.esm.js?94e4:438 [vuex] unknown action type: undefined/storeRelated
dispatch @ vuex.esm.js?94e4:438
boundDispatch @ vuex.esm.js?94e4:347
local.dispatch @ vuex.esm.js?94e4:720
eval @ reststate-vuex.js?61e3:335
Promise.then (async)
update @ reststate-vuex.js?61e3:322
wrappedActionHandler @ vuex.esm.js?94e4:792
dispatch @ vuex.esm.js?94e4:457
boundDispatch @ vuex.esm.js?94e4:347
mappedAction @ vuex.esm.js?94e4:1005
_callee4$ @ poc.vue?e5e6:291
tryCatch @ runtime.js?ad1f:45
invoke @ runtime.js?ad1f:274
prototype.<computed> @ runtime.js?ad1f:97
asyncGeneratorStep @ asyncToGenerator.js?a338:5
_next @ asyncToGenerator.js?a338:27
eval @ asyncToGenerator.js?a338:34
F @ _export.js?fe90:36
eval @ asyncToGenerator.js?a338:23
handleUpdateRestaurant @ poc.vue?e5e6:306
_callee3$ @ poc.vue?e5e6:256
tryCatch @ runtime.js?ad1f:45
invoke @ runtime.js?ad1f:274
prototype.<computed> @ runtime.js?ad1f:97
asyncGeneratorStep @ asyncToGenerator.js?a338:5
_next @ asyncToGenerator.js?a338:27
eval @ asyncToGenerator.js?a338:34
F @ _export.js?fe90:36
eval @ asyncToGenerator.js?a338:23
submitRestaurant @ poc.vue?e5e6:267
submit @ poc.vue?ca56:182
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
invoker @ vue.runtime.esm.js?5593:2179
original._wrapper @ vue.runtime.esm.js?5593:6917

I think the bug is here:

// remove old relationships first

Maybe it shouldn't update relationships if the submitted record doesn't contain any? I'm not sure... looking at getRelationshipType it seems to be looking for a data property in the relationships object... but looking at the vuex data loaded from even those restaurants that do have related dishes, none of them have a data node in the relationships property. Seems spurious to me.

A way to fetch current user (/users/me)

I could not find a way to fetch the current user.

I added to the api a custom hook that replaces "me" with the current user id, but then in the client I can't know what is the current id.

Is there something I'm missing?

Response status on Create/Update

Hello,

Is there any way to catch the response coming from the API (without using axios interceptors) in the vuex action? Or at least make the action return a promise with the response coming from the server?

Situation: imagine registering a user, you get success once the action was dispatched not when the response came from the server. Same for other situation.. sending a contact form for example etc

methods: {
sendDataToApi() {
try {
const recordData = {
attributes: this.form.data.attributes
}
this.$store.dispatch('register/create', recordData).then((response) => {
// HERE is not the correct way to dispatch the toast, but rather in the action response
// Here you might want to login the user with the newly created account details etc
this.$toast.success('Your account has been registered!')
})
} catch (error) {
this.$toast.error(error.response.message)
}
},
async handleRegister() {
this.$v.$touch()
if (!this.$v.$invalid) {
try {
await this.sendDataToApi()
} catch (error) {
this.$toast.error(error.response.message)
}
}
}
}
}
</script>

[Feature request] Create related

So far the library seems to support everything we need. Except creating related resources. We have an endpoint like this:

POST /foo/{fooId}/bar

There are two reasons why we'd want this:

  • As per DDD we are only allowed to create the Bar relation through Foo.
  • If we'd first have to create Bar and assign it to Foo, and for whatever reason that fails, we end up with a dangling Bar.

Is this something you would accept as a PR, a new foo/createRelated dispatch?

Why is state a function rather than an object?

The problem with having it as a function is that you can't do clever things like merge the reststate state of a store with other state that you might need to track in the same store because you end up with Webpack errors.

Why not simplify this and make it just an object? I've tested this and it works.

Removing `.attributes` and `.data`

It gets tedious having to type user.attributes.username, user.relationships.todos.data. Could this library restructure the data to the more user-friendly format user.username and user.todos respectively?

Why does GET handle errors but POST, PATCH and DELETE do not?

I notice that when loading (i.e. GET) it always handles the error by catching the error and calling handleError. However for create and update it does not catch the errors. This makes it impossible to write generic code for error handling. Effectively one has to separate requests out according to method and then handle it differently depending on whether it's a GET request or not.

Please can you explain why there is this difference (one might say inconsistency) in approach?

Jest tests give SyntaxError: Cannot use import statement outside a module

Hi,

I've added @restate/restate-vuex to our project and it works perfectly. Unfortunately, our jest tests are failing on the import of the module into the project:

    /app/node_modules/@reststate/vuex/index.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import { resourceModule, mapResourceModules } from './src/reststate-vuex';
                                                                                             ^^^^^^

    SyntaxError: Cannot use import statement outside a module

      1 | import getters from './getters'
      2 | import httpClient from '@/services/JsonAPIService'
    > 3 | import { mapResourceModules } from '@reststate/vuex';
        | ^
      4 | import merge from 'lodash/merge'
      5 | 
      6 | const modules = merge(

      at ScriptTransformer._transformAndBuildScript (node_modules/jest-runtime/build/script_transformer.js:403:17)
      at Object.<anonymous> (src/state/modules/portfolios/index.js:3:1)

Since the app builds w/o errors I'm assuming I have something wrong in the jest configuration and was hoping you might have some pointers on property configuring jest for this library.

jest.config.js

module.exports = {
  moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
  transform: {
    '^.+\\.vue$': 'vue-jest',
    '.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$':
      'jest-transform-stub',
    '^.+\\.jsx?$': 'babel-jest'
  },
  transformIgnorePatterns: ['/node_modules/'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1'
  },
  snapshotSerializers: ['jest-serializer-vue'],
  testMatch: [
    '<rootDir>/(tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx))'
  ],
  collectCoverageFrom: ["**/*.{js,vue}", "!**/node_modules/**", "!**/tests/**", "!**/coverage/**", "!*config.js"],
}

Thanks for any assistance!

How to map v-model to relationships?

Let's say I have an entity enquiry that is related to another entity person. So now I have an enquiry edit form where I want to show a select option of people and show which person is currently selected as being associated with the enquiry.

How would one handle that from a v-model perspective? One can't map enquiry.attributes.person_id because that doesn't exist. To get the related person one would have to do this.$store.getters[person/related]({parent: enquiryEntity}).

I'm guessing one would have to create a getter / setter mapping like:

<template lang="pug">
div
    select(v-model="person_id")
        options(v-for="person in people" value="person.id") {{person.attributes.name}} 
</template>
<script>
computed: {
    person_id: {
        get() { return this.$store.getters[`person/related`]({parent: enquiryEntity})[0].id },
        set(val) {
             this.$store.commit('STORE_RELATED', { params: {relationship: 'person'}, [val] })
        }
    }
}

Is that even correct? Is there an easier way?

What's extra confusing is that in the case of a form for handling a new enquiry. JSONAPI is perfectly happy having person_id within attributes. This doesn't break the spec and will create the required association on the back-end.

However when retrieving that record via JSONAPI person_id will not be present in attributes.

Support Compound Documents

It would be nice if the library could support compound documents and automatically restructure the data so that I can do user.todos if user is my primary resource with included todo resources.

Why are these mutations also wrapped as actions?

I notice this:

delete({ commit }, record) {

      delete({ commit }, record) {
        return client.delete(record).then(() => {
          commit("REMOVE_RECORD", record);
        });
      },

      storeRecord({ commit }, record) {
        commit("STORE_RECORD", record);
      },

      storeRelated({ commit }, { relatedIds, params }) {
        commit("STORE_RELATED", {
          relatedIds,
          params
        });
      },

      removeRecord({ commit }, record) {
        commit("REMOVE_RECORD", record);
      },

      resetState({ commit }) {
        commit("RESET_STATE");
      },

These just wrap mutations. Why have them? I think it's confusing. Rather keep actions as things that interact with the back-end and mutations as mutations.

I imagine this was done to avoid using ...mapMutations but I think it detracts from clarity and makes things less explicit.

Related getter returns old data after loadRelated options change

It seems that with the changes on how the library deals with relationships in version 0.0.13 something broke that causes data to be not updated when it's needed.

Or more concretely, an example:

    computed: {
        products() {
            const products = this.$store.getters['products/related']({
                parent: {
                    id: this.organisationId,
                    type: 'organisation'
                }
            }) || []
            console.log(products)
            return products.map(({attributes}) => attributes)
        }
    },
    methods: {
        loadProducts() {
            this.$store.dispatch('products/loadRelated', {
                parent: {
                    id: this.organisationId,
                    type: 'organisation'
                },
                options: {
                    page: this.currentPage,
                    itemsPerPage: this.itemsPerPage,
                    sort: this.sort
                }
            }).then(() => {
                const meta = this.$store.getters['products/lastMeta']
                this.itemsPerPage = meta.itemsPerPage
                this.totalItems = meta.totalItems
            })
        }
    }

Initially the data gets loaded and getter like normal. But when I want to sort the data by the name property in ascending order I do another request with loadProducts() method, let me receive the correctly sorted data via GET /organisations/1/products?page=1&itemsPerPage=30&sort=name. When I access the data via the getter in the products() computed property, the old data gets returned. Same is true with pagination and itemsPerPage. So in general it seems like changing something in the request parameters isn't detected as new data.

Pagination with filters

I hope this is not an issue but my lack of knowledge how to use this package..

I'm trying to make grid component with laravel, argon (bootsrap-vue) and this package.

Amazing package by the way 😁 sad that it is not maintained any more (maybe readme should include some alternative packages if there is one?)

So, when using loadPage action, there's only pagination data is sent, why not to include filter data? action loadWhere accepts options and filters, but getters.page is empty when using it... is this normal or it can be tweak here?

Make resource modules extendable easily

Currently there doesn't seem to be a proper way to extend a resource module. You can't add your own getters, actions, mutations, etc. when defining the resource modules. I can see that allowing this can cause conflict due to overwrites. What I'm mainly asking is ways to create custom endpoints for things like a getter to retrieve the translated name of a resource, or an action that removes an item from a relationship, something what could be tedious and error prone to rewrite for every view.

It's possible this is less of a feature request and more of a question. Since I suppose this could be done by using the reststate-vuex resource module as a module of a custom module. But that would make for awkward names like this.$store.getters['organisations/organisations/page]. Any ideas?

Fetch "Included"

Hello
Is there any way to fetch included part from jsonapi response?
Thanks

spread operators give problems on mobile devices

Somehow the spread operator '...' used in reststate-vuex.js crashes the javascript. As a test I commented all the lines that have the operator and indeed JS keeps running. I have tested with numerous babel.config files. But nothing, so far, seems to resolve the problem. Strangely enough the spread operators in my own code doesn't break the runtime.

current babel.config.js
module.exports = { presets: [ ['@vue/app', { polyfills: [ 'es6.promise', 'es6.symbol' ] }], ] }

my package.json
{ "name": "www", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" }, "dependencies": { "@reststate/vuex": "^0.1.0", "axios": "^0.19.0", "core-js": "^2.6.5", "jwt-decode": "^2.2.0", "moment": "^2.24.0", "register-service-worker": "^1.6.2", "vue": "^2.6.10", "vue-router": "^3.0.3", "vuex": "^3.0.1" }, "devDependencies": { "@babel/preset-env": "^7.4.5", "@vue/cli-plugin-babel": "^3.8.0", "@vue/cli-plugin-eslint": "^3.8.0", "@vue/cli-plugin-pwa": "^3.8.0", "@vue/cli-service": "^3.8.0", "babel-eslint": "^10.0.1", "eslint": "^5.16.0", "eslint-plugin-vue": "^5.0.0", "node-sass": "^4.9.0", "sass-loader": "^7.1.0", "vue-template-compiler": "^2.6.10" } }


edit: fixed typo

additional: tested on chrome android, chrome iphone, safari iphone

Example doesn't work

Hi,

I fetched via Github Desktop. Ran yarn install then yarn run serve. Error:

yarn run v1.22.4
error Command "serve" not found

Automatic update for related?

Hello there!

I'm a bit confused about a feature.

I've noticed that the state object has each resource mapped to a property (such as "comments") . These properties have other properties such as "records", "related" , "filtered" etc. However , when i create a resource, the related property (on which my computed property depends) is not updated, hence the component is not re-rendering. If i were to use an "all" getter("comments/all") instead, this would work perfectly fine, however, it is not the case in this component.

Is there a way in which i can specify which property should update? I could not seem to find any information related to this in the documentation . Am i missing something or is this an issue? If it's an issue , my team and I would be happy to contribute.

Here is some sample code to illustrate what i mean:

...
methods: {
    ...mapActions({
      createComment: 'comments/create',
      deleteComment: 'comments/delete',
      loadRelatedComments: 'comments/loadRelated',
    }),
   createComment() {
      this.createComment({
        attributes: {
         ...
        },
      });
    },
},
computed: {
    ...mapGetters({
      relatedComments: 'comments/related',
    }),
   parent() {
      return {
        type: this.parentType,
        id: this.parentId,
      };
    },
   comments() {
      return this.relatedComments({ parent: this.parent });
    },
 }

P.S. I know that in this case i could use lastCreated, but I am wondering if there's a way for the component to automatically update (like it does if i use the "comments/all" getter), without my interceding. The same goes for deleting.

Thank you for your time!

Don't include options with value undefined in URL parameters

Below I have a situation where by default the data doesn't have any special sorting applied to it. this.sort is still undefined at this point, yet it passed in the URL as a parameter GET /organisation/1/products?page=1&itemsPerPage=10&sort=undefined. While an argument can be made to still include the falsely values false, 0 and null, I don't think undefined should be included when creating the URL parameters.

    data() {
        return {
            currentPage: 1,
            itemsPerPage: 10,
            sort: undefined
        }
    },

    methods: {
        loadProducts() {
            this.$store.dispatch('products/loadRelated', {
                parent: {
                    id: this.organisationlId,
                    type: 'organisation'
                },
                options: {
                    page: this.currentPage,
                    itemsPerPage: this.itemsPerPage,
                    sort: this.sort
                }
            })
        },

Change npm & GitHub ownership

Hi @CodingItWrong could you please give me ownership? I'd be happy to take this over.

If you could give me ownership over the reststate GitHub account and the npm account I'd be happy to take over, maintain and grow this library. I think that may be better for existing users than me creating a fork that then has to have a new npm registry account etc. (my npm username is the same as my GitHub handle).

Please let me have your thoughts.

the byId getter doesn't work.

For me it looks like the byId getter does not work as expected.
the loadById action seems to work fine, but the getter byId only returns a function not the result:

({ id }) => state.records.find(r => r.id === id)

Integration with browser database (e.g. localforage) for offline access

My application has an offline requirement. The way I'm managing that is I have vuex store plugin which persists the store via localforage when any mutation is run.

However. The question is how to recover data from the browser database. The scenario in which this would apply is when the application is offline, causing the reststate getters to return null and any actions to reject.

My initial solution was merging state when the store is initialised. However, as you've mentioned in other conversations, this breaks the modular nature of the store and it relies on state being an object and not a function.

So is there a recommended way of handling this? I assume that one should run a mutation, rather than a merge. However there isn't a provided mutation that simply replaces the entire store state, so that one can just take the entire store from the localforage copy and load it into the reststate store.

Can we add a mutation to support this? Should just be:

RESTORE: (state, data) => {
   state = data
}

Related getter has an undefined item after deleting the related resource

Setup

We have a simple situation where a resource has many related resource records. Think a blog post and its comments.

We have a Vue component to handle the comments. We pass the post object into the component as a prop called parent for relationship identifier purposes. From there, we have the following snippet:

// Comments.vue
<template>
...
  <div v-for="comment in comments" :key="comment.id">
      <comment :comment="comment" v-on:delete-comment="deleteComment" />
  </div>
...
</template>

<script>
...
computed: {
  comments() {
    return this.$store.getters['comments/related']({ parent: this.parent })
  }
},
methods: {
  loadComments() {
    this.$store.dispatch('comments/loadRelated', {
      parent: this.parent
    })
  },
  deleteComment(comment) {
    this.$store.dispatch('comments/delete', comment)
}
...

Pretty standard setup. We're using the blog post as the parent and retrieving the comments related to it. From there, we have a computed property called comments from the store's getter and that is used to render a v-for of comment components. The comment component will emit a event when the user clicks to delete their comment. To do so, we're simply dispatching the store's action.

Important notice: this bug appears specifically when using the related getter.

The Bug

Having a collection of comments and then deleting a single item from that collection will trigger the bug. When this happens, we get an error in the console saying comment is undefined in Comments.vue. Sure enough, there is still an item in the computed comments but it's value is undefined. Ie, the index key in the array exists but it's value is undefined. Here's a screenshot:

image

What's interesting is that the state object itself has the following results after performing the comment's delete:

image

As you can see, comments.records is accurately in a state of an empty array (we started with 1 comment for this example). Meanwhile, comments.related is still identifying the relatedId of the comment before it was deleted.

Hypothesized Source of the Problem

I'm not a Vue or javascript expert - far from it in fact. However, I believe the problem stems from something like this:

  1. When the REMOVE_RECORD action/mutation is called, the record is removed from the state's records array.
  2. From there, the related getter then attempts to
// getters.related...
const related = state.related.find(matches(relationshipIndex)); // line 371
...
return ids.map(id => state.records.find(record => record.id === id)); // line 377
...
  1. However, because the record doesn't actually exist, it yields an array with a key assigned but an undefined value.
  2. The computed property reacts and is thus undefined for the v-for loop.

Conclusion

I believe this is a bug with the package and not an implementation mistake. However, being the non-expert I am, that may very well be the case. Nonetheless, I'd greatly appreciate a response from anyone looking, or @CodingItWrong, to see if I may be on to something here. Unfortunately, I'm not versed enough in Vue reactivity nor Javascript to assist with a worthy PR attempt. Perhaps there are 2 potential solutions:

  1. Update the REMOVE_RECORD mutation to find locations where that resource is stored within another resource's related records and remove from those arrays. Then, when the related getter is performed again, it won't try to map that object that no longer exists (line 371).
  2. Update the related getter so that we first ensure a state.records can be found before/during the map operation (line 377 and 380).

How to use with Jest?

When I try to use this module with Jest I get errors:

● Test suite failed to run

    Jest encountered an unexpected token

    This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.

    By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".

    Here's what you can do:
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/en/configuration.html

    Details:

    /Users/me/projects/MyProject/node_modules/@reststate/vuex/src/deepEquals.js:2
    export default function deepEquals() {
    ^^^^^^

    SyntaxError: Unexpected token export

      2 | import { ResourceClient } from "src/utils/reststate-vuex/client";
      3 | // @todo for production
    > 4 | import deepEquals from "@reststate/vuex/src/deepEquals";
        | ^
      5 | const STATUS_INITIAL = "INITIAL";
      6 | const STATUS_LOADING = "LOADING";
      7 | const STATUS_ERROR = "ERROR";

      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1295:14)
      at Object.<anonymous> (src/utils/reststate-vuex/index.js:4:1)

Do you have a fix for this? I'm not sure what to put in "transformIgnorePatterns" to fix this.

Related Getters Returns Old Data After Updating Resource

Hi @CodingItWrong thanks for helpful package.

Currently I'm integrating restate-vuex with vuetify v-data-table to make a stateful data table.

Here is my data table component
https://gist.github.com/yoelpc4/beed885b72b1082c27208ce64ded98e9

My edit province component
https://gist.github.com/yoelpc4/58093a0d447064e0cf1aca0b80be1d08

And this is my province view
https://gist.github.com/yoelpc4/cf88bcdfe88ff2c60397ae5e1a12e7c4

I binded v-data-table items with items data-table computed property so that, whenever provinces/update action is dispatched from edit province component, the computed items in data-table component is invoked.

The provinces/all getter is working as expected returning province resource with updated name. Problem arised when countries/related getter is invoked, because it keeps returns the updated province with old country instead of new country.

After console logging restate-vuex.js, I found that line 371 is the root of my problem.

const related = state.related.find(matches(relationshipIndex));

Province records before update
https://gist.github.com/yoelpc4/4d94605e981ce834a2bc059645b7036b

Province records after update
https://gist.github.com/yoelpc4/f58322e821f150748e816d01881b295b

Notice the first record before & after update in store is updated, but countries/related getters always returns the old related data instead of the new one.

Maybe you can reproduce my issue in advance to resolve this issue.

Getter for current page number, total number of pages and page size

Currently there are getters for if there exists a previous and current page (which btw aren't properly documented at this point. I had to figure the existence of these out via this diff 93fffc6), but none for these:

  • current page number
  • total number of pages (total number of items / items per page).
  • Items per page

I would argue this information is essential when making paginated frontends.

included resources fail when using namespaces

If this plugin is used with namespaced modules the storeIncluded function will generate incorrect actions due to missing the correct namespace.

I have solved this in my own fork and can be seen here: Chilinot@01ed68d

However, i have not created a PR because I was unable to update the test suite with proper test in a satisfying way. It is however currently in use in one of my project, with both namespaced and non namespaced modules without issue.

Doesn't clear out removed records

If you first load a set of resources, then remove one in the set, then reload the resources. The one you removed will still be present in the Vuex store.

Looking over the code, it only seems to add or update records, never remove missing ones.

error getter should return Error

The /error getter currently returns a boolean value. Instead I would prefer this to be either the Error object containing the relevant error message, so I can show the appropriate error message to the user. Of course I can make my own error handlers for each case to get around this limitation of the library, but it would be nice to have.

    computed: {
        error() {
            // should return Error : null, not true : false
            return this.$store.getters['publishedContent/error']
        }
    },

How should one handle caching?

I notice that the documentation for related for example shows that first the related records must be loaded (which triggers an XHR) and then only can one use the get.

However what if you've already run the loader? I.e. what if the data already exists in the store?
How do we first test for the data before running XHR needlessly?

Page getter return old data after loadPage options change

Same issue as #34, except that this time it's not possible to supply options to the getter.

For example:

    computed: {
        products() {
            // Cannot add options to /page since it's not a function
            const products = this.$store.getters['products/page'] || []
            // return products formatted for table
            return products.map(({attributes}) => attributes)
        }
    },

   methods: {
        loadProducts() {
            // Load products for the current page number.
            this.$store.dispatch('products/loadPage', {
                options: {
                    page: this.currentPage,
                    itemsPerPage: this.itemsPerPage,
                    sort: this.sort
                }
            }).then(() => {
                const meta = this.$store.getters['products/lastMeta']
                this.totalItems = meta.totalItems
            })
        }
  }

Support Paginated Relations

The api im querying returns paginated lists when accessing related resources. It would be nice to have some simple getters for accessing the next and previous page similar to how normal resources work.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.