Apollo query that depends on another query's result - vue.js

I’m trying to create an Apollo query (in Vue/Nuxt) that depends on the result of another query.
sessions needs person in order to use this.person.id. How can I ensure that person exists before getting data for sessions?
My script and queries are below. Both queries work fine in GraphiQL. Thank you!
JS
import gql from 'graphql-tag'
import Photo from '~/components/Photo.vue'
import Session from '~/components/Session.vue'
import {Route} from 'vue-router'
import { currentPerson, currentPersonSessions } from '~/apollo/queries.js'
export default {
name: 'Speaker',
props: ['slug', 'id', 'person'],
data() {
return {
title: 'Speaker',
routeParam: this.$route.params.slug
}
},
apollo: {
person: {
query: currentPerson,
loadingKey: 'loading',
variables() {
return {
slug: this.routeParam
}
}
},
sessions: {
query: currentPersonSessions,
loadingKey: 'loading',
variables() {
return {
itemId: this.person.id
}
}
}
},
components: {
Photo,
Session
}
}
Queries
export const currentPerson = gql `query ($slug: String!) {
person(filter: { slug: { eq: $slug }}) {
name
bio
affiliation
id
photo {
url
}
}
}`
export const currentPersonSessions = gql `query($itemId: ItemId!) {
allSessions (filter: { speakers: { anyIn: [$itemId] }}) {
title
slug
start
end
}
}`

I managed to solve this by breaking out the part the sessions into a separate component.
Is that the appropriate way to handle a case like this, or is there a better way by chaining the requests here? If there's a way to do this, I still wouldn't mind hearing other answers.

I think the best way is to have a proper graph of your data model like :
type Person {
name: String!
bio: String
affiliation: String
id: ID!
photo: Photo
sessions(filter: SessionFilter): SessionList!
}
With GraphQL, you model your business domain as a graph
Graphs are powerful tools for modeling many real-world phenomena because they resemble our natural mental models and verbal descriptions of the underlying process. With GraphQL, you model your business domain as a graph by defining a schema; within your schema, you define different types of nodes and how they connect/relate to one another. On the client, this creates a pattern similar to Object-Oriented Programming: types that reference other types
You can read more in the official docucmentation

Related

What's the best way to transform vue-query results to deal with belongsTo relationships?

So I'm using vue-query to get data from my API. The current way I'm doing that looks a little like this. I have a folder in src called hooks, and it may contain a file such as usePosts.ts. That file contains code like this:
import { useQuery, useMutation, useQueryClient } from "vue-query"
import axios, { AxiosError } from "axios"
import {
performOptimisticAdd,
handleMutateSuccess,
handleMutateError,
} from "./handlers"
export interface Post {
id: number
title: string
body: string
user: number // user_id
}
export function usePostsListQuery() {
return useQuery<Post[], AxiosError>(
"posts",
() => axios.get("/v1/posts").then(resp => resp.data),
{ placeholderData: [] }
)
}
export function useAddPostMutation() {
const client = useQueryClient()
return useMutation<Post, AxiosError>(
post => axios.post("/v1/posts", post).then(resp => resp.data),
{
onMutate: performOptimisticAdd(client, "posts"),
onSuccess: handleMutateSuccess(),
onError: handleMutateError()
}
)
}
Of course I'm not showing all the code, for brevity.
Now in my Vue components, I'm often doing something like this:
<script setup>
import { usePostsListQuery, useAddPostMutation } from "#/hooks/usePosts";
import { useUsersListQuery } from "#/hooks/useUsers";
const { data: posts } = $(usePostsListQuery())
const { data: users } = $(useUsersListQuery())
const { mutate: addPost } = $(useAddPostMutation())
const postsWithUsers = $computed(() => posts.map(
post => ({ ...post, user: users.find(user => user.id === post.user) })
))
const addPostWithUserId = (newPost: Post) => addPost({ ...newPost, user: newPost.user.id })
</script>
Because I want to be able to directly access the user associated with a post. And of course, the way I'm doing it works. But it doesn't seem right to do that transformation inside a Vue-component. Because that means I need to repeat that same code inside every new Vue-component.
So I'm wondering what would be the best place to do this transformation. One obstacle is that useQuery() may only be called during / inside the setup() function. So I'm a bit limited in terms of where I'm allowed to call these queries.
Maybe I could just put it inside usePosts.ts? But is that really the best place? I can imagine that it might make all my hooks very messy, because then every hook suddenly has TWO responsibilities (talking to my API, and transforming the output and input). I feel like that breaks the single responsibility principle?
Anyhow, this is why I'd love to hear some of your opinions.

ember multi level has many serialization issue

I am facing some issues with my serialization of model. Below is a detailed code which will show how my current model and corresponding serializers look.
User model
export default Model.extend({
name: attr('string'),
accounts: hasMany('account', {async: false})
});
Account model
export default Model.extend({
type: attr('string'),
transactions: hasMany('transaction')
})
Transaction model
export default Model.extend({
date: attr('string'),
amount: attr('number')
})
So basically its a hasMany within another hasMany.
Serializers looks like this:
base serializer:
export default BaseSerializer.extend({
keyForAttribute(key) {
return key.underscore();
},
keyForRelationship(key, relationship) {
return key.underscore();
}
});
User serializer:
export default BaseSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
account: { embedded: 'always' }
}
});
Account serializer:
export default BaseSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
transaction: { embedded: 'always' }
}
});
Now when I am invoking api call which gives me a json response where user model has a property named as accounts: which internally have another property called as transactions, I am expecting them to serialize but somehow its not working. have I done anything wrong here? Please advise as I am new to ember and still learning it.
Base serializer is:
export default DS.JSONSerializer.extend(DS.EmbeddedRecordsMixin, {
keyForRelationship(key, relationship) {
return key.underscore();
}
})
Serialized json
I dont have it but from the console logs, seems like only user model is getting serialized as when I tried to print console.log(user.get('accounts').get('firstObject').get('type') then i saw undefined there.
What I want is:
{
name: "bhavya"
accounts: [
{
type : 'savings',
transactions: [
{
amount: 500
}
]
}
]
}

How Can I correctly use dynamic variables in react-apollo graphql query?

I have an apollo-wrapped component that's supposed to provide my component with response data from the github graphql v4 api. I intend to use a string(SEARCH_QUERY) from another part of the app to be used in my gql query but github keeps returning undefined. I am following offical apollo docs http://dev.apollodata.com/react/queries.html#graphql-options.
I dont see what I am doing wrong.
import React, { Component } from 'react';
import { Text, FlatList } from 'react-native';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import { SEARCH_QUERY } from './Home' // this is a string like "react"
// The data prop, which is provided by the wrapper below contains,
// a `loading` key while the query is in flight and posts when ready
const ReposList = ({ data: { loading, search }}) => <Text>SearchResults</Text>
// this doesnt work because I cant properly inject 'SEARCH_QUERY' string
const searchRepos = gql`
query searchRepos($type: searchType!, $query: String!) {
search(type: REPOSITORY, query: $query, first: 100) {
edges {
node {
... on Repository {
nameWithOwner
owner {
login
}
}
}
}
}
}
`
// The `graphql` wrapper executes a GraphQL query and makes the results
// available on the `data` prop of the wrapped component (ReposList here)
export default graphql(searchRepos, {
options: { variables: { query: SEARCH_QUERY }, notifyOnNetworkStatusChange: true }
}
)(ReposList);
This query without variables works well and returns search results as expected. straight forward, right?
const searchRepos = gql`{
search(type: REPOSITORY, query: "react", first: 100) {
edges {
node {
... on Repository {
nameWithOwner
owner {
login
}
}
}
}
}
}
`
When this is used github returns undefined.
const searchRepos = gql`
query searchRepos($type: searchType!, $query: String!) {
search(type: REPOSITORY, query: $query, first: 100) {
edges {
node {
... on Repository {
nameWithOwner
owner {
login
}
}
}
}
}
}
`
Your query is erroring out because you've defined a variable $type -- but you don't actually use it inside your query. You don't have to actually send any variables with your query -- you could define one or more in your query and then never define any inside the graphql HOC. This would be a valid request and it would be up to the server to deal with the undefined variables. However, if you define any variable inside the query itself, it has to be used inside that query, otherwise the query will be rejected.
While in development, you may find it helpful to log data.error to the console to more easily identify issues with your queries. When a query is malformed, the errors thrown by GraphQL are generally pretty descriptive.
Side note: you probably don't want to use a static values for your variables. You can calculate your variables (and any other options) from the props passed down to the component the HOC is wrapping. See this section in the docs.
const options = ({someProp}) => ({
variables: { query: someProp, type: 'REPOSITORY' },
notifyOnNetworkStatusChange: true,
})
export default graphql(searchRepos, {options})(ReposList)

How do I handle deletes in Vue Apollo with deeply nested queries?

I have a query that returns multiple nested objects to render a screen full of information. I want to delete one of the deeply-nested objects and update the screen optimistically (i.e. without running the complete query).
To explain the query and UI, I'll use a Trello board -like query as an example:
query everything {
getBoard(id: "foo") {
name
cardLists {
edges {
node {
id
name
cards {
edges {
node {
id
name
}
}
}
}
}
}
}
}
The result of this query is used to build a UI like this: https://d3uepj124s5rcx.cloudfront.net/items/170a0V1j0u0S2p1l290I/Board.png
I'm building the app using:
VueJS
Vue Apollo
Scaphold.io as my GraphQL store
When I want to delete one of the cards, I call:
deleteCard: function () {
this.$apollo.mutate({
mutation: gql`
mutation deleteCard($card: DeleteCardInput!) {
deleteCard(input: $card) {
changedCard {
id
}
}
}
`,
variables: {
'card': {
id: this.id
}
},
})
.then(data => {
// Success
})
}
The mutation is successfully deleting the card, but I want to update the UI immediately based on the mutation. The simple option for doing this is to call refetchQueries: ['everything'] — but this is an expensive query and too slow for quick UI updates.
What I want to do is update the UI optimistically, but the example mutations for Vue Apollo don't address either deletes or the deeply-nested scenario.
What is the right solution / best-practices for deleting an item from a deeply-nested query, and optimistically updating the UI?
If you look at the documentation for the optimistic Response of apollo you see that the optimistic response "fakes" the mutation result.
So it will handle the updateQueries function with the optimistic values. In your case this means that if you add the optimisticResponse property it should alter the query inside the apollo store with the optimistic values:
Could look like this:
this.$apollo.mutate({
mutation: gql `
mutation deleteCard($card: DeleteCardInput!) {
deleteCard(input: $card) {
changedCard {
id
}
}
}
`,
variables: {
'card': {
id: this.id
}
},
updateQueries: {
// .... update Board
},
optimisticResponse: {
__typename: 'Mutation',
deleteCard: {
__typename: 'Card', // graphQL type of the card
id: this.id,
},
},
})

where is the best place to adjust how ember-data talks to sails

I am trying to implement an ember-cli (ember-data) to sails.js connection. I have installed ember-data-sails and have a simple model and am using the socketAdapter...
adapters/application.js
import SailsSocketAdapter from 'ember-data-sails/adapters/sails-socket';
export default SailsSocketAdapter.extend({
useCSRF: true,
coalesceFindRequests: true,
namespace: 'api/v1',
//defaultSerializer: '-rest',
});
Example Model:
RequestType.js
module.exports = {
attributes: {
name : { type: 'string' },
desc : { type: 'string' },
requestSubType : { type: 'hasMany' }
}
};
So far it is mostly working, I can get a list of RequestTypes ok. However if i try and nest a route to display/create the hasMany relationship, then i have an issue.
request-sub-types/new/routes.js
This file, requests the Parent model requestType first, and then creates a new record, inserting the Parent in the belongsTo attribute.
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
var requestType = this.modelFor('requestTypes/view');
return requestType.get('requestSubTypes').createRecord({
requestType: requestType
});
}
});
request-types/view/route.js
This file is the model I think that is being requested above. The issue here is the params object that get fed through is:
{ requestType_id: 6 }
but if this is passed to the store.query call then nothing gets returned. If however I change the params object to:
{ id: 6 }
then I do see a record come back from the sails api.
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
console.log('RT: View Route MODEL', params);
var query = {};
if(params.requestType_id) {
query.id= params.requestType_id;
}
return this.store.query('requestType', query);
}
});
So My question is what do i need to edit to make the Primary Key definitions match up. so that Ember-data and Sails can talk to each other correctly? Is it in a serializer somewhere, the Sails API or in each model call?
thanks... and sorry if the above doesn't make any sense! :)