For most of us whenever we start using apollo client and follow the documentation we almost always end up using the React hooks useQuery or useMutation to handle our network calls to an apollo graphql server.
But what about when we don’t want to use a hook to handle these requests?
In this post we will be covering just that. We will look into how to use apollo client without hooks such as useQuery or useMutation.
When would you want to use Apollo client without hooks?
Generally speaking using React hooks makes life easier when it comes to developing React applications.
It makes more sense because it ties into re-rendering and side effects nicely and keeps the flow of your application and the way your application works in a way we can understand.
You render your component, if something changes with an action or event, you then re-render the component with the new data to update the UI.
Apollo client React hooks work well with this because you run them using re-renders and side effects and you can control them with the skip
property. To find out more about how to use apollo client hooks, checkout my post on multiple apollo client hooks in sequence.
However there are times when they don’t make so much sense and don’t make things easier.
If you make use of redux or a middleware such as redux saga to manage your side effects and store, then you are probably not going to want to be using hooks with apollo client because it will add complexity to your application if you then need to add in that data back into your store which would potentially mess up the flow of your application.
That is one example, another might be if you find yourself using useLazyQuery
a lot. This means you want to trigger this query after an action or event has taken place, this could be a user clicking a button or a side effect being triggered.
How to use apollo client without hooks
So, how do we use apollo client without hooks? It is actually very similar to how you would use apollo client with hooks in the sense of how you create a query.
The only difference really is how you run the query.
All we need to do is to call .query
on our apollo client instance.
apolloClient.query({ query: MyQuery, variables: { ... }, ...otherOptions })
To set this up so we can work with both hooks and without hooks, we need to make sure we can call our apollo client from anywhere in our codebase.
This is pretty straightforward to do, we just need to export the client when we create the provider at the root of our application like so:
import React from "react"
import { ApolloClient, InMemoryCache, ApolloProvider } from "@apollo/client"
import MyApp from "./MyApp"
export const apolloClient = new ApolloClient({
uri: "https://atomizedobjects.com/i-am-an-example",
cache: new InMemoryCache(),
})
export default function Root() {
return (
<ApolloProvider client={apolloClient}>
<MyApp />
</ApolloProvider>
)
}
Then to look at how we can use apollo query without hooks, we will start with an example of how we can use the useQuery hook, and then how we can do the same thing without the useQuery hook.
With a useQuery hook:
const { data, loading, error } = useQuery(MyExampleQuery, {
variables: {
name,
},
skip: !name,
})
And without the useQuery hook:
const response = await apolloClient.query({
query: MyExampleQuery,
variables: {
name,
},
})
Both of these methods would need to be used in different circumstances, using the useQuery hook is better suited when you need to use it directly in a component and want to display loading and error states to the user.
For example:
import { useState }, React from "react"
export default function ExampleComponent() {
const [name, setName] = useState("")
const [confirmation, setConfirmation] = useState(false)
const { data, loading, error } = useQuery(MyExampleQuery, {
variables: {
name,
},
skip: !name,
})
const handleClick = () => setConfirmation(true)
return (
<>
<p>Hello, {name}</p>
<input type="text" onChange={e => setName(e.target.value)} />
<button onClick={handleClick}>
Please confirm if your name is {name}
</button>
{loading && <p>Loading</p>}
{data && !loading && !error && (
<>
<p>Celebrities with the name name as you:</p>
{data.findCelebritiesWithTheSameName.names.map(({ name, id }) => (
<p key={id}>{name}</p>
))}
</>
)}
</>
)
}
And apolloClient.query would be better suited if you needed an async request that might not directly affect the UI or something you don’t need to display to the user that is loading.
Here is a quick example of how you might use apolloClient.query on a user action rather than relying upon a useEffect side effect:
export default function Login() {
const [username, setUsername] = useState("")
const [status, setStatus] = useState("NOT_LOADING")
const handleLogin = async () => {
setStatus("LOADING")
if (!username) return
const response = await apolloClient.query({
query: LoginQuery,
variables: {
username,
},
})
if (response.data.success) {
storeToken(response.data.token)
navigate("home")
}
}
return (
<>
{status === "LOADING" ? <p>Loading</p> : null}
<input type="text" onChange={e => setUsername(e.target.value)} />
<button onClick={handelLogin}>Login</button>
</>
)
}
Apollo client vs Apollo react hooks
If you can avoid using apollo client without hooks in an application where you are using hooks it will probably be best.
Whilst it might be useful in some circumstances, it definitely adds confusion because of how the two ways require different patterns of thought about how the flow will work.
One is with re-renders and side effects and the other is with asynchronous JavaScript.
The main thing to remember is that whatever one you choose to use, it is best not to combine them both.
If you do opt in for apollo client without hooks, it would be best in combination with a middleware like redux saga.
And if you do use react hooks, it won’t be quite as easy to use with sagas and a redux store (although it is definitely still possible), meaning that if you are using a global store like redux, it would have to be much simpler and you would need to rely more on local states.
In my opinion, using apollo client with hooks is the better option here because it forces the code to be more reliant on local states rather than forcing us to use a global store for everything.
Global stores are useful in some places and for some data, but one of the worst things that can happen is when global stores are implemented they can have a tendency to be used for more state storage than is needed which causes the global store to become polluted with data that doesn’t need to be there which makes it difficult to work with.
Summary
And there we have how to use apollo client without hooks.