Skip to content

does vue-apollo work with vuex? #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
DougDavenport opened this issue Oct 4, 2016 · 115 comments
Closed

does vue-apollo work with vuex? #7

DougDavenport opened this issue Oct 4, 2016 · 115 comments

Comments

@DougDavenport
Copy link

Can / should vue-apollo be used with vuex?
If yes then could you please show examples?

@stubailo
Copy link

stubailo commented Oct 4, 2016

This is something that would need to be integrated into apollo-client core. What is the VueX API like, especially for integrations? For Redux we ship a special reducer and middleware, does VueX have similar integration points?

@DougDavenport
Copy link
Author

Vue, Vuex, Apollo, and GraphQL are new technologies for me so I hesitate to comment on specifics. My investigations so far lead me to believe this combo may be productive. The Vuex API is at https://vuex.vuejs.org/en/api.html . I'm hoping these can work together.

@Akryum
Copy link
Member

Akryum commented Oct 5, 2016

We could either do a vuex plugin or a vuex module I think.

@mygnu
Copy link

mygnu commented Oct 6, 2016

a plugin would be great for vuex. I have another question though
can i use the apollo store for storing client generated data that doesn't require to sync with server? if yes how would i go about it?

@stubailo
Copy link

stubailo commented Oct 6, 2016

Usually people do that by using Redux with Apollo Client. So part of your store is managed by AC (the server stuff) and the rest with regular Redux.

So integrating with VueX would mean that AC would manage the server-side data, and you manage the rest as usual.

@mygnu
Copy link

mygnu commented Oct 6, 2016

I have never used Redux in vue, but vuex. at the moment my app is ok with managing local stuff in components but if there i a chance of vuex plugin for vue-apollo i will wait?
another option is to use bouth vuex and apollo client which is a bit of overkill in my view

@stubailo
Copy link

stubailo commented Oct 6, 2016

another option is to use bouth vuex and apollo client which is a bit of overkill in my view

Why? It doesn't seem like a bad idea to me.

@Akryum Akryum added the question label Oct 6, 2016
@stephan281094
Copy link

Any news on this? I'd be happy to help.

@xpepermint
Copy link

@Akryum, @stubailo This vuex-apollo thing is really bugging me and as I can see I'm not alone :). Would you be so kind and help us find a proper solution for vuex? I think/hope you are the right people to ask. It would also be great to join forces and have this topic described at dev.apollodata.com. Thanks.

@davenport
Copy link

After reviewing vue-apollo, apollo client, and vuex I think there may be an easy way to enhance vue-apollo to allow for vuex support. Currently vue-apollo updates the local state. In vuex the store is modified by committing a mutation.

What if a new option were added to vue-apollo to bypass the local state update and instead call a user supplied function?

Let's call the new option "apply" (or whatever makes more sense). The following updated applyData should be enough to allow support for vuex (or something else):

function applyData(data) {
  loadingDone();

  if (typeof options.apply === 'function') {
    options.apply.call(vm, data);
  } else if (typeof options.update === 'function') {
    vm[key] = options.update.call(vm, data);
  } else if (data[key] === undefined) {
    console.error(`Missing ${key} attribute on result`, data);
  } else {
    vm[key] = data[key];
  }

  if (typeof options.result === 'function') {
    options.result.call(vm, data);
  }
}

What do you think?

@kristianmandrup
Copy link

@davenport Please post some example code to demonstrate how this would look in a Todo (or Counter) or similar simple example with Vuex. Cheers!

@kristianmandrup
Copy link

kristianmandrup commented Nov 7, 2016

@DougDavenport As I understand your example, this would work for incoming data to be fed into the store and thus force a new VDOM render phase.

methods: mapActions(...actions), // where actions includes: increment(counter)
apollo: {
  // Query with parameters
  ping: {
    query: ...,
    ...
    apply: (data) => {
      // emit as a Vuex action, that is subscribed to //
      // action then turned into a store mutation on local Vuex store...
      this.increment(data)
    }
  }

Would it make sense to go the other way? ie. for local mutations on store to also potentially cause a side effect of a graphQL mutation? I think it should only be unidirectional, as you would otherwise get into a lot of sync problems, just like with two-way data binding, ie. database/state sync in general. So this truly looks like an elegant flexible solution :)

Note: I'm very new to this, so syntax/understanding might be totally off the tracks!

@mygnu
Copy link

mygnu commented Nov 8, 2016

while we are using the redux store under the hood with apollo as I understand, is it possible to access and set data to it in vue??

@mikelnrd
Copy link

mikelnrd commented Nov 8, 2016

Hi I'm just posting here to say +1 - I'm also very interested in having vuex support.

@kristianmandrup
Copy link

kristianmandrup commented Nov 8, 2016

I suggest you also have a look at vue-rx with apollo
Awesome!

@ykshev
Copy link

ykshev commented Nov 8, 2016

@MikeLeonard
Isn't it enough? Just pass your apollo client to the root store, and then:

  actions: {
    $GET_PROJECTS: ({ commit, state, rootState }) => {
      return rootState.apollo.watchQuery({
        query: gql`
          query projectsList{
            projects {
              _id
              name
            }
          }
        `,
      }).subscribe({
        next: (resulut) => {
          const projects = resulut.data.projects || [];
          commit('SET_PROJECTS', projects)
        },
        error: (error, done) => {
          console.log('there was an error sending the query', error);
        }
      });
    },
}

@kristianmandrup
Copy link

Of course, externalise these functions. Otherwise your actions hash will not scale and quickly become an entangled jungle! :P

@kristianmandrup
Copy link

On another note, we need a system to avoid the "two-way sync" problem. Ie. the above example is for mutating local AND external store. Then you might also have listeners to external store, that contain changes from other users (and your self). So I guess you need to ensure you don't infinitely loop... ie, you need a commit hash or sth to identify mutations you have performed before and skip them? Your thoughts...

@davenport
Copy link

@kristianmandrup , @ykshev
As I was trying to think through an example of using vuex and vue-apollo together, it seemed to make more sense to just use the apollo client directly with vuex. Is that what you are suggesting?

@kristianmandrup
Copy link

kristianmandrup commented Nov 8, 2016

@DougDavenport Yes!

Some mind experiments for how it could look.

Mutations

// apollo/mutations.js

export const addTag = (state, newTag) => {
  this.$apollo.mutate({
    // Query
    mutation: gql`mutation ($label: String!) {
      addTag(label: $label) {
        id
        label
      }
    }`,
    // Parameters
    variables: {
      label: newTag,
    },
    // Update the cache with the result
    // 'tagList' is the name of the query declared before
    // that will be updated with the optimistic response
    // and the result of the mutation
    updateQueries: {
      tagList: (previousQueryResult, { mutationResult }) => {
        // We incorporate any received result (either optimistic or real)
        // into the 'tagList' query we set up earlier
        return {
          tags: [...previousQueryResult.tags, mutationResult.data.addTag],
        };
      },
    },
    // Optimistic UI
    // Will be treated as a 'fake' result as soon as the request is made
    // so that the UI can react quickly and the user be happy
    optimisticResponse: {
      __typename: 'Mutation',
      addTag: {
        __typename: 'Tag',
        id: -1,
        label: newTag,
      },
    },
  })
}

Queries

// apollo/subscriptions.js
export const projectList = rootState.apollo.watchQuery({
    query: gql`
      query projectsList{
        projects {
          _id
          name
        }
      }
    `,
  })

Subscription services

import { * as subscriptions } from './apollo/subscriptions'
import store from './store'

export getProject = () => {
  subscriptions.projectList.subscribe({
    next: (result) => {
      const projects = result.data.projects || [];
      store.dispatch('setProjects', projects)
    },
    error: (error, done) => {
      console.log('there was an error sending the query', error);
    }
  });
}

Actions

// apollo/actions.js

import { * as mutations } from './apollo/mutations'

export const addTag = ({ commit, state, rootState }) {
    mutations.addTag(state.newTag)
    .then((data) => {
        // commit to VueX store with confirmation result from server
        commit('addTag', data)
    }).catch((error) => {
      // Error
      console.error(error);

      // Restore the initial newTag??
      commit('clearTag')
    });
  }

const setProjects = ({ commit, state, rootState }) => {
  commit('setProjects', state.projects)
}

Actions

// actions.js

import { addTag, getProject } from './apollo/actions'

export const actions = {
  increment ({commit}) {
    commit('increment')
  },
  addTag,
  getProject
}

Mutations

// mutations.js

export const mutations = {
  increment (state, n) {
    // mutate state
    state.count = state.count + n
  },
  setProjects: (state, projects) {
    state.projects = projects
  },
  addTag: (state, newTag) {
    state.tags.push(newTag)
  },
  clearTag: (state) {
    state.newTags = ''
  }
}

State

// state.js
const state = {
  count: 1,
  tags: [],
  newTag: '',
  projects: []
}

Then export actions, mutations and state from ./config.js

import { actions, mutations, state } from './config'

const store = new Vuex.Store({
  state,
  mutations,
  actions
  }
})

As the example above demonstrates, it would be best to follow the @ykshev architecture proposal of putting the logic in an action, so mutations are kept "clean".

Note: I'm still a novice with Vue2, Apollo and VueX. I'm I on the right track here!?

@kristianmandrup
Copy link

However, perhaps VueX should be made better suited to subscription models? What do other socket solutions integrate with VueX? I think there should be a separate apollo subscription Service, which when it receives data performs an action on VueX, which is committed.

@kristianmandrup
Copy link

I have no idea how to handle the optimisticResponse with Vuex. Any ideas? Somehow when we do a store.dispatch from the component we need to "catch it" there and do a fake UI update until we get a full cycle roundtrip with state update and re-render ?

@kristianmandrup
Copy link

as far as I understand, the action can return a promise, so if the optimisticResponse could resolve the promise it's all good :)

@kristianmandrup
Copy link

kristianmandrup commented Nov 8, 2016

ah well, the optimisticResponse it just a fake response which would then be fed into the VueX store/state.

{
        __typename: 'Mutation',
        submitComment: {
          __typename: 'Comment',
          // Note that we can access the props of the container at `ownProps`
          postedBy: ownProps.currentUser,
          createdAt: +new Date,
          content: commentContent,
        },

The __typename is apollo specific metadata, which I guess could be ignored.

// from a component action handler method (f.ex activated by button 'add' click) 
store.dispatch('addTag', newTag)
  .then((fakeResponse) -> {
  this.tags = fakeResponse.tags
  })
  .catch(err => this.handleError(err))

Sweet :)

@mygnu
Copy link

mygnu commented Nov 13, 2016

Why are trying to have two sore management systems redux (used by apollo under the hood) and puting vuex on top of it. Why can't we access the internal redux store for local state? am I missing something here??
See issue here

@davenport
Copy link

Good question that I have asked myself. It may be possible but vuex is a reactive store that integrates with vue components.

https://vuex.vuejs.org/en/getting-started.html

At the center of every Vuex application is the store. A "store" is basically a container that holds your application state. There are two things that makes a Vuex store different from a plain global object:

  1. Vuex stores are reactive. When Vue components retrieve state from it, they will reactively and efficiently update if the store's state changes.
  2. You cannot directly mutate the store's state. The only way to change a store's state is by explicitly committing mutations. This ensures every state change leaves a track-able record, and enables tooling that helps us better understand our applications.

I think the problem with what you are suggesting is described by:

https://vuex.vuejs.org/en/intro.html

@smolinari
Copy link
Contributor

smolinari commented Dec 6, 2016

I've been sort of fanning the fire on these debates and for the push to Vuex and I will admit, I've done too little otherwise and I apologize for that.

It seems to me though, especially with Vue 2.0 now using a virtual DOM, and in a sense working more like React, that a direct integration of Vue and Apollo (like with what we have here) is going to work just fine. In other words, we don't need Vuex anymore, as its flux type of system was built mainly for the type of reactivity Vue 1.0 offered. Remember, my fanning the need for Vuex integration was being done in the Vue 1.0 days. 😄

I welcome the more conceptual discussion on this. Where are the cutting points between Apollo and Vue, which aren't possibly working quite right? Or is everything ok, as I suspect it is?

Scott

@smolinari
Copy link
Contributor

Has anyone dug any further on this? Or has everyone been finding the Vue-Apollo solution appropriate?

Scott

@beebase
Copy link

beebase commented Jan 8, 2018

interesting discussion... I'm fluid with vuex and learning the graphql paradigm right now.
I can see the points made about attaching queries to components vs vuex actions and I think both approaches have their advantages, however...
Looking at this example of a mutation with apollo client directly inside a component makes me wonder. Do we really have to write this "mess" in order to link component data to graphql??

createPost.vue
Just looking at one field "description", you need to do stuff in line 43,44,47,53,61,64. That's ridiculous. Imagine having 5 or 10 fields. I suppose this could be reduced with graphql 'fragments', but still, this doesn't feel right. If this is really the only way to link vue components to graphql, I'd rather stick to vuex and maybe use graphql inside vuex actions.

Are there any best practices evolving round vue and graphql/vuex? I'm still new to graphql so still exploring. Thanks.

@alajfit
Copy link

alajfit commented May 9, 2018

We are a few months on and this is still no clearer
I still don't feel confident with using vue-apollo on a large scale application
@PierBover would you still recommend the Vuex/ Apollo approach ?

@beebase What did you end up doing?

@beebase
Copy link

beebase commented May 14, 2018

@alajfit I sticked to vuex and I'm using it as a local offline database that syncs with a socketio / arangodb server. I LOVE the reactivity and the way you can store data in vuex.

Regarding graphql. I still feel there's a lot of code overhead needed on the client side to keep offline data in sync with the server. Also building the resolvers serverside is a LOT of work, although prisma and hasura.io look promising. Eventually I think I'll switch to graphql once products like apollo, prisma and hasura have evolved more and are settled.

@maarteNNNN
Copy link

@beebase I agree prisma has too many changes in a short amount of time. It hasn't matured enough to fully rely on.

@Samuell1
Copy link

@beebase You dont need to write that mess, its more like that example is writed more simplier way to understand. I recommed to look at more actual/complex examples like this: https://github.com/Akryum/vue-summit-app

@triesa
Copy link

triesa commented Sep 30, 2018

Any news on this? Does plugin for integrating vuex + apollo exist, or something?

@diguliu
Copy link

diguliu commented Oct 2, 2018

After reading this whole thread I feel like I learned a lot and still have no idea of what's the proper way to do this. :)

@smolinari
Copy link
Contributor

@diguliu - I believe the state management of choice should now be apollo-link-state.

Scott

@ecker00
Copy link

ecker00 commented Oct 15, 2018

#384 Seems apollo-link-state becomes a replacment for vuex. Any drawbacks to this approach?

@smolinari
Copy link
Contributor

I'm now delving into this solution. I'll report back, once I know how good (or bad) it works.

Scott

@ozguruysal
Copy link

ozguruysal commented Nov 9, 2018

I'm not sure if this was there before but Apollo Link can be used as stand alone without cache. See the docs here. This way you can build a typical Vue + Vuex application and completely avoid ApolloClient and InMemoryCache. Of course in this case you won't be using Vue Apollo either.

I tested it and it seems to work perfectly.

@Akryum
Copy link
Member

Akryum commented Nov 9, 2018 via email

@PierBover
Copy link

Then you miss the benefits of Apollo client and have to manage all the state yourself. 😸

Isn't that the point of Vuex?

@Akryum
Copy link
Member

Akryum commented Nov 9, 2018 via email

@PierBover
Copy link

PierBover commented Nov 9, 2018

I see... it seems Apollo client is a whole new beast form the last time I checked it.

🤯

@ozguruysal
Copy link

ozguruysal commented Nov 9, 2018

@Akryum Yes, absolutely. But I'm really not a fan of the way you interact with Apollo cache or apollo-link-state. On the other hand Vuex is so great.

EDIT: Having said that, for many projects, using ApolloClient and apollo-link-state as local state management would probably be the best fit as they do eliminate a lot of code for local state management.

@smolinari
Copy link
Contributor

@Akryum - Will Vue-Apollo work with the new Apollo Client 2.5.0 Alpha? Which btw to everyone, now includes link-state.

I'd test myself, but have other things to attend to. Just a reassuring "yes" or a disappointing "no" would suffice. 😄 If it is "no", how long do you think it would take until you can get Vue-Apollo updated?

Scott

@ecker00
Copy link

ecker00 commented Nov 10, 2018

As this is such a hot discussion it might be a good idea to reflect some of these thoughts on how Vuex does and doesn't integrate with Apollo on the "Local state" part of the documentation?
https://akryum.github.io/vue-apollo/guide/local-state.html

@rnenjoy
Copy link

rnenjoy commented Nov 10, 2018

@ecker00 i dont get that example. Where is updateHello defined?

@ecker00
Copy link

ecker00 commented Nov 12, 2018

@rnenjoy Neither do I, but I don't have any experience with Apollo and GraphQL yet, so I would not be the right person to update the documentation.

@Samuell1
Copy link

@ecker00 @rnenjoy you can see example here https://codesandbox.io/s/zqqj82396p and resolvers const is where is it defined

@Ouradze
Copy link

Ouradze commented Nov 22, 2018

Yeah but with Apollo client you don't even need Vuex. Le ven. 9 nov. 2018 à 09:46, Pier Bover notifications@github.com a écrit :

Then you miss the benefits of Apollo client and have to manage all the state yourself. smile_cat Isn't that the point of Vuex? — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#7 (comment)>, or mute the thread https://github.com/notifications/unsubscribe-auth/ACqyfHCxam4MsYMCuZOaFmHBhbKR3iD2ks5utb91gaJpZM4KN3-U .

Hi,

You may not need Vuex but what about an application previously written using vuex ? I feel like it is all or nothing. How do you make an existing application using vuex compatible with apollo. We don't really want to switch completely from one to the other otherwise it will break quite a few components. So, we may not need vuex but how could we use it anyway smoothly with apollo such that we don't loose all the benefits from apollo. Do you any code samples we could use as an example ?

Regards,

@PierBover
Copy link

PierBover commented Nov 22, 2018

@Eclar you can keep using Vuex as your central store, but instead of making calls to your REST API with Axios, you just make calls and/or subscriptions with Apollo client and feed Vuex with the responses.

I made this example some time ago with an old version of Apollo client.

https://github.com/PierBover/vuex-apollo-example-project/blob/master/store.js

Don't take this at face value, it's just a quick and dirty example. For a more realistic approach I would put all the GraphQL code outside of Vuex.

@Ouradze
Copy link

Ouradze commented Nov 26, 2018

Thank you for the answer. Do we have a good practice for this sort of behaviour ? Maybe @Akryum could clarify ?

@oller
Copy link

oller commented Dec 17, 2018

If one were to remove the Vuex dependency and rely solely on vue-apollo's store. Updating the cache on successful data response. What is the recommended way to replace the functionality Vuex getters affords? To apply some logic when accessing previously stored data

Say I've a graph query that returns a list of numbers and I'd like to reduce down to a total based on a user input range, or get the value for a matching ID etc.

Many thanks!

@smolinari
Copy link
Contributor

@oller - Can you expand on your use case? What kind of component would offer a list of data, to be then reduced to a range or single id? If you need a range, wouldn't that be an input variable to the query to begin with?

Scott

@oller
Copy link

oller commented Dec 17, 2018

Hey @smolinari , thanks for the response.

Sorry that wasn't the best worded example. Lets say I've a query that returns leads...

query leads {
  leads {
    uuid
    title
    client {
      uuid
      fullname
    }
    lastInteraction
    notes
    state
    target
...
}

The state value is a string ID, which can be one of 7 values. Depending on the value of the state I want to apply a contextual css class to set a color for the leads of that state in a list. In Vuex, I previously had this leadStates array (an array of objects with id, label, desc) hardcoded in the applicable module and I could getClassForLeadState amongst some other utility functions getIndexForLeadState, getLabelForLeadState and getTargetTotalForLeadState (which given a lead state will reduce down a total of the target properties. Having already loaded leads it seems quite bloated to send all these additional queries back to the server for simple reductions etc.

 getClassForLeadState: state => leadState => {
    let className
    switch (state.leadStates.findIndex(i => i.id === leadState)) {
      case 0:
      case 1:
        className = 'success'
        break
      case 2:
        className = 'warning'
        break
      case 3:
       ...
 },
  getLabelForLeadState: state => leadState => {
    return state.leadStates.find(ls => ls.id === leadState).label
  },
  getTargetTotalForLeadState: state => leadState => {
    return state.leads
      .filter(lead => lead.state === leadState)
      .reduce((prev, current) => prev + current.target, 0)
  }

How would a scenario like this best be implemented if I was looking to remove Vuex? Multiple components need to be able to access these getters, so I can't just factor this into a single component. Should I factor out a common helper file? look to load this logic into the Apollo store perhaps with @client ? look to move these into separate queries?

I could add some of these into new graphQL queries, but I don't want to start adding css presentational classes into the graphQL, that's no use. And it seems crazy to ask for a subset of data again when i've already the whole list and i just want to match against a key/value, but perhaps that's just the graphQL paradigm and i've not totally got my head around it yet.

Any input gratefully received!

Thanks!

@smolinari
Copy link
Contributor

smolinari commented Dec 19, 2018

@oller - not sure this will answer your question completely. But, I think it might get you thinking in a certain direction. Here is a Todo app with client-link-state and also the situation of setting styling, etc. All those "gets" you had before simply aren't needed, once the data is set. Where the data comes from doesn't matter. It just needs to be available and "set", when the component is rendered, either by calling it from a server a file or set in the data prop. After that, it is automatically reactive and that is the beauty of Vue. 👍

https://codesandbox.io/s/k3621oko23

I hope it helps. 😃

Scott

@AnalyzePlatypus
Copy link

AnalyzePlatypus commented Feb 5, 2019

February 2019 and we still haven't figured this out.
Sigh.

(BTW, why is this issue closed?)

@smolinari
Copy link
Contributor

smolinari commented Feb 5, 2019

@AnalyzePlatypus - Why not figured out? Apollo Client has its own state management built in and therefore Vuex is not needed at all.

My example To-do app above demonstrates this ability.

Scott

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests