What does imperative mean in Apollo GraphQL? - graphql

I'm new to Apollo GraphQL. When reading its docs, it mentioned the word imperative several times. But I can't really find an explanation online that clarify what exactly "imperative" does for Apollo GraphQL. So far, the easiest def. I got is imperative programming is like how you do something, and declarative programming is more like what you do.
Here is an example of mentioning imperative for Apollo GraphQL:
GraphQL’s mutations are triggered imperatively, but that’s only
because a HOC or render prop grants access to the function which
executes the mutation (e.g. on a button click). With Apollo, the
mutations and queries become declarative over imperative.
Could someone provide a solid example to help me understand what imperative mean in Apollo GraphQL?

It's actually a simpler paragraph than you realise, in this sentence imperative is explaining how one might choose to write out a graphql react component as:
import React from 'react';
import { Query } from 'react-apollo';
const Profile = () => (
<Query query={}>
{() => <div>My Profile</div>}
</Query>
);
export default Profile;
While this component doesn't actually make a query because no query is provided we are imperatively programming it. Imperative in the sense we are providing the query into the component and the HOC triggers the query or mutation and passes it into the props. In this example we can step through the code from creating the HOC and adding a query to calling it via props on the component. Though it's interesting to note that a GraphQL query on it's own is declarative in nature.
Declarative is best characterised as describing what we would like and in the apollo client the best way to visualize this is through a functional component.
const LAST_LAUNCH = gql`
query lastLaunch {
launch {
id
timestamp
}
}
`;
export function LastLaunch() {
const { loading, data } = useQuery(LAST_LAUNCH);
return (
<div>
<h1>Last Launch</h1>
{loading ? <p>Loading</p> : <p>Timestamp: {data.launch.timestamp}</p>}
</div>
);
}
In this example you can see we are essentially executing this query / mutation using
const { loading, data } = useQuery(LAST_LAUNCH);
This line of code describes using the query written above what we would like to be returned making it a declarative statement.
In simplistic terms the HOC component in example one has several steps that you can follow before you could use your data. In the second example we are simply describing what we would like in a single statement and receiving back the data.
Finally it's also important to mention that in programming we generally have a mixture of imperative and declarative statements / blocks of code throughout our application and it's perfectly normal.

Related

How to separate logic when updating Apollo cache that used as global store?

Using Apollo cache as global store - for remote and local data, is very convenient.
However, while I've never used redux, I think that the most important thing about it is implementing flux: an event driven architecture in the front-end that separate logic and ensure separation of concerns.
I don't know how to implement that with Apollo. The doc says
When mutation modifies multiple entities, or if it creates or deletes entities, the Apollo Client cache is not automatically updated to reflect the result of the mutation. To resolve this, your call to useMutation can include an update function.
Adding an update function in one part of the application that handle all cache updates; by updating queries and/or fragments for the all other parts of the application, is exactly what we want to avoid in Flux / Event driven architecture.
To illustrate this, let me give a single simple example. Here, we have (at least 3 linked components)
1. InboxCount
Component that show the number of Inbox items in SideNav
query getInboxCount {
inbox {
id
count
}
}
2. Inbox list items
Component that displays items in Inbox page
query getInbox {
inbox {
id
items {
...ItemPreview
...ItemDetail
}
}
}
Both of those components read data from those GQL queries from auto generated hooks ie. const { data, loading } = useGetInboxItemsQuery()
3. AddItem
Component that creates a new item. Because it creates a new entity I need to manually update cache. So I am forced to write
(pseudo-code)
const [addItem, { loading }] = useCreateItemMutation({
update(cache, { data }) {
const cachedData = cache.readQuery<GetInboxItemsQuery>({
query: GetInboxItemsDocument,
})
if (cachedData?.inbox) {
// 1. Update items list GetInboxItemsQuery
const newItems = cachedData.inbox.items.concat(data.items)
cache.writeQuery({
query: GetInboxItemsDocument,
data: {
inbox: {
id: 'me',
__typename: 'Inbox',
items: newItems,
},
},
})
// 2. Update another query wrapped into another reusable method, here
setInboxCount(cache, newItems.length)
}
},
})
Here, my AddItem component must be aware of my different other queries / fragments declared in my application 😭Moreover, as it's quite verbose, complexity is increasing very fast in update method. Especially when multiple list / queries should be updated like here
Does anyone have recommendations about implementing a more independent components? Am I wrong with how I created my queries?
The unfortunate truth about update is that it trades simplicity for performance. A truly "dumb" client would only receive data from the server and render it, never manipulating it. By instructing Apollo how to modify our cache after a mutation, we're inevitably duplicating the business logic that already exists on our server. The only way to avoid this is to either:
Have the mutation return a larger section of the graph. For example, if a user creates a post, instead of returning the created post, return the complete user object, including all of the user's posts.
Refetch the affected queries.
Of course, often neither approach is particularly desirable and we opt for injecting business logic into our client apps instead.
Separating this business logic could be as simple as keeping your update functions in a separate file and importing them as needed. This way, at least you can test the update logic separately. You may also prefer a more elegant solution like utilizing a Link. apollo-link-watched-mutation is a good example of a Link that lets you separate the update logic from your components. It also solves the issue of having to keep track of query variables in order to perform those updates.

Can Queries be used for data writing?

For my GraphQL app I'd like to save logs of certain resolved fields. Because the users can view these logs themselves, should that be considered apart of a mutation instead of a query?
Since it's not the application's focus I'd assume that using a mutation is overkill, but I'm not sure if there's some sort of side effects I'm going to run into by modeling it in such a way.
The other questions I've read didn't really answer this question, so sorry if this seems like a duplicate.
Conceptually Graphql Queries & Mutations do the same thing but however differ in the way the resolvers are executed.
For the following Queries:
{
user {
name
}
posts {
title
}
}
The GraphQL implementation has the freedom to execute the field entries in whatever order it deems optimal. see here.
For the following Mutations:
{
createUser(name: $String) {
id
}
addPost(title: $String) {
id
}
}
The GraphQL implementation would execute each Mutation sequentially. see here
Par from this, the Mutation keyword is just a bit of syntax to say "hey this is gonna edit or create something". I think here, in your case, its a better decision to perform a Query & store the event in your Audit log. Exposing the fact that the Query stores an audit log is an implementation-specific detail & clients shouldn't know about it.

React Apollo - multiple mutations

I'm using react-apollo#2.5.6
I have a component, when you click on it, it will based on "select" state and issue either an add or a remove operation.
Currently I'm doing this to have 2 mutations function injected to my component. Is that the correct way to do it? Am I able to just use one Mutation ( HOC ) instead of multiple ?
<Mutation mutation={ADD_STUFF}>
{(addStuff) => (
<Mutation mutation={REMOVE_STUFF}>
{(removeStuff) => {
And later in the wrapped component, I will do something like that
onClick={(e) => {
e.preventDefault()
const input = {
variables: {
userId: user.id,
stuffId: stuff.id,
},
}
// Based on selected state, I will call either add or remove
if (isSelected) {
removeStuff(input)
} else {
addStuff(input)
}
}}
Thanks
Everything is possible but usually costs time and money ;) ... in this case simplicity, readability, manageablility.
1st solution
Common mutation, f.e. named 'change' with changeType parameter.
Of course that requires API change - you need a new resolver.
2nd solution
Using graphql-tag you can construct any query from the string. Take an inspiration from this answer - with 'classic graphql HOC' pattern.
This solution doesn't require API change.
I think using two different Mutation components does not make sense. If I understand correctly, there can be two ways to solve your problem.
Using Apollo client client.mutate function to do manual mutation based on the state and set mutation and variables properties based on the new state. To access the client in current component, you need to pass along the client from parent component where it was created to child components where mutation is taking place.
Using single Mutation component inside render method of your component and setting mutation and variables attributes based on the state variable.
The approach that you are using is working as you said, but to me looks like you are delegating some logic to the UI that should be handled by the underlying service based on the isSelected input.
I think that you should create a single mutation for ADD_STUFF and REMOVE_STUFF, I would create the ADD_OR_REMOVE_STUFF mutation, and choose the add or remove behavior on the resolver.
Having one mutation is easier to maintain/expand/understand, if the logic requires something else besides add/remove, for example if you have to choose add/remove/update/verify/transform, would you nest 5 mutations?
In the previous case the single mutation could be named MULTI_HANDLE_STUFF, and only have that one mutation called from the UI.

Use Graphql variables to define fields

I am trying to do something effectively like this
`query GetAllUsers($fields: [String]) {
users {
...$fields
}
}`
Where my client (currently Apollo for react) then passes in an array of fields in the variables section. The goal is to be able to pass in an array for what fields I want back, and that be interpolated to the appropriate graphql query. This currently returns a GraphQL Syntax error at $fields (expects a { but sees $ ). Is this even possible? Am I approaching this the wrong way?
One other option I had considered was invoking a JavaScript function and passing that result to query(), where the function would do something like the following:
buildQuery(fields) {
return gql`
query {
users {
${fields}
}
}`
}
This however feels like an unecessary workaround.
Comments summary:
Non standard requirements requires workarounds ;)
You can use fragments (for predefined fieldsets) but they probably won't be freely granular (field level).
Variables are definitely not for query definition (but for variables used in query).
Daniel's suggestion: gql-query-builder
It seams that graphQL community is great and full of people working on all possible use cases ... it's enough to search for solutions or ask on SO ;)

graphql multiple mutations using prior mutation return results?

I understand that mutations are sequential, so it makes sense to me that if Mutation 1 creates an entity and returns an id, that Mutation 2 should have access to that id. However I don't see any examples online and can't seem to get it to work. I see that people say you need to handle this in the resolve function of your route but it seems like extra unnecessary code if I can get this in just the query.
For example I have the following where accounts belong to clients and hence need the clientId before being created. However this does not work...
mutation createClientAndAccount($account: AccountInput, $client: ClientInput){
createClient(client: $client){ clientId }
createAccount(account: $account, clientId: USE_CLIENT_ID_FROM_ABOVE) { ... }
}
I've also tried nesting mutations but didn't have much luck there either...
Is what i'm trying to do possible? Would the resolve function of createAccount have the return data from createClient?
This is not possible right now, though would be useful.
See this PR.
Maybe using a custom schema directive we could achieve that.
Schema stitching will be a better approach(though usually it is preferred in API Gateway for merging APIs from different services).
If this requirement is very rare in your application, simply creating a new API that can do both CreateClientAndAccount is enough.

Resources