Skip to content
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

vue-apollo and vuex interaction best practices #140

Closed
alidcast opened this issue Nov 1, 2017 · 72 comments
Closed

vue-apollo and vuex interaction best practices #140

alidcast opened this issue Nov 1, 2017 · 72 comments

Comments

@alidcast
Copy link

alidcast commented Nov 1, 2017

Hi @Akryum!

Apollo's cache and vuex's store seem to have some overlap in terms of usage, so how would you recommend using vue-apollo and vuex together?

The usual approach with REST would be to have a folder that holds the api service and a folder that holds the state store (this is how Gitlab does it), and then have it all come together inside the main component; another approach is to have the api service/calls integrated into vuex actions.

My first instinct was to use the vanilla apollo plugin and follow one of the above approaches. But then I realized that graphql is a different paradigm - the component queries and mutates its own data - hence why you tightly integrated query fetching inside components. So it'd be great to have the creator of the plugin weigh in on best practices.

Thanks

// This is to related issue #7, so I'll cc people who might be interested in this discussion @PierBover @SebT @RichAyotte @smolinari

@gijo-varghese
Copy link

@alidcastano I'll tell you from my experience (I'm building a chat application, something like Slack)...

I was also a big fan for Vuex when using REST. But after using Apollo client, I no longer needs Vuex. Vuex is some sort of Flux/Redux implementation. It's already built in Apollo client.

All data I need is inside the Apollo cache, and I access it directly. You have full access to that cache so that you can insert, update and do everything with it.

So after playing with Apollo client cache I really think there is no need for Vuex (in my case). But in some cases, I still need a simple centralized store to save some information like selected_user_id. For that, I use Vue-stash

Again, it's from my experience. Maybe it doesn't fit for you. What are you building?

Waiting for expert opinions...

@Akryum
Copy link
Member

Akryum commented Nov 2, 2017

Side note: one of Apollo Client 2.0 good news is that we can write a vuex implementation of the apollo cache. That may open nice possibilities like getting apollo data directly in getters.
Side side note: the default in-memory cache is no longer using Redux (so you don't have Redux + Vuex anymore in your app).

@SebT
Copy link

SebT commented Nov 2, 2017

Yeah I'm eager to use apollo-client 2.0, it has so really nice features.

To answer your question from my experience @alidcastano, there is no problem with using vuex + apollo. It just depends on how you use them.

If your application uses a REST api, the Gitlab approach is very nice. You query your API, and store data in any format you wish in your store for further use in your application, vuex is not only used as a local app state manager but can also be used as a cache manager.

Apollo is made to interact with your graph API. You send a query, the result is normalized by stored in apollo's redux store, is a similar way the Gitlab approach uses vuex for.

So apollo (vue-apollo for easier integration) is the tool of choice to interact with your API data, locally and remotely. But you still need a tool to manage your local application state. Sure, components have state and you can pass is down to children components. But sometimes you need transversal data. The is where vuex comes in.

For instance you can have a currently selected item id (that comes from apollo) stored in vuex, so that you can request this item via apollo in different components. By default, if a query was already fetched, apollo will use the redux store instead of re-querying it, so don't hesitate to do the same query multiple times.

TL;DR: Use vue-apollo to interact with your graph API. Use vuex to manage your local app global state. But you shouldn't duplicate data between vuex and apollo.

@alidcast
Copy link
Author

alidcast commented Nov 2, 2017

@gijo-varghese What's interesting is that Facebook created the Flux pattern and they also created Graphql... But yea I had a similar feeling that most aspects of Vuex are not necessary given that Apollo 2.0 comes with its own cache/store implementation.

As @SebTSo mentioned, Grapql can manage component local and remote state, but you still need a way to manage application state - which is where Vuex getters (or vue stash) could come in. 👍

I'll go ahead and close this issue since the gist of the question has been answered but feel free to still add to the discussion. Thanks again guys.

@alidcast alidcast closed this as completed Nov 2, 2017
@SebT
Copy link

SebT commented Nov 2, 2017

@alidcastano: The flux pattern and graphql are made to work together. The apollo client implements the flux pattern (because it uses redux, which is derived from flux).

The only question I have now is what would be the best way to do keep everything in sync

Actually, you shouldn't imo. Apollo should be the only one handling data from API. And vuex/vue-stash shoud only manage your local data (dumb example: a click counter).

But sometimes you need to reference API data with vuex. Let's say your app is in logged mode. You often need to access the current user's name for instance. You can store the userId in vuex, but every time you need to access the name, you should to an apollo query in your component.

The confusion is caused by th fact that 2 state managment tools are present in the same app. Apollo 2 will be much more intuitive because you'll be able to use vuex for local state and API data.

EDIT: Maybe you meant "in sync" with your API data, not in sync between vuex & apollo. That makes my answer unrelevant.

But if that's what you meant, you can specify a fetchPolicy for each request so that a network request is done everytime.

@lobosan
Copy link

lobosan commented Nov 3, 2017

A fullstack example with real world cases would be super helpful or even a video tutorial in Udemy. Otherwise it feels like Vue is falling behind React which has a more mature ecosystem and examples in terms of GraphQL. Ideally there should be an official implementation of Apollo that replaces Vuex

@Akryum
Copy link
Member

Akryum commented Nov 3, 2017

@lobosan Did you take a look at https://github.com/Akryum/devfest-nantes-2017 ?

@beebase
Copy link

beebase commented Feb 4, 2018

@Akryum

Side note: one of Apollo Client 2.0 good news is that we can write a vuex implementation of the apollo cache. That may open nice possibilities like getting apollo data directly in getters.

Wouldn't it make sense to drop vuex altogether and put stuff like local session state in apollo client as well?

@SebT
Copy link

SebT commented Feb 5, 2018

@beebase nope. Apollo's job is to interact with a server via the graphql queries (or mutations & subcriptions). It stores that data locally in your app so that you don't have to query the server every time you need some data. To do so, it uses a state management library (redux in apollo-client 1.x) to manage a representation of the data in your app's memory.

Most people using vue-apollo already use vuex in their application to manage their app state (usually non-persisted state). So it makes sense to drop redux & tell apollo to store its data in vuex instead. Because you're already using it for your app local state, you don't need 2 state management libraries.

@Akryum
Copy link
Member

Akryum commented Feb 5, 2018

Using apollo as a client-side state management system is becoming more and more popular, thanks to https://github.com/apollographql/apollo-link-state

@beebase
Copy link

beebase commented Feb 5, 2018

@SebT

So it makes sense to drop redux & tell apollo to store its data in vuex instead.

Do you mean like apollo directly writing into vuex state, or apollo triggering vuex actions, that write to vuex state?

Either way, with apollo 2.0, you can make local state persistent, control interaction with server, control caching, and it has the same reactivity in components like vuex has.
So I don't see why I would still use vuex.

The only thing that's a bit funky, is writing queries inside components as opposed to vuex. It feels like you might end up with duplicate code and I wonder how maintainable this is in very large apps.
I guess you could build vue query components (factories) to avoid repetitive graphql. But then again, this might lead to over-fetching again 😕

@Akryum
Copy link
Member

Akryum commented Feb 5, 2018

@beebase Write .gql files 😄

@beebase
Copy link

beebase commented Feb 5, 2018

@Akryum yes I am, but at the same time that probably only makes sense for queries/mutations being repeated in multiple components?

@smolinari
Copy link
Contributor

@Akryum - do you not recommend to have the GQL inside of each .vue component?

Scott

@gijo-varghese
Copy link

@smolinari @Akryum me too waiting for an answer for that I've created a separate issue for that #228

@AshwinTayson
Copy link

Is there any update on this? Defining this would help with defining the best practice to link Apollo into Vue + Vuex. I have created a project with just Apollo and Vuex. If it works out fine I'll make a boilerplate. Personally it doesn't make sense to directly write the queries on components. Since these are asynchronous process it could be best handled in actions in Vuex and only the result can be returned to the component. Would help in separation of HTTP request logic.

@Akryum
Copy link
Member

Akryum commented May 29, 2018

Personally it doesn't make sense to directly write the queries on components.

This is the concept behind Apollo. Co-locate the data requirements of each component with the component itself, like you do with presentation/logic. Apollo does the rest!

The Apollo community is also moving towards apollo-link-state for local client-only data instead of relying on Redux/Vuex/etc.

@Akryum
Copy link
Member

Akryum commented May 29, 2018

For example, I recently wrote a reasonably big app which doesn't need vuex.

@resao
Copy link

resao commented Jun 16, 2018

@beebase

The only thing that's a bit funky, is writing queries inside components as opposed to vuex. It feels like you might end up with duplicate code and I wonder how maintainable this is in very large apps.
I guess you could build vue query components (factories) to avoid repetitive graphql. But then again, this might lead to over-fetching again 😕

My thoughts were the same and my approach has been to store anything related to apollo in a mixin and then include that in my individual components.

For example I have an allFruitQuery mixin that allows me to simply use 'allFruits' in any component by including my mixin

`import allFruit from '@/graphql/queries/allFruit.gql'

export const allFruitQuery = {
data () {
return {
allFruits: ''
}
},
apollo: {
allFruits: allFruit
}
}`

@typerory
Copy link

typerory commented Jul 20, 2018

More of this! Outside of reading code (which is great), I like to read/know the WHY behind it. Any good resources? Tuts? Most of the Apollo or GraphQL tutorials are for React. I've been waiting for one of you Vue/Apollo experts to put something out that I can purchase. :)

Just found: https://akryum.github.io/vue-apollo/guide/#what-is-graphql

@dreglad
Copy link

dreglad commented Jul 28, 2018

Worth mentioning these packages:

@m3nu
Copy link

m3nu commented Sep 5, 2018

I ended up using Apollo directly, similar to this approach: https://markus.oberlehner.net/blog/combining-graphql-and-vuex/

@Gerald1614
Copy link

I have been working around various approaches and tried to give a chance to using vue apollo and cache without vuex. I admit the caching woks well but one key aspect i could not solve is to be be able to use cache state in a computed property which we use a lot with vuex getters to react to change of state.
I think those comments are not 100% acurate :

This is the concept behind Apollo. Co-locate the data requirements of each component with the component itself, like you do with presentation/logic. Apollo does the rest!

The Apollo community is also moving towards apollo-link-state for local client-only data instead of relying on Redux/Vuex/etc.
while the principle of vue is to bring together presentation and logic, most of the time the logic to access to the backend is managed in separete files and stored centraly. And while apollo-link-state is not a bad tool it is far from bringing capabilities vuex provides. in sucg case we can challenge the relevance of using such a librayr compared to use apollo-client natively with vuex.

@smolinari
Copy link
Contributor

FYI also. apollo-link-state has been moved into the Apollo client core, so the idea of getting rid of Redux or Vuex has become a first class citizen. Version 2.5.0 of Apollo Client will include it and the alpha is basically ready for testing now.

Scott

@Gerald1614
Copy link

@smolinari , Hi Scott thanks for the feedback, do you know how we can use the state in a computed property. a simple exemple is I am trying to create a computed property to check if a user is logged In. so i created a isLoggedIn typeDefs in my local cache and change its value from false to true when users are logging in and out. but the process to access the cache is asynchronous and the computed property does not provide the reactivity vuex getters provide. is there any easy way to do that with apollo-link-state ?

@jdus
Copy link

jdus commented Dec 12, 2018

@smolinari , Hi Scott thanks for the feedback, do you know how we can use the state in a computed property. a simple exemple is I am trying to create a computed property to check if a user is logged In. so i created a isLoggedIn typeDefs in my local cache and change its value from false to true when users are logging in and out. but the process to access the cache is asynchronous and the computed property does not provide the reactivity vuex getters provide. is there any easy way to do that with apollo-link-state ?

When you use the local cache to store whether the user is logged in or not, why do you still need a computed property? Can't you just use a query defined in the apollo object and use result() or update() methods to deal with updates?
EDIT: What I mean is: if you query the local cache for the login status of the user, the property will be reactive. No need for a computed property, imho.

@Gerald1614
Copy link

Gerald1614 commented Dec 12, 2018

the difference is that you can use a computed property to bind data to the presentation layer very easily. The cache requires you to initiate a query while computed property is updating the UI everytime the value changes. I coudl not find a way to do it so far with the cache. ( but I am a beginner so it may just be ignorance) this is for my poitn of vue a greta value brought by vuex. you can associate a getter to a computed property and bind it to update the UI. every time the state of the store is changed the getter will trigger a change in the computed property which through the binding will impact the UI. so in my exemple it is very easy to show or hide components or DOM elements based on this value.

@smolinari
Copy link
Contributor

smolinari commented Jan 6, 2019

Now I understand what you are looking for. Thanks for keeping at the explanations. But, your wish for better readability/ more Vue-like code (from your perspective) will end up an extra layer of abstraction, which only means extra work and added verbosity. That's because in your example, this.$apolloStore.projectCreation still needs the queries, typDefs and resolvers to be created to get the needed data.

Instead of wanting to grab the central store and have it magically return the data needed, you should think in terms of querying and mutating that store. The queries and mutations themselves are also quite declarative and basically self documenting. Unfortunately this.$apolloStore.projectCreation isn't and instead of simplifying, such efforts will actually make reasoning about the code much more difficult, which goes against your wishes for helping new devs to the code.

If I may ask, what's not "Vue-like" with my Todo app in your opinion? Can you give me some concrete examples where you are going, "this looks really odd to me"? Maybe I can get your mental model changed, so you can see "the Apollo/ GraphQL way" is a very good methodology for state management, both on the server and the client. 😃

Edit: I just looked at my refactored Todo app again and to me it is totally Vue-like. It can't get any more Vue-like. The queries and mutations are within the components where they are needed, they are clear in what is wanted in terms of data and the components are all doing only one thing (aka SRP). Please do try to tell me what you don't understand or don't see as Vue-like. It doesn't get any better than that. 😄

Scott

@TheJoeSchr
Copy link

Hi Scott,

sorry I didn't mean to offend by using "vue-ish"/"vue-like" or trying to be a gatekeeper.

Unfortunately this.$apolloStore.projectCreation isn't and instead of simplifying, such efforts will actually make reasoning about the code much more difficult, which goes against your wishes for helping new devs to the code.

Maybe it wasn't the best idea of renaming it to this.$apolloStore.projectCreation, I just wanted it to be clear, that this come from my "store interface". And I'm still not really sure what projectCreation actually is, it was just the first thing I used for my example. In a real app, this line would read more like this and I can't believe that you wouldn't find this more readable:

// ViewProjects.vue
// fictional
...
export default {
  ...
  apollo: {
    allProjects: this.$state.projects
  },
  ...
}

This file which includes, while hard to read, all the flexibility and goodness of graphql/apollo is squared away and usually almost nobody working on just the vue component needs to looked at it:

// ./store/apollo/StoreInterface.js
import ALL_PROJECTS from '@/graphql/project/projectsAll.gql'
const store = new VueApollo.Store({ // inspired by Vuex.Store
 state: {
    projects: {
     get()  {
      query: ALL_PROJECTS
      fetchPolicy: 'network-only'
     },
     set() { // maybe do some mutation to add a project
     }
   }
}

Edit: I just looked at my refactored Todo app and to me it is totally Vue-like. It can't get any more Vue-like. The queries and mutations are within the components where they are needed, they are clear what is wanted and the components are all doing only one thing (aka SRP). Please try to tell me what you don't understand. It doesn't get any better than that. 😄

As I explained in my first post, what I love most about Vue is the simple and powerful way, where you have one state, a global source of truth from which everything else then reacts and flows. When you then change the state, you do it in an "atomic" way. Atomic not in the sense as used with mutex, but like more a single line of code which you can grasp with one look. Also you can glance over a Vue component in a few screens an get a pretty good idea what's happening. That's what I also find most valuable about SRP approach but of course isn't technically what SRP is about and totally misapprehension of it. So maybe it's better to say I don't care about SRP that much.

So for example in your codesandbox.io, I like how you do this:

// TodoInput.vue
 data() {
    return {
      todo: '',
      addTodo: ADD_TODO
    }

It's one line of code and I don't really need to know, that there is some graphql stuff going on. it could also be a vuex store or something completely else that manages the state. it's abstracted away, all I see is how to modify my state.

I don't care so much that the state gets modified with a SRP approach, so I would put the definition of APP_TODO in a helper/store file, with all the others I would eventually need. this is what I'm trying to do.

On the other kind, while glancing over I didn't care much for this:

// TodosList.vue
 queries: {
    getTodos: gql`
      {
        todos @client {
          id
          todo
          completed
        }
      }
    `
  },
}

but then I saw that it was used exactly like I explained above here:

// TodosList.vue
 data() {
    return {
      todos: this.$options.queries.getTodos
    }
  },

So my mind immediately got caught by the verbosity and strangeness of having gql code there, inside the component. as much as having all this nested mutation(){... update(){...}) stuff, which is just "ridiculously verbose" when you don't expect it. If this would be squared away in an helper file or "store like interface", for me it would feel more "vue-ish"

Instead of wanting to grab the central store and have it magically return the data needed, you should think in terms of querying and mutating that store.
True, but I should have to switch, to say it hyperbolic, to another language (graphql) just to fetch/mutate my store/state.

The queries and mutations themselves are also quite declarative and basically self documenting.
I think that's were I beg to differ. I believe it gets a lot easier the more you learn graphql, then it happens almost subconscious. But it still believe, managing the state inside a vue component should almost as easy and concise as doing it with a native JS struct/object.

extra layer of abstraction, which only means extra work and added verbosity. That's because in your example, this.$apolloStore.projectCreation still needs the queries, typDefs and resolvers to be created to get the needed data.

Totally true. Also in fact, really fine, I want to be able to create all the queries, typDefs and resolver, to be able to wield this power. I just feel, it shouldn't happen onthespot inside a SFC vue component, were it messes up the awesome simplicity of vue's syntax.

For me that's more worth then technical correct SRP. because in my mind it's still SRP as long as I see where the component is modifying the state. If this happens via graphql or websockets or localStorage or http request or vuex or vue-stash, that's just an implementation detail. Which in my mind a vue component should not have to care about.

Like I said, I think you two guys just have a different philosophy and approach, which is totally fine. I was just looking for someone which maybe wants to help to make it more concise or most ideally already has a best practice how to square everything away in a helper file or store like interface without reinventing the wheel like you said.

maybe I will try to use your example as an simple starting point to try to show you what I mean. It feels more approachable then having to clean up my own project first before I can try it out. Thank you for that great jumping point!

@smolinari
Copy link
Contributor

smolinari commented Jan 6, 2019

got caught by the verbosity and strangeness of having gql code there

Aha! This mindset needs to change, because the query language is a core strength of GraphQL. Any query defines exactly what is expected to be retrieved from the "store" (actually the Apollo cache). If you want to, you can put your queries in a .gql file and import them. Then you have almost what you want. But, I'd not put any other layer of abstraction on top of the queries. They are a necessity and a strength. You need to embrace them. 😄

were it messes up the awesome simplicity of vue's syntax

Well, I don't think the queries or mutations mess up the Vue syntax. I think it enhances it, a lot! Think about it this way. You are a developer and you want to understand what pieces of state are being brought into the component. Let's say, it's a blog post. And in a blog post, you could just have

data () {
return { 
  blogPost: getSomeBlogPost()
}
...

That's what you are looking for right? But, how does one know what is being delivered, what props are there to be "consumed"? The dev would need to go looking at that method and what it does.

When the gql query is in the component, the dev knows exactly what data is being "pulled" into the component for instance.

Like this:

data () {
 return { 
   blogPost: gql`
     query singlePost($slug: String!) {
       post: Post(slug: $slug) {
         id
         slug
         title
         description
         image {
            url
         }
      }
   }
   `
 }
... // the rest of the component

Now imagine that dev is needing to make a change (and regarding SRP), with the gql query (or mutation) in the component, she would simply go to the component and update it with the new field or fields needed and.... Boom! Those fields are ready to use and the dev just adds the necessary interpolation fields in the template code for rendering. With the function, the dev would have to go do some change somewhere else in code she might not understand. That breaks SRP. 😄

Now think about the dev coming to this component and just wanting to understand it. She will see right off the bat which fields are coming in from the server. And, with the @client directive, she would also directly see what is being managed on the client as client side state too.

How awesome is that?

Thank you for that great jumping point!

No problem. Just don't spend too much time with it trying to fight the flow. 😁

Scott

@TheJoeSchr
Copy link

TheJoeSchr commented Jan 6, 2019

Hi Scott,

it's true that by getting rid of the query, you have to go hunt for the fields and specifications. that's one downside of it. great thing, with using a global store, there is only one file where it could be and you already know how it's called.

But at the same time, when using vuex, I also don't have my API calls where I specify which arguments get passed directly in my SFC. This is why I feel, like I wrote in my first post, that the "final step" of vue-apollo is missing. It feels too barebones to use it in SFC directly.

I just modified your example, maybe it's clearer now: https://codesandbox.io/s/k2jyq0np0o

You are using ApolloQuery and ApolloMutation mostly, which works beautifully. The only thing I modified using them, was to bring in the query strings via my "store". But could also just see of another way how you access constants. at least they are not messing up the sfc anymore

But then I saw in Todo.vue, that you strikethrough your todo item once it's completed. BUT you do it via a @click action that basically just sets a intern property which then is used :style="`textDecoration:${labelStyle}`". That's an example of not being "vue-ish" for me.

For me the style of the component has to flow from the data directly. If a todo is completed, there should be no need for an action which then changes the style (dirty, having a separate ui style state) but the style should flow from the state (also it wouldn't work once you reload, because it's only "saved" in DOM).

So I created an computed property which returns the strikethrough depending if todo.completed is true.

computed: {
    // a computed getter
    labelStyle: function() {
      // `this` points to the vm instance
      return this.todo.completed ? "line-through" : "none";
    }

The click action now directly changes the state of the todo. It does this via the store and the component writer doesn't need to use any gql for it.

  methods: {
    toggleTodo() {
      $state.todo.complete(this.todo, 
          this.$apollo);
    }

Of course like I commented it would be even better if it would work directly via this.todo.completed = !this.todo.completed, but I fear, that's even farther away.

But the next thing I will try to work on is to make this possible:

  methods: {
    toggleTodo() {
        this.todo.completed = !this.todo.completed;
        this.$state.todo.update($this.todo);
    }

Ideally only updating fields which are changed. Or hopefully vue-apollo is already keeping track of this for me...

Obviously $state should not be needed to be imported in every file, just be accessible via this.$state. and you also shouldn't need to pass this.$apollo. I have to get my vue-plugin foo up to notch to make it possible.

@smolinari
Copy link
Contributor

smolinari commented Jan 6, 2019

great thing, with using a global store, there is only one file where it could be and you already know how it's called.

Um, in a big application, this is NOT a good thing to have. Anything "god-like" is basically a bad coding pattern.

That's an example of not being "vue-ish" for me.

I actually just changed it to a computed property myself too, because the function caused a bug. If you selected the filter for active tasks, and went back to all tasks, the strike-through was missing on the completed tasks. Oops! 😁

And, that code has nothing to do with Apollo usage really. 😃

I see where you are heading and it's also doable, i.e. creating a store for the queries and mutations. But, it's counter-intuitive to me. I mean, Guillaume was doing something similar and has decided to change it to local queries and mutations. I'll bet it's because the code is then easier to reason about and that is what Vue is all about too. 👍 You just have to agree it's the better way to make the SFC comprehensible at first sight. 😉

Scott

@TheJoeSchr
Copy link

Hi Scott,
yeah, like I already said, it comes down to preference and philosophy and I don't want to convert you individually. If you don't want it or don't like it, you don't need to use it.
I just was looking for somebody similar minded, which is maybe already thinking alongside an similar approach.. Which is not so far fetched, if you look at all the questions which pops up around this.

Um, in a big application, this is NOT a good thing to have. Anything "god-like" is basically a bad coding pattern.

But as far as I understand, Vuex also does something like it, were all the "business logic" is at the same place with all the other actions, mutations, etc. And not inside the SFCs...? Maybe when working more with it, I will also get sick of it like @guillaume. We will see.

I actually just changed it to a computed property myself too, because the function caused a bug. If you selected the filter for active tasks, and went back to all tasks, the strike-through was missing on the completed tasks. Oops!
Yeah, because in Vue, unlike eg. jQuery, you don't change the DOM/style directly, you change the data/state and let the representation handle it. Every time you change something, it gets then computed from the single source of truth, functional programming style. I love it, but sometimes hard to wrap your head around :-D

And, that code has nothing to do with Apollo usage really. 😃

No biggie, I just mentioned it just because you ask what I don't find "vue-ish" about it. Also while refactoring TodosList.vue so it's not using ApolloQuery, I saw your allDone and visibleTodos methods. I think to be more "vue-is" they should be computed too, because you already have all the data in this.todos and it get's then filtered automatically when somebody changes in the filterBar aka it gets "computed". that's what I mean with following the flow of the state.

 computed: {
    allDone() {
      if (this.todos.length === 0) return false;
      return !this.todos.some(function(todo) {
        return !todo.completed;
      });
    },
    visibleTodos() {
      switch (this.listFilter) {
        case "SHOW_ALL":
          return this.todos;
        case "SHOW_COMPLETED":
          return this.todos.filter(t => t.completed);
        case "SHOW_ACTIVE":
          return this.todos.filter(t => !t.completed);
        default:
          throw new Error("Unknown filter: " + this.listFilter);
      }
    },
  <li v-for="todo in visibleTodos" :key="todo.id"><Todo :todo="todo" /></li>
  <p v-if="allDone">You did everything, awesome!</p>

@smolinari
Copy link
Contributor

But as far as I understand, Vuex also does something like it, were all the "business logic" is at the same place with all the other actions, mutations, etc.

Sort of. The Vuex store can and should be broken up into modules and can be namespaced.

The changes to computed properties are good ones. I'll update my sandbox.

Scott

@TheJoeSchr
Copy link

Sort of. The Vuex store can and should be broken up into modules and can be namespaced.

Of course, this has to be possible with "vue-apollo-store" in the end as well. Like I said, using it should feel like just another flux store, the graphql goodness is just squared away at the store area/namespace/file/folder and not inside the SFC.

But this feels huge for somebody like me which has never written a vue-plugin. So if anybody else with more experience in the vue ecosystem feels interested in taking this on, I won't be mad! Just tell me about it! ;)

@TheJoeSchr
Copy link

Or if somebody want to give me pointers for starters I would be really grateful.

I have 2 major roadblock, where I'm not sure about whats best practices or where to even start:

First
I have to get my own $state inside every component so I can use it like above without importing in every file. I know I can look at apolloDollar function in vue-apollo, but I don't know what is chaff for my usecase and what's really needed

Second
Also what's the best practices to initialize this $store, so I can use $apollo instance the same way as this.$apollo is used now from every component. This seems to be similar on how Vuex get's it's state and mutation defined. Question is, what property from vue-apollo do I need to pass along and how to access it?

Third
This is farther down the line, but maybe I have to keep in the back of the head to take the right approach now.

I may be really cool to make something similar to how Vuex initializes it's state.

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

So it would be really cool, if this state then could be automatically mixed in with

apollo: { 
    count: gql
}

by my plugin?

Question is, is mixin the right aproach? Can I even make a mixin for another plugin? If yes, can I just populate vue-apollo's apollo:{} with my own gql template literals ?

@smolinari
Copy link
Contributor

smolinari commented Jan 6, 2019

The changes to computed properties don't work, because the ApolloQuery component seems to have a different scope. So, I have to use methods to get the todos into the scope of the component to manipulate the data. The todos data property isn't the todos themselves (so maybe bad naming), but rather just a holder for the query. Theoretically, I don't need the todos data prop. I'm going to look more into the scoping of the ApolloQuery component.

And you must admit, even with a simple app like this, the amount of additional code you are adding is a lot and it's making what is fairly simple, a lot more complicated and.....all that just so components look a bit "cleaner"? 😉

Scott

@smolinari
Copy link
Contributor

smolinari commented Jan 6, 2019

How can you ask for best practices, when what you are wanting to do has never been done?

To #2: The Apollo Client (the store) is initialized via the defaults property (see main.js). So, any data being controlled on the client through Apollo Client is initialized with the defaults property.

Scott

@TheJoeSchr
Copy link

TheJoeSchr commented Jan 6, 2019

The changes to computed properties doesn't work, because the ApolloQuery component seems to have a different scope. So, I have to use methods to get the todos into the scope of the component to manipulate the data. The todos data property isn't the todos themselves (so maybe bad naming), but rather just a holder for the query. Theoretically, I don't need the todos data prop. I'm going to look more into the scoping of the ApolloQuery component.

Hmm, it just works at my end and I'm still using the same gql I got from your original example. Maybe take a look at it?

And you must admit, even with a simple app like this, the amount of additional code you are adding is a lot and it's making what is fairly simple, a lot more complicated and.....all that just so components look a bit "cleaner". 😉

Of course I'm adding a lot of stuff now, because this "vue-apollo-store" doesn't exist yet, the whole reason I write all this.

In the end, once this exist, you should be able to just import & use it like any another plugin with 1-2 lines and then use it like this but with all the graphql/apollo instead of vuex/axios. Maybe like this?

const store = new Apollo.Store({
  state: {
    todos: ALL_TODOS // is just another gql import from a ".gql" like right now
  },
  mutations: {
    addTodo (todo) {
      this.$apollo.mutation(ADD_TODO,{todo});
    }
  }
})

Or it will be a mixin? But it shouldn't be anymore boilerplate like right now, just all in one place (or seperate namespaces if you want)

How can you ask for best practices, when what you are wanting to do has never been done?

I mean more like best practices how to interact with other plugins, how to initalize, like I wrote above

But I feel like I repeat myself now. Like I said, you don't have to use it, if you don't like the idea.

But if somebody else wants to work on it or give me some pointers, please step forward! :-D

@TheJoeSchr
Copy link

To #2: The Apollo Client (the store) is initialized via the defaults property (see main.js). So, any data being controlled on the client through Apollo Client is initialized with the defaults property.

Yeah I saw that. But my question is more, how to write your own plugin, which does something similar. So working from this, it should look something like this for the "enduser"

// store.js
import Vue from 'vue'
import VueApollo from 'vue-apollo'
import VueApolloStore from 'vue-apollo-store'

Vue.use(VueApollo)
Vue.use(VueApolloStore)
const state = {
  ...
}
export default new VueApolloStore({
  state
})
// main.js
import apolloStore from "store.js"
const client = new ApolloClient({
  clientState: {
    defaults,
    resolvers,
    typeDefs
  },
  uri: ''
})

const apolloProvider = new VueApollo({
  defaultClient: client
 })

apolloStore.setProvider({
   defaultProvider: apolloProvider
})



Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  apolloStore,
  render: h => h(App)
})

Question is, how and why does new Vue({}) now work with what you pass along here. What's minimal necessary if you take a look at eg. vue-apollo/dollarApollo.js to make it work like that. Also how can I make so vue-apollo is still working as intended, just with my store plugin on top it.

That's what I'm stuck with right now and where I hoped to get some pointers to accelerate this process. But as always, you never can jumpstart reading the docs and browsing the source ;)

@smolinari
Copy link
Contributor

smolinari commented Jan 6, 2019

Hmm, it just works at my end

Yeah, because you took out the <ApolloQuery> component. 😄

But it shouldn't be anymore boilerplate like right now

There is no boilerplate right now. Your attempts are only adding more abstraction and verbosity to an already finished abstraction, and there is no way you can simplify the Apollo Client / GraphQL API, because you still have to fulfill it to make the intended data retrieval and manipulation work.

once this exist, you should be able to just import & use it like any another plugin with 1-2 lines

It seems to me, you are trying to fit the mental model of Flux architecture into Apollo Client's non-Flux system for state management. You are doing exactly what the Apollo team are trying to overcome, needing two state management systems on the client.

you don't have to use it, if you don't like the idea.

Yeah. The idea is a waste of time and I'm sorry I can't get you to change your mind about it.

Scott

@lobosan
Copy link

lobosan commented Jan 6, 2019

This issue is too long to be useful for someone trying to figure out how to deal with state in a vue + graphql app. So it would be better to maintain a section in the official docs related to this topic or even better docs about vue + graphql in general covering authentication and authorization

@smolinari
Copy link
Contributor

@lobosan

So it would better to maintain a section in the official docs related to this issue or vue + graphql in general.

Although the vue-apollo docs (I am assuming that is what you meant with official docs?) are a bit limited and could go into more detail with examples and explanations, there is no topic to cover from this thread in the docs.

Or put another way, you don't need Vuex with Apollo Client anymore to run Vue with a GraphQL server. So, there are no best practices when using the two together. It's simply no longer necessary.

If you understand Apollo Client along with Apollo link state, then the vue-apollo docs should get you rolling with Vue and Apollo (GraphQL).

Scott

@TheJoeSchr
Copy link

It seems to me, you are trying to fit the mental model of Flux architecture into Apollo Client's non-Flux system for state management. You are doing exactly what the Apollo team are trying to overcome, needing two state management systems on the client.

I don't understand what you mean? Why would that make it two state management systems? How I imagine it and showed above, it's more like another interface to access the same state management which gets handled by vue-apollo. It's true that it adds more abstraction, but it doesn't double it, because it still refrencing, reading, mutating the same graphql/apollo-client/apollo-state-link?

@smolinari
Copy link
Contributor

smolinari commented Jan 7, 2019

it's more like another interface to access the same state management which gets handled by vue-apollo

Correct.

Why would that make it two state management systems?

Because I'm fairly certain at some point, when we have a large application, your extra layer of abstraction will also need some sort of extra management to make sense of it all. It might not be a state management system, correct, but it will be some sort of (extra/ unnecessary) system.

You are also moving the logic for reasoning about state and data control away from the point where it should be controlled.

And lastly, you are trying to make a declarative system into a different type of declarative system.

This may sound harsh, and I don't want to discourage you or hurt any feelings, but you are solving a problem that is only between your own ears. 😊

Scott

@smolinari
Copy link
Contributor

smolinari commented Jan 7, 2019

I'm going to add one more thing to hopefully change your mind @JoeSchr . The query and mutation calls in the components have the ability to "define the shape of the returned result data". In other words, if the developer can see what can be returned from either a GraphQL endpoint or a client resolver, then she can ask (query) for only specific fields of that data. And again, this is another one of the great advantages to GraphQL. It is also that extra verbosity you have an issue with in the component. The query in the component isn't THE query, it's A query for data, but in the specific form the component needs it in (and why it may seem verbose). The Todo app doesn't demonstrate this response structure control, unfortunately.

So put another way, your intentions of redefining queries into simpler commands is obscuring this ability and flexibility to define the response structure. For every query needed with a new response structure, and in a big app it could get quite intense, you'd have to come up with a new wrapping function, or even a new DSL, and doing all that is just so you can be at ease with what you are seeing? I get it too. I hate JSX and this is sort of like JSX. But, you are a smart guy. Dream a little and imagine the consequences of your intentions. Please. 😉

Btw, I've updated my Todo app to delete completed tasks too. 😄

https://codesandbox.io/s/k3621oko23

@Akryum - if you consider it worthy (and I'd really like your opinion on it), I'd like to add it to the examples in the docs for client side state. My version of the app was originally Sara Viera's example app. I've just enhanced it some. 😄

Ok? 🙏

Scott

@Akryum
Copy link
Member

Akryum commented Jan 7, 2019

@smolinari Why not! 😸

@TheJoeSchr
Copy link

For every query needed with a new response structure, and in a big app it could get quite intense, you'd have to come up with a new wrapping function, or even a new DSL, and doing all that is just so you can be at ease with what you are seeing?

A valid point, but also just another tradeoff

I think one has to take this into considerations with the kind of application they builds. But I would rather have there be a choice than there be no choice at all.

For the kind of application I'm doing right now, only querying a subset of the whole dataset is not a usecase that happens often (if at all). On the other hand, the components are already very big and unwieldly, so I really hestitate to make it even more verbose.
You probably will say, that's a sign it needs to be splitted up, but the reason is not to many responsiblities, rather just because there are a lot of inputs that needed to be filled out, imagine like a big tax form sheet.

Further on I have gotten really sick of having all my data queries sprawled all around the place, which really makes bug hunting an issue. This firstly led me to search for a vuex like global state management, where you have a better handling on state management changes via dedicated mutations/actions.

That's all more important to the kind of application I'm working on, then having granular control over how much I fetch, because I mostly fetch everything.

So my plan right now is to replace all the axios calls with apollo (inside each) component and decide from there if this is good enough or if it still causes the same issues and move some or most into a store like entity.

@TheJoeSchr
Copy link

TheJoeSchr commented Jan 7, 2019

Btw, I've updated my Todo app to delete completed tasks too. 😄

Nice, I was thinking of adding that along as well, to get a better feeling for it. But then the strikethrough caught my eye and I decided this was a smaller challenge to try it out

@smolinari
Copy link
Contributor

smolinari commented Jan 7, 2019

the components are already very big and unwieldly
You probably will say, that's a sign it needs to be splitted up,

You are correct! 😄

Just to show you how you can "split it up", check out this fiddle.

https://jsfiddle.net/smolinari/65meb0px/

It's using Quasar Framework.

This form is only three questions and is a survey, but the whole concept can be scaled to any size form. Also, currently the data is being formed and given in the component. Imagine the form being built automatically, where each input field type does its own mutation. That is incredibly powerful.

which really makes bug hunting an issue.

Is it a REST API you are using? If yes, this is where GraphQL(done right) is going to blow it out of the water. Front-end developer efficiency both in coding and troubleshooting was one of the reasons why Facebook came up with GraphQL, believe it or not. 😃

Scott

@smolinari
Copy link
Contributor

@Akryum - PR is up! 👍

Scott

@drewbaker
Copy link

I spent months battling with Vue Apollo (via Nuxt Apollo) and trying to get the Apollo queries/subscriptions to actually be reactive like a Vuex store would be, and all the verbose code you need to write to update the local state after each mutation. I read this thread and I can see that a lot of people feel the same way as I do.

I decided to use VuexORM to handle all my data, it took me a few hours to get setup and define my models, and I have to say it is 10x better than using the Apollo Local state. Yes you end up with "two sources of truth" in that you have the Apollo cache/local state, and Vuex, but if one of those sources of truth is a pain in the ass to use, then I'm happy to just ignore it!

If anyone is looking for a better way to handle complicated data in their Vue or Nuxt app, this will probably make you life easy!
vuex-orm/vuex-orm#651 (comment)

@beebase
Copy link

beebase commented Jun 10, 2020

I was also thinking of using vuex orm as a local "database" and only using graphql for fetching data from the server that gets normalised into vuex relational tables. I believe there's even a graphql plugin for vuex orm. As tempting as graphql looks, It still feels scary using it with local relational database structures

@smolinari
Copy link
Contributor

smolinari commented Jun 11, 2020

I'm not sure what the problem might be which you are having with reactivity. @drewbaker If you update the cache via @client, then the queries that are involved should update too (ie. are reactive).

Was it mainly subscriptions being an issue by chance?

I can't disagree that the code you need to write to update the cache can get a bit verbose. I was thinking it needs an abstraction like an ORM too and then it would be awesome and most definitely a simplification from having two sources of truth and more importantly, keeping them in sync. I feel using Vuex at all is defeating the whole purpose of a GraphQL client with Vue. You might as well stick with REST. :)

It still feels scary using it with local relational database structures

You haven't found the GraphQL paradigm yet. Keep plugin at it! 😁

Shameless plug (pun not intended): Have a look at an article I wrote on the subject. It will either confuse you more or get GraphQL to click for you.

Scott

@drewbaker
Copy link

Thanks Scott. I’ve since replaced Nuxt Apollo with https://www.npmjs.com/package/nuxt-graphql-request and VuexORM (on larger projects) and it works fantastic.

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