vuejs / apollo

🚀 Apollo/GraphQL integration for VueJS
http://apollo.vuejs.org
MIT License
6.02k stars 522 forks source link

vue-apollo and vuex interaction best practices #140

Closed alidcast closed 7 years ago

alidcast commented 7 years ago

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 commented 7 years ago

@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 commented 7 years ago

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 commented 7 years ago

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 commented 7 years ago

@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.

SebT commented 7 years ago

@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 commented 7 years ago

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 commented 7 years ago

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

beebase commented 6 years ago

@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 commented 6 years ago

@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 commented 6 years ago

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 commented 6 years ago

@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 commented 6 years ago

@beebase Write .gql files 😄

beebase commented 6 years ago

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

smolinari commented 6 years ago

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

Scott

gijo-varghese commented 6 years ago

@smolinari @Akryum me too waiting for an answer for that I've created a separate issue for that https://github.com/Akryum/vue-apollo/issues/228

AshwinTayson commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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

resao commented 6 years ago

@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 commented 6 years ago

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 commented 6 years ago

Worth mentioning these packages:

m3nu commented 6 years ago

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

Gerald1614 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago

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.

oller commented 5 years ago

I too have been wrestling with the best combination of Vue Apollo, Vuex etc. I've a trusted pattern with Vuex but that was a rest API backend. Moving to graphQL presents an opportunity to move the store to Apollo. As with @Gerald1614 the main gap in my knowledge is implementing the equivalent of getters

typerory commented 5 years ago

I too have been wrestling with the best combination of Vue Apollo, Vuex etc. I've a trusted pattern with Vuex but that was a rest API backend. Moving to graphQL presents an opportunity to move the store to Apollo. As with @Gerald1614 the main gap in my knowledge is implementing the equivalent of getters

I am the furthest possible one can be from an expert. That being said maybe this will help: https://blog.apollographql.com/the-future-of-state-management-dd410864cae2

@Gerald1614 Hasura (Postgres only) gives you subscriptions: https://hasura.io/all-features#realtimeSubscriptions

jdus commented 5 years ago

@Gerald1614 A query defined in the apollo property, will be reactive. For example:

apollo: {
    loggedIn: {
      query: gql`
          query {
            loggedIn @client
          }`
    }
}

The property 'loggedIn' in the component, will be updated whenever 'loggedIn' in the cache is updated. Of course you can now use 'loggedIn' in your template and it will be reactive. But maybe I still don't get you and this is a big misunderstanding.

Gerald1614 commented 5 years ago

thanks @jdus. I tried this approach and it worked. I do not know why i focused on using a local variable to bind to the query instead of using the apollo object. do you know how we can make the apollo object accessible in an external JS file ? i am trying to use authguard to protect some routes and need thus to access the query from an external js file.

Akryum commented 5 years ago

Here is a real-life example: https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-ui/src/router.js

smolinari commented 5 years ago

Um. is that using apollo-link-state?

Scott

Akryum commented 5 years ago

Use the @client directive.

Gerald1614 commented 5 years ago

@Akryum merci. the exemple was very useful. i could make it all work. I just add to modify a lot the vue-apollo.js file as the one generated by vue-CLI was not exporting apolloClient. i modified it like in your exemple and made everything work. very constructive discussion, the product is very good.

smolinari commented 5 years ago

Use the @client directive.

Looking through the code, I did find it was being used. Then I was just baffled as to where link state was being imported. But I dug a bit deeper and found it. 😁

https://github.com/Akryum/vue-cli-plugin-apollo/blob/master/graphql-client/src/index.js

Scott

oller commented 5 years ago

Seeing as this thread seems to have some attention at the moment, I was hoping to get some guidance on how to best implement the equivalent of vuex getters on data retrieved (and cached) via vue-apollo.

For a simple scenario, I've a graphQL query returning an array of numbers, that I need to reduce down via say a getTotal() method. I understand how a vuex getter would perform this on the state, but i'm not sure in this scenario, how best to structure and implement this.

Should I be adding these server side into the types/resolvers?

Thanks in advance!

TheJoeSchr commented 5 years ago

@Akryum wrote

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

Hi, I think this is a wonderful example what currently is missing in the current state of vue-apollo/graphql ecosystem. I find the cli-ui project is beautifully organized and written. But as someone with limited experience with graphql, this feels like the scene in the "Matrix" where Neo sees the matrix code for the first time and all in all feels very un-"vueish".

I truly believe @Akryum only sees beautiful bindings, but for me it looks like a lot of obscure code.

This is not meant as an offense. Let me explain where I'm coming from. I started to fell in love with vue after seeing the first example of it, like this:

<div id="app">{{ message }}</div>
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

and also the sugar of using state as a single source of truth for all your components:

// pseudo-code cobbled together from vuex.vuejs.org
new Vue({
  // state
  data () {
    return {
      count: this.$store.state.count
    }
  },
  // view
  template: `
    <div>{{ count }}</div>
  `,
  // actions
  methods: {
    increment () {
      this.store.commit('increment')
    }
  }
})
// Make sure to call Vue.use(Vuex) first if using a module system
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

Even here, in this vuex example, having to use a mutation to modify count almost feels to far away from a simple state management approach, but acceptable, because it's encapsulated

I'm currently in the way of refactoring code for my project. I started out without vuex or any other kind of global state management, because as vuex states on their site, you will know when you need it.

Now for my current project, we have reached a burning desire for a global state store. My research firstly of course took me to Vuex. But we are really sick of the REST api approach, where you cobble together your state with a lot of tiny request, e.g. /api/posts/ then api/posts/1/ then api/posts/1/comments to get the full picture. So I think apollo/graphql, where you get all you need in a nice json in one request, not more not less and then you have it as a "state-like" entity to work and update it, this is really the future and the idiosyncratic way to along great with Vues philosophy.

But using vue-apollo in the current state, feels like there is an final step missing. With all the direct calls to $apollo for mutation, update, etc, it seems to me one of the strength of vue is lost: components are simple/readable enough for a junior developer or even a designer to start working with it.

Example given from the vue-cli/vue-ui link posted above

// ProjectCreate.vue
import PROJECT_CREATION from '@/graphql/project/projectCreation.gql'
import FEATURE_SET_ENABLED from '@/graphql/feature/featureSetEnabled.gql'
import PRESET_APPLY from '@/graphql/preset/presetApply.gql'
import PROJECT_CREATE from '@/graphql/project/projectCreate.gql'
import PROJECT_CANCEL_CREATION from '@/graphql/project/projectCancelCreation.gql'
...
export default {
  name: 'ProjectCreate',
  ...
  apollo: {
    projectCreation: {
      query: PROJECT_CREATION,
      fetchPolicy: 'network-only'
    }
  },
  ...
  methods: {
    async selectPreset (id) {
      this.formData.selectedPreset = id
      if (id === '__remote__') {
        this.showRemotePreset = true
        return
      }
      await this.$apollo.mutate({
        mutation: PRESET_APPLY,
        variables: {
          id
        },
        update: (store, { data: { presetApply } }) => {
          store.writeQuery({ query: PROJECT_CREATION, data: { projectCreation: presetApply } })
        }
      })
    },

I totally understand why then somebody like @beebase #7 would say, that this looks just "ridiculous".

I think we need a further abstraction, so we could use vue-apollo like just another global state store.

We would have a single store file which we can import and use in all our components instead of importing all .gql separately. So taking the example above, it should look something like this, where all the graphql minutiae are hidden away for the "enduser" so it should really look simple and readable in a component, eg:

// ProjectCreate.vue
/* syntax to access apollo store should be very simple and clean
 * components should render data reactive and 
 * if they need to change data it should be in best case like just access a struct
 * or like a simplified mutation function call
 */
...
export default {
  name: 'ProjectCreate',
  ...
  apollo: {
    projectCreation: this.$state.projectCreation
  },
  ...
  methods: {
    async selectPreset (id) {
      ...      
      this.$state.preset = id;

    },
// @/graphql/store
/* all the hard and "messy" stuff is implemented in this single store file
 * so everybody else using it doesn't have to know how to use graphql or
 * even if we are using graphql or just another REST api/vuex store
 */
import PROJECT_CREATION from '@/graphql/project/projectCreation.gql'
import FEATURE_SET_ENABLED from '@/graphql/feature/featureSetEnabled.gql'
import PRESET_APPLY from '@/graphql/preset/presetApply.gql'
import PRESET from '@/graphql/preset/presetFragment.gql'

const store = new VueApollo.Store({ // inspired by Vuex.Store
 state: {
    projectCreation =  {
      query: PROJECT_CREATION,
      fetchPolicy: 'network-only'
    },
    preset: {
      get(): {
      query: PRESET,
      fetchPolicy: 'network-only'
      },
      set (id): {
        return await this.$apollo.mutate({
        mutation: PRESET_APPLY,
        variables: {id:id},
        update: (store, { data: { presetApply } }) => {
          store.writeQuery({ query: PROJECT_CREATION, data: { projectCreation: presetApply } })
        })
      },
    },
   }
    }
  },
})

Right now I'm trying to cobble together something like this, but as you can see from my "pseudo-code", my expertise with vue-plugin system is very limited and my apollo/graphql skills are null.

My road of approach would be to use vue-apollo/DollarApollo as inspiration on how to get "my" $store inside of every Vue component, so it could then maybe as simple as that:

// main.js
/* example pseudo-code how I imagine you would import it into your app */
import VueApolloStore from 'vue-apollo-store'
import store from '@/graphql/store'

Vue.use(VueApolloStore);

const app = new Vue({
  el: '#app',
  // provide the store using the "store" option.
  // this will inject the store instance to all child components.
  state: store.state,
})

But with my limited knowledge, one major roadblock already hit today is: how to get this.$apollo inside my "store"? what's best practices are there to get access to another plugin from inside a plugin?

So I think this is getting a while before I get something useful to share done. Therefor I wanted to ask is somebody maybe already working on something like this "vue-apollo store wrapper" or even has solved it for himself and want to share it?

Would it be even possible to abstract it away like this or do the components need to have the detailed access like now? I think at least it would get people "just use" apollo/graphql, instead of worrying about minutia like "sending the same request too often" when instead vue-apollo already caches it beautifully.

Or did I miss something huge why we can't have a "simple" global store/state file without losing vue-apollo greatness/flexibility?

From all the "best practices", "vuex integration" and "apollo-link-state" questions like e.g. #384 #161 #7 and this thread which seams to pop up pretty regularly, it seems to me there is a clear need for something like this?

So if somebody with slightly more vue/apollo/graphql experiences want to take the lead and run with this, I wouldn't mind and be very grateful. Or even weight in with their experience/opinion?

Anyways I will try to truck along with this, but I don't even know yet how to make a vue-plugin, even less how to publish it to npm/github/etc so people could work together on this...

smolinari commented 5 years ago

Hey @JoeSchr,

I know exactly where you are coming from. And what Guillaume has done with Vue UI is like major league advanced GraphQL (he might not think it is, but it is). So, trying to make sense of it without knowing the basics is like trying to understand Chinese sentences, when you only understand like 10 characters of the Chinese alphabet (and there are 3000). 😄 I hope I'm not insulting you either. No intention there for sure.

So, I'd like to give you piece of mind and help you find the basics you need.

First off, as you have noted, GraphQL is a major advancement against REST APIs, simply because the GraphQL queries, mutations and subscriptions can be co-located or "designed" on a per component basis. It allows the front-end dev to work much more quickly, because she doesn't have to put too much cognitive load into understanding the API and making it work with the UI. With GraphQL, getting and mutating the data at component level just works!

More good news is, link-state, the part of Apollo which allows for controlling state on the client, is going to be a core part of Apollo-Client as of version 2.5 (in Alpha, and Beta is coming soon!!!)

As for learning, I have two things I think you should look at, which demonstrate exactly what you are looking for within GraphQL (through Apollo Client) and can teach you the basics.

One is an example Todo app that I put together, which shows how you can use vue-apollo and Apollo-Client (with link-state) for client-side manipulation of state (without Vuex).

https://codesandbox.io/s/k3621oko23

It's simple enough, but should also get you thinking along the lines of what needs to happen to make Apollo your client-state friend. Of course, a more advanced application would even mix and match state on the client and the server. That is the real beauty of Apollo. 😃

The next thing I'd like to you look at is the new docs for Apollo-Client's full stack tutorial with example app. It is written with apollo-react, but the whole idea of query and mutation components is what you will be learning (which vue-apollo also offers).

https://www.apollographql.com/docs/tutorial/introduction.html

Two points on this. The example app is running off of the newest alpha version of Apollo Client (which I mentioned above) and isn't quite working 100% correctly, as there is a small issue with the refetchQueries on mutations and updating the React state (it might be a react-apollo issue too). Also, the whole bit about Zeit.co and uploading the schema to Apollo Engine you can skip.

Important is, this tutorial does get you into the basics of Apollo Client, which you need to learn. Doing the whole tutorial should take you about 2-3 hours.

I hope that helps you. Once you have these things under you belt, vue-apollo should make a good bit more sense, especially with not needing to use Vuex with your Vue application, while using Apollo Client.

It's too cool for school!!! 😄

Scott

smolinari commented 5 years ago

Btw, I am going to be porting that Apollo-Client tutorial over to a tutorial for working with Quasar Framework (which is a Vue based Framework). The main intention of the tutorial will be teaching how to work with Quasar, but inadvertently the student will get to learn Apollo and GraphQL too. I'll be finished with the app port by the end of the month and the tutorial by the end of February. If you'd like updates on that, please join the Quasar Framework Discord Channel. 👍

Scott

TheJoeSchr commented 5 years ago

Hi Scott,

you are right, it's major league GraphQL over there! ;)

I already found the examples you posted and read all the introductions/tutorial I found. I understand whats happening in the code of cli-ui (more or less) and I know about withClientState from apollo-link-state and how to use it.

But the point I was trying to make is not that graphql/vue-apollo is too hard to use or that I don't get it, in fact I love it.

I just think there should be one more abstraction layer between the "business logic" of vue-apollo/graphql and vue representation components, so that you or somebody else you work with can still see at one glance what this component does.

That's what I was trying to show with my example of

// ProjectCreate.vue
// simplified
...
export default {
  ...
  apollo: {
    projectCreation: this.$apolloStore.projectCreation
  },
  ...
  methods: {
    selectPreset (id) {
      ...      
      this.$apolloStore.preset = id;
    },
    createProject() {
      await this.$apolloStore.createProject(this.form);
    }

The question is or better said, where I'm struggling with currently, what would be the best practice to get such an store in (every) component?

After I wrote the last post, I was also thinking about maybe using an mixin, so the store could automatically populate the apollo part with all of it's own field.

export default {
    ...
    apollo: {
       // a mixin could maybe populate this with every gql query from $apolloStore
    }

Another question were I sit right now: How can I use this nice setter/getter syntax of eg. vue computed properties without reinventing the wheels?

Basically I feel like this is a good idea, but I may have not the javascript/vue-interna chops to build it, but with what I could now find out about vue-apollo works/is received by reading documentations and all the questions and issues which seem to rise again and again about best practices, that this is something really needed for vue-apollo and graphql to get traction.

Because today it's way easier and more "vue-ish" to just use Vuex/REST instead of vue-apollo. So I think maybe if vue-apollo becomes "cleaner" it would also be easier to use.

How I see it:

What's missing now is a store that works with vue-apollo or is build with vue-apollo in mind, so you don't have all the unneeded stuff you get with a e.g. vuex/apollo aproach or reinvent the wheel, because all the hard stuff like caching,clientState, etc is already down. All that is missing seems to be this "store interface", then vue-apollo would be amazingly wonderful to work with.

Something like my example above so all that gql`query` and mutation, update, etc messiness is not directly straggling around in every component but sits nice and orderly in one global store file.

Edit: Tried to make my point clearer at the end

Akryum commented 5 years ago

@JoeSchr To me, one of the main advantage of Apollo is being able to collocate the data requirements of a component with itself. I also recently started using gql inside component directly instead of separate .gql files (will refactor cli-ui in the future).

smolinari commented 5 years ago

what would be the best practice to get such an store in (every) component?

You can't or rather you don't need to. You create your queries and mutations per component. What you are looking for is that "connectivity" to a Flux store, when in effect, there isn't that kind of store anymore, Instead, you are offered a system with a smart cache management, which you can access via the @client directive.

So you don't have a this.$store = that kind of functionality in the component, but rather a

<template>
  <ApolloQuery 
    :query="that"
  >
  <div>
    <!-- do what you need to do with the data here -->
  </div>

  </ApolloQuery>
  <!-- more Queries or Mutations or other elements here -->
</template>

<script>
import gql from 'graphql-tag'

const that = gql`
  {
    that @client {
      id
      someProp
      someOtherProp
    }
  }
`
export default {
  data () {
    that: that
  }
}
</script>

functionality.

And of course you'd need to create definition types and client resolvers, where needed.

This querying methodology may seem much more verbose at first, but theoretically with the Flux store, you'd need to add $store retrieval logic, some sort of caching (if needed), pagination (if needed), API calling, deciding how to retrieve local and server data, etc. etc and it all be centralized away from your component logic. That makes reasoning about your what state goes where and when fairly difficult.

All those "state concerns" I just mentioned above are "standardized" or given within the Apollo / GraphQL system's APIs. Plus you get the loading feature, network error warnings and more. The best part, the data retrieval and manipulation logic can be right next to or even in your component (as Guillaume mentioned) which is less cognitive load for devs (and yourself) when returning back to the component code later. That in itself is priceless, because it is helping you with SRP. I mean, if you have too many queries or mutations, you know your component is doing too much (like in my Todos.vue example btw!!! I think I'll change that too.....EDIT: The code now is refactored.). 😁

So, don't shy away from needing to write a bit more code in the component. It's a lot less (mental) work in the end with GraphQL. 😄

@Akryum - how about making gql global within Vue instead of having to import it in every component?

Also, I've refactored my Todo app to have the queries in the components. Would you consider the use of the options a correct way to define queries? Or is there a better way? I tried my best to declare the query in TodosList.vue as an extra named export, but being the component is already the default export and my ES6 modules foo isn't very good, I couldn't get it to work.

https://codesandbox.io/s/k3621oko23

😃

Scott

TheJoeSchr commented 5 years ago

Hi Guys,

thx for weighting in!

@Scott:

I'm afraid I haven't made myself very clear:

What you are looking for is that "connectivity" to a Flux store, when in effect, there isn't that kind of store anymore, Instead, you are offered a system with a smart cache management, which you can access via the @client directive. This querying methodology may seem much more verbose at first, but theoretically with the Flux store, you'd need to add $store retrieval logic, some sort of caching (if needed), pagination (if needed), API calling, deciding how to retrieve local and server data, etc. etc and it all be centralized away from your component logic. All those "state concerns" I just mentioned above are "standardized" or given within the Apollo / GraphQL system's APIs. Plus you get the loading feature, network error warnings and more.

Yes, I know all of that goodness and that's what get's me excited about vue-apollo/graphql. BUT I'm not talking about replacing it with a store, merely of offering a different interface on top of it, which feels more "vue-ish" then the verbose way now.

"My store" interface how I imagine it would just pass-through all the apollo-goodness while on the same time offering a simpler to read experience.

// @/graphql/store
import PROJECT_CREATION from '@/graphql/project/projectCreation.gql'
const store = new VueApollo.Store({ // inspired by Vuex.Store
 state: {
    projectCreation =  {
      query: PROJECT_CREATION,
      fetchPolicy: 'network-only'
    },

No need to reinvent everything vue-apollo/apollo/apollo-link-state already does. Just a less verbose interface for use in components with the added plus that all the business logic is in one place.

So for example, a developer, who is really good with vue in general but doesn't know much about apollo/graphql. Imagine he now wants/needs to add something to vue-cli/vue-ui and looks at an component, he probably would be very afraid to touch anything because of fear of breaking something, because it's all very verbose and graphql specific.

If instead he would just see a few lines like this:

export default {
  ...
  apollo: {
    projectCreation: this.$apolloStore.projectCreation // immediately clear this will just be projectCreation object
  },
  methods: {
     createProject() {
       await this.$apolloStore.createProject(this.form); // immediatly clear this just adds an Project
    }
 }
}

They just know without any deeper intimacy of graphql that at this line just a new project is added or that they can just read from projectCreation. More generally said, in this lines a state is just fetched/changed. No need for a context switch to another language like graphql. It should be really easy for this component to later switch it to another store like vuex or use another http/REST api if so desired.

The best part, the data retrieval and manipulation logic can be right next to or even in your component (as Guillaume mentioned) which is less cognitive load for devs (and yourself) when returning back to the component code later.

That in itself is priceless, because it is helping you with SRP.

To me, one of the main advantage of Apollo is being able to collocate the data requirements of a component with itself. I also recently started using gql inside component directly instead of separate .gql files (will refactor cli-ui in the future).

That's are all valid points, I understand. I think our philosophy how a component should look and act just differ. I was looking here if maybe somebody shares my kind of philosophy and already did something in this direction or has more experience/foo with vue-plugins/apollo and wants to try create something/help with it. Maybe I will just share what I got once I got something to run, no matter how hackish it gets, so you guys can get the gist of it. Once again I'm not trying to replace vue-apollo with a store, just give add another layer interface so it feels more like a typical flux store when working with it and reads less verbose.

You can't or rather you don't need to.

I think I may just get some inspiration how vuex-apollo does it then. They must have access to vuex instances as well as apollo.

Edit: added last part, pressed post comment too soon by accident ;)

smolinari commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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>