Giter VIP home page Giter VIP logo

vuex-crud's Introduction

Vuex-CRUD

Build Status codecov npm npm

Introduction

Vuex-CRUD is a library for Vuex which helps you to build CRUD modules easily.

Installation

yarn add vuex-crud

OR

npm install vuex-crud

Vuex-CRUD uses Array.prototype.includes, Object.assign and Promise. Make sure hat your project use polyfill for those if you want to support older browsers! Look at here: https://babeljs.io/docs/usage/polyfill/ for more info.

Basic Usage

  1. Create your first CRUD resource:
// src/store/articles

import createCrudModule from 'vuex-crud';

export default createCrudModule({
  resource: 'articles'
});
  1. Register your CRUD resource in your store:
// src/store/index.js

import Vue from 'vue';
import Vuex from 'vuex';

import articles from '@/store/articles';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {},

  modules: {
    articles
  }
});
  1. Use it in your components:
<!-- src/components/Articles -->

<template>
  <main>
    <article v-for="article in articleList">
      <h1>{{ article.title }}</h1>
      <p>{{ article.content }}</p>
    </article>
  </main>
</template>

<script>
  import { mapGetters, mapActions, mapState } from 'vuex';

  export default {
    name: 'articles',

    computed: {
      ...mapGetters('articles', {
        articleList: 'list'
      }),

      ...mapState([
        'route', // vuex-router-sync
      ]),
    },

    methods: {
      ...mapActions('articles', {
        fetchArticles: 'fetchList'
      }),

      fetchData() {
        return this.fetchArticles();
      }
    },

    watch: {
      $route: 'fetchData',
    },

    created() {
      this.fetchData();
    }
  };
</script>
<!-- src/components/Article -->

<template>
  <main>
    <article v-if="currentArticle">
      <h1>{{ currentArticle.title }}</h1>
      <p>{{ currentArticle.content }}</p>
    </article>
  </main>
</template>

<script>
  import { mapGetters, mapActions, mapState } from 'vuex';

  export default {
    name: 'article',

    computed: {
      ...mapGetters('articles', {
        articleById: 'byId'
      }),

      ...mapState([
        'route', // vuex-router-sync
      ]),

      currentArticle() {
        return this.articleById(this.route.params.id);
      }
    },

    methods: {
      ...mapActions('articles', {
        fetchArticle: 'fetchSingle'
      }),

      fetchData() {
        return this.fetchArticle({
          id: this.route.params.id
        });
      }
    },

    watch: {
      $route: 'fetchData',
    },

    created() {
      this.fetchData();
    }
  };
</script>

Advanced Usage

The following options are available when creating new Vuex-CRUD module:

import createCrudModule, { client } from 'vuex-crud';

export default createCrudModule({
  resource: 'articles', // The name of your CRUD resource (mandatory)
  idAttribute: 'id', // What should be used as ID
  urlRoot: '/api/articles', // The url to fetch the resource
  state: {}, // Initial state. It will override state generated by Vuex-CRUD
  actions: {}, // Initial actions It will override actions generated by Vuex-CRUD
  mutations: {}, // Initial mutations. It will override mutations generated by Vuex-CRUD
  getters: {}, // Initial getters. It will override getters generated by Vuex-CRUD
  client, // Axios client that should be used for API request
  onFetchListStart: () => {}, // Callback for collection GET start
  onFetchListSuccess: () => {}, // Callback for collection GET success
  onFetchListError: () => {}, // Callback for collection GET error
  onFetchSingleStart: () => {}, // Callback for single GET start
  onFetchSingleSuccess: () => {}, // Callback for single GET success
  onFetchSingleError: () => {}, // Callback for single GET error
  onCreateStart: () => {}, // Callback for POST start
  onCreateSuccess: () => {}, // Callback for POST success
  onCreateError: () => {}, // Callback for POST error
  onUpdateStart: () => {}, // Callback for PATCH start
  onUpdateSuccess: () => {}, // Callback for PATCH success
  onUpdateError: () => {}, // Callback for PATCH error
  onReplaceStart: () => {}, // Callback for PUT start
  onReplaceSuccess: () => {}, // Callback for PUT success
  onReplaceError: () => {}, // Callback for PUT error
  onDestroyStart: () => {}, // Callback for DELETE start
  onDestroySuccess: () => {}, // Callback for DELETE success
  onDestroyError: () => {}, // Callback for DELETE error
  only: [
    'FETCH_LIST',
    'FETCH_SINGLE',
    'CREATE',
    'UPDATE',
    'REPLACE',
    'DESTROY'
  ], // What CRUD actions should be available
  parseList: res => res, // Method used to parse collection
  parseSingle: res => res, // Method used to parse single resource
  parseError: res => res // Method used to parse error
});

Nested Resources

Vuex-CRUD is designed mainly for flatten APIs like:

/api/articles/
/api/users/1
/api/pages?byBook=1

but it also supports nested resources like:

/api/books/1/pages/10
/api/users/john/tasks/15

However your store will always be flattened and will look similar to this:

{
  books: {
    entities: {
      '1': {
        // ...
      }
    }
  },
  pages: {
    entities: {
      '1': {
        // ...
      },
      '2': {
        // ...
      },
      '3': {
        // ...
      }
    },
    list: ['1', '2', '3']
  },
}

There are 2 possible ways to implement provide custom URL:

  1. Provide custom url for each request:
fetchList({ customUrl: '/api/books/1/pages' });
fetchSingle({ customUrl: '/api/books/1/pages/1' });
create({ data: { content: '...' }, customUrl: '/api/books/1/pages' });
update({ data: { content: '...' }, customUrl: '/api/books/1/pages/1' });
replace({ data: { content: '...' }, customUrl: '/api/books/1/pages/1' });
destroy({ customUrl: '/api/books/1/pages/1' });
  1. Define a getter for custom url:
import createCrudModule from 'vuex-crud';

export default createCrudModule({
  resource: 'pages',
  customUrlFn(id, type, bookId) {
    // id will only be available when doing request to single resource, otherwise null
    // type is the actions you are dispatching: FETCH_LIST, FETCH_SINGLE, CREATE, UPDATE, REPLACE, DESTROY
    const rootUrl = `/api/books/${bookId}`;
    return id ? `rootUrl/${id}` : rootUrl;
  }
});

and your requests will look this:

const id = 2;
const bookId = 1;

fetchList({ customUrlFnArgs: bookId });
fetchSingle({ id, customUrlFnArgs: bookId });
create({ data: { content: '...' }, customUrlFnArgs: bookId });
update({ id, data: { content: '...' }, customUrlFnArgs: bookId });
replace({ id, data: { content: '...' }, customUrlFnArgs: bookId });
destroy({ id, customUrlFnArgs: bookId });

Custom client

Vuex-CRUD is using axios for API requests. If you want to customize interceptors or something else, then you can do following:

import createCrudModule, { client } from 'vuex-crud';
import authInterceptor from './authInterceptor';

client.interceptors.request.use(authInterceptor);

createCrudModule({
  resource: 'comments',
  client
});

Parsing Data from Your API

You can provide a custom methods to parse data from your API:

import createCrudModule from 'vuex-crud';

createCrudModule({
  resource: 'articles',

  parseList(response) {
    const { data } = response;

    return Object.assign({}, response, {
      data: data.result // expecting array of objects with IDs
    });
  },

  parseSingle(response) {
    const { data } = response;

    return Object.assign({}, response, {
      data: data.result // expecting object with ID
    });
  }
});

Getters

Vuex-CRUD ships with following getters:

  • list() returns list of resources
  • byId(id) returns resource by ID

Actions

Vuex-CRUD ships with following actions (config is configuration for axios):

{
  // GET /api/<resourceName>
  fetchList({ commit }, { config } = {}) {
    // ...
  },

  // GET /api/<resourceName>/:id
  fetchSingle({ commit }, { id, config } = {}) {
    // ...
  },

  // POST /api/<resourceName>
  create({ commit }, { data, config } = {}) {
    // ...
  },

  // PATCH /api/<resouceName>/:id
  update({ commit }, { id, data, config } = {}) {
    // ...
  },

  // PUT /api/<resouceName>/:id
  replace({ commit }, { id, data, config } = {}) {
    // ...
  },

  // DELETE /api/<resouceName>/:id
  destroy({ commit }, { id, config } = {}) {
    // ...
  },
}

Usage with Nuxt

vuex-crud works with Nuxt modules system as well. You can simply define your Nuxt modules as following:

import createCRUDModule from 'vuex-crud';

const crudModule = createCRUDModule({
  resource: 'articles'
});

const state = () => crudModule.state;

const { actions, mutations, getters } = crudModule;

export {
  state,
  actions,
  mutations,
  getters
};

and then use it in your component:

export default {
  computed: {
    ...mapGetters('articles', {
      articleList: 'list'
    })
  },

  fetch({ store }) {
    store.dispatch('articles/fetchList');
  }
};

License

The MIT License (MIT) - See file 'LICENSE' in this project

Copyright

Copyright © 2017 Jiří Chára. All Rights Reserved.

vuex-crud's People

Contributors

adam-lynch avatar jirichara avatar josx 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  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  avatar  avatar  avatar  avatar  avatar  avatar

vuex-crud's Issues

how to add query string to fetch method

The docs mention that the module can be used for urls like "/api/pages?byBook=1". How would something like this be implemented? For the create method, we pass in a data object to fill the body of the http request. But for the fetch method, how do populate the request's query?

customUrlFn needs access to store

I have a case where one of my modules uses a url which includes the id of the logged in user, only available on the store, but there does't seem to be any way to get the store in scope for customUrlFunction. Is there any way to access the store here, or could one be made?

defaultClient export

It seems to be exporting it as "client". So the example is wrong as it needs to be:

import createCrudModule, { client } from 'vuex-crud'

Why entities, list, and singles?

I'm new to Vuex. What's the reasoning behind having entities, list, and singles in the state and how do they differ? Why not a single list?

How to use it with nuxt's axios-module ?

I am using axios-module for nuxt, primary usecase is — different base url for SSR and browser. In case of server rendering, backend available at http://backend:5000/ and in browser its available on the same host.
So, config for axios be:

  axios: {
    baseURL: 'http://backend:5000/api',
    browserBaseURL: '/api',
    proxy: true
  }

in my store/articles.js i have

import createCRUDModule from 'vuex-crud'

const crudModule = createCRUDModule({
  resource: 'articles',
  client: this.$axios
})

const state = () => crudModule.state

const { actions, mutations, getters } = crudModule

export {
  state,
  actions,
  mutations,
  getters
}

Aaand its not working as expected. Base urls defined in nuxt.config.js ignored completely. Axios doing requests to http://127.0.0.1:80/api instead of http://backend:5000/api

Hovewer, if i use axios in a classic manner, like this.$axis.get('/articles'), its working as expected on server and in browser.

Am i missing something?

How to get msg from CREATE action?

I try send comment to API, so I create this method

      newComment (msg) {
        this.create({
          data: {
            content: msg
          },
          config: {
            params: {
              post: this.postId
            }
          }
        })
      }

Comment was send to API and display on my page, but I need get sometimes response message. How can I get it?

Getting Uncaught (in promise) TypeError: Cannot read property 'toString' of undefined

I am trying to get a fetchSingle and getting an error when running the script.

Here is my vue file contents:

<script>
    import { mapGetters, mapActions, mapState } from 'vuex';

    export default {
        name: 'tag',
        computed: {
            ...mapGetters('tags', {
                tagById: 'byId'
            }),

            ...mapState([
                'route', // vuex-router-sync
            ]),

            currentTag() {
                return this.tagById(this.$route.params.id);
            }
        },

        methods: {
            ...mapActions('tags', {
                fetchTag: 'fetchSingle'
            }),

            fetchData() {
                return this.fetchTag({
                    id: this.$route.params.id
                });
            }
        },

        watch: {
            $route: 'fetchData',
        },

        created() {
            this.fetchData();
        }
    };
</script>

CreateMutations.js

      fetchSingleSuccess: function fetchSingleSuccess(state, response) {
        var data = response.data;
        var id = data[idAttribute].toString(); <-- causes the error

Any ideas?

Pagination

Hello how to implement pagination using laravel crud and this library ?

state.list is not being updated when CREATE method is used

Hi @JiriChara,

I've noticed that the state.list object is being updated only when an item is deleted or updated, but not when created. Because of this my list is not being rerendered after an item is added.

I gues the code below should be added in the createSuccess callback of createMutations.js in order to fix this issue.

const listIndex = state.list.indexOf(id);

if (listIndex < 0) {
  Vue.set(state.list, state.list.length, id);
} 

Maximum call stack size exceeded

I setup the nuxt example as in the README.

I got an error when the api route for fetchList was not defined yet. This would result in a 404 in the first request but in a "Maximum call stack size exceeded" error. Afterwards this would also happen on other routes where the request is not called.

I am not sure if this is supposed to happen or if there could be something done to prevent the application from failing afterwards.

TypeError: data.forEach is not a function at Store.fetchListSuccess

I try fetchList from wordpress API custom navigation, but vuex crud return this error:

TypeError: data.forEach is not a function at Store.fetchListSuccess

What am I doing wrong? My JSON structure look like this:
[ { "id": 2959, "name": "1_Websites", "slug": "1_websites", "description": "", "count": 4, "items": [ { "id": 46783, "order": 1, "parent": 0, "title": "Blog", "url": "https://google.com", "attr": "", "target": "", "classes": "", "xfn": "", "description": "", "object_id": 46783, "object": "custom", "type": "custom", "type_label": "Custom Link", "object_slug": "blog" }, { "id": 46779, "order": 2, "parent": 0, "title": "Kontakt", "url": "https://google.com/", "attr": "", "target": "", "classes": "", "xfn": "", "description": "", "object_id": 46779, "object": "custom", "type": "custom", "type_label": "Custom Link", "object_slug": "kontakt" }, { "id": 46781, "order": 3, "parent": 0, "title": "Home", "url": "https://google.com/", "attr": "", "target": "", "classes": "", "xfn": "", "description": "", "object_id": 46781, "object": "custom", "type": "custom", "type_label": "Custom Link", "object_slug": "home" }, { "id": 46782, "order": 4, "parent": 0, "title": "Ideas", "url": "https://google.com", "attr": "", "target": "", "classes": "", "xfn": "", "description": "", "object_id": 46782, "object": "custom", "type": "custom", "type_label": "Custom Link", "object_slug": "ideas" } ], "meta": { "links": { "collection": "https://google.com/", "self": "https://google.com" } } }, { "id": 2957, "name": "2_Office", "slug": "2_office", "description": "", "count": 4, "items": [ { "id": 46771, "order": 1, "parent": 0, "title": "Word", "url": "https://office.live.com/start/Word.aspx", "attr": "", "target": "", "classes": "", "xfn": "", "description": "", "object_id": 46771, "object": "custom", "type": "custom", "type_label": "Custom Link", "object_slug": "word" }, { "id": 46772, "order": 2, "parent": 0, "title": "Excel", "url": "https://office.live.com/start/Excel.aspx", "attr": "", "target": "", "classes": "", "xfn": "", "description": "", "object_id": 46772, "object": "custom", "type": "custom", "type_label": "Custom Link", "object_slug": "excel" }, { "id": 46773, "order": 3, "parent": 0, "title": "PowerPoint", "url": "https://office.live.com/start/PowerPoint.aspx", "attr": "", "target": "", "classes": "", "xfn": "", "description": "", "object_id": 46773, "object": "custom", "type": "custom", "type_label": "Custom Link", "object_slug": "powerpoint" }, { "id": 46774, "order": 4, "parent": 0, "title": "OneNote", "url": "https://www.onenote.com/notebooks", "attr": "", "target": "", "classes": "", "xfn": "", "description": "", "object_id": 46774, "object": "custom", "type": "custom", "type_label": "Custom Link", "object_slug": "onenote" } ], "meta": { "links": { "collection": "https://google.com", "self": "https://google.com" } } } ]

How to call getter from parseList()?

In my parseList I want to get an existing list from the store and merge newer values into it.

What is the correct way to access elements in the store while parsing the incoming response?

entities & list aren't updated after re-run fetchList

For example, I have 2 lists that are specified by query string ?id=

So I create a custom action for getting with a custom url:

actions: {
	fetchBuildsOfService({commit, dispatch}, id) {
		const url = `/my-url?id=${id}`;
		dispatch('fetchList', {
			customUrl: url
		})
	}	
}

When I visit url?id=1 the list has 2 items.
When I visit url?id=2 the list is empty, but the state is keeping 2 items => it didn't update new response.

Should we have a method for resetting the states? Is this following the best practice?

how do I clear list from outside of vuex-crud?

I'm mapping the 'list' of a resource via mapGetters as a computed property. How do I clear or modify the list state from a component manually? sorry if a noob question, vuex is very new to me.

Created entry not being added to the list

I have list of todos, for example (rendered from todoList). On the same page i have form and button that adds a new todo. I expected this newborn todo being added to todoList (and rendered to the page), but thats not the case.
Is that bug or feature? I think there is some inconsistence, because deleted todo being deleted from todoList and page.

Related support

do you think is it worth it to have a related feature?

Like getRelated('pages') --> calls API on articles/id/pages

if yes, how do you think we can do the state?

Best practice for error handling?

Could you please create a documentation section about error handling?

In particular I need to know the best way to catch and handle an Authorization error?

I can call my store.dispatch('logIn') from each of the onXXXError handlers. However, with my limited experience so far, I see no way to stop the error from surfacing in the user's browser log.

react to request success/error with actions

First of all, Id like to say this is a great little library, great work!

My only issue is that we can only react to request errors/successes with custom mutations. I think it would be much more helpful if we could react with actions instead, giving us more flexibility, with access to rootState, ability to dispatch further actions etc ...

Although it would be a breaking change it seems a relatively straightforward one. For example, for the onFetchListSuccess and onFetchListError you could change

if (only.includes('FETCH_LIST')) {
    Object.assign(crudActions, {
      /**
       * GET /api/<resourceName>
       *
       * Fetch list of resources.
       */
      fetchList({ commit }, { config, customUrl, customUrlFnArgs = [] } = {}) {
        commit('fetchListStart');

        return client.get(urlGetter({ customUrl, customUrlFnArgs }), config)
          .then((res) => {
            const parsedResponse = parseList(res);

            commit('fetchListSuccess', parsedResponse);

            return parsedResponse;
          })
          .catch((err) => {
            const parsedError = parseError(err);

            commit('fetchListError', parsedError);

            return Promise.reject(parsedError);
          });
      }
    });
  }

to

if (only.includes('FETCH_LIST')) {
    Object.assign(crudActions, {
      /**
       * GET /api/<resourceName>
       *
       * Fetch list of resources.
       */
      fetchList({ commit, dispatch }, { config, customUrl, customUrlFnArgs = [] } = {}) {
        commit('fetchListStart');

        return client.get(urlGetter({ customUrl, customUrlFnArgs }), config)
          .then((res) => {
            const parsedResponse = parseList(res);

            dispatch('fetchListSuccess', parsedResponse);

            return parsedResponse;
          })
          .catch((err) => {
            const parsedError = parseError(err);

            dispatch('fetchListError', parsedError);

            return Promise.reject(parsedError);
          });
      }
    });
  }

I think that would work but forgive me if I'm missing something and this feature cant be implemented. If it can I do think it will make the library a lot more flexible/powerful.

Thanks,
Connor

Use spread operator on initial state.

Actually this is not an issue. I just wonder if there's a way to just push additional property to initial state. e.g. pushing

export default createCrudModule({ resource: "posts", client: client, state: {...state, some_property} })

Request header not set when using with @nuxtjs/auth

I use the plugin @nuxtjs/auth for authorization.
When I request the API with this.$axios the auth headers are set correctly.
When requesting the API through the vuex-crud the headers are not set correctly.

Is there a way to use the existing axios configuration without writing a custom request intercepter for axios?

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.