Update ApolloClient headers after it was initialised - reactjs

My app is wrapped with <Apollo /> component that essentially initialises the client.
const client = new ApolloClient({
link: new HttpLink({
// ...
}),
cache: new InMemoryCache({
// ..
}),
});
Further down the road users can make certain action that requires me to set few new headers to apollo client that were not there before. I initially thought to use react context for this to pass set new headers and consume them inside <Apollo /> but am not sure if this is the right way to go about it.
After looking through the docs, it seems that apollo headers can be only set when it is initialised?

Rather than passing the headers directly to your Apollo client instance, you normally want to utilize apollo-link-context. You can store the actual header values in memory, LocalStorage or whatever makes sense for your app. Then use the link to inject them into each request before it's sent:
const headerLink = setContext((request, previousContext) => ({
headers: {
// Make sure you include any existing headers!
...previousContext.headers,
authorization: localStorage.getItem('authHeader')
},
}));
const client = new ApolloClient({
link: headerLink.concat(httpLink),
cache: new InMemoryCache()
});
setContext can be asynchronous. The function you pass it should return either an object with whatever context fields you want to change, or a Promise that will resolve to one:
const headerLink = setContext(async (request, previousContext) => {
const authorization = await someAsyncCall()
return {
headers: {
...previousContext.headers,
authorization,
},
}
});
You can check out the docs for additional examples.

To expand on Daniel Rearden's answer, if you want to add headers just for a specific query/mutation and not all of the subsequent queries:
Initialise Apollo:
const httpLink = createHttpLink({
uri: '/graphql',
});
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = localStorage.getItem('token');
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
And then simply add the context to the desired query/mutation itself:
const {loading, data, error} = useQuery(QUERY_DEF, {
context: {
headers: {
"HeaderKey": "HeaderValue"
}
}
});

Daniel Rearden is right. However, with Apollo 3 there're some minor changes that I've found not well systematized in the docs yet. So maybe it will help as well.
import React from 'react';
import { ApolloClient, InMemoryCache, HttpLink } from '#apollo/client';
import { setContext } from '#apollo/client/link/context';
function App() {
const link = new HttpLink({ uri: process.env.REACT_APP_GRAPHQL_URI });
const setAuthorizationLink = setContext((request, previousContext) => ({
headers: {
...previousContext.headers,
authorization: `Bearer ${ localStorage.getItem('auth_token') }`
}
}));
const client = new ApolloClient({
link: setAuthorizationLink.concat(link),
cache: new InMemoryCache()
});
return (
<ApolloProvider client={client}>
...
</ApolloProvider>
);
}
export default App;
Migrating to Apollo Client 3.0 (docs)

Related

Document is undefined when using apollo query with next.js getServerSideProps

I am trying to use getServerSideProps to fetch a query every time this component is rendered using apollo and next.js
export async function getServerSideProps(context) {
const { data } = await client.query({
query: GET_AUTHED_USER
})
return {
props: { user: data.getAuthedUser },
}
}
const Profile = ({ user }) => {
const router = useRouter();
// const [state, setState] = useState(JSON.parse(router.query.currentUser));
const [state, setState] = useState(user);
console.log(state)
...
APOLLO CONFIG
import { ApolloClient, InMemoryCache, ApolloLink } from '#apollo/client';
import { getCookie } from './utils/functions';
import { setContext } from '#apollo/client/link/context';
import { createUploadLink } from 'apollo-upload-client';
const authLink = setContext((_, { headers }) => {
// get the authentication token from storage if it exists
const token = getCookie('JWT');
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? token : '',
}
}
});
const httpLink = createUploadLink({
uri: 'http://localhost:5000/graphql',
});
const client = new ApolloClient({
uri: 'http://localhost:5000/graphql',
cache: new InMemoryCache(),
link: ApolloLink.from([authLink, httpLink])
});
export default client;
currently, when visiting the /profile page I receive the error:
Server Error
Error: document is not defined
Does anyone have any insight on how to resolve this or suggest a valid work around?
appolo/client is a client side library. getServersideProps execute in server side. document doesn't exist in server side. It will only work within client side.
Workaround 1:
You can use swr. swr is for client side data fetch. so you don't need getServerSideProps.
Workaround 2:
You can use apollo-server. Then you can call apollo-server function in getServerSideProps as apollo-server is for server side calling.

How to access the request context from the setContext function from graphql apollo-client in SSR from nextJS?

So I'm trying to do a custom header for the requests to my graphql backend from my nextJS frontend. The code of the graphql client is as follows:
import { ApolloClient, ApolloLink, InMemoryCache, createHttpLink } from '#apollo/client'
import { setContext } from '#apollo/client/link/context'
import getConfig from 'next/config'
import nookies, { parseCookies } from 'nookies'
const { publicRuntimeConfig } = getConfig()
const httpLink = createHttpLink({
uri: publicRuntimeConfig.uri,
credentials: 'same-origin'
})
const authLink = setContext((_, { headers }) => {
let token = ''
if (typeof window !== 'undefined') {
const { 'toDo-token': newToken } = parseCookies()
token = newToken
} else {
// error is right here. I cant find a way to access the ctx
const { 'toDo-token': newToken } = nookies.get(ctx)
token = newToken
}
return {
headers: {
...headers,
Authorization: token ?? undefined
}
}
})
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
ssrMode: typeof window === 'undefined'
})
export default client
On the client side it works just fine. However, since I cant access client cookies on the server side, I'm trying to use the get function from nookies to grab the auth cookies and pass to the headers with the setContext function. The problem I'm having is that I cant find a way to grab the app context from next so I can look at the request info and grab the ctx from there so I can see the cookies.
I do something similar in my _app.tsx in the following way:
MyApp.getInitialProps = async ({ ctx }: AppContext) => {
const { locale } = ctx
const { 'toDo-token': token } = nookies.get(ctx)
let user = null
try {
user = await useUser()
} catch {
user = null
}
return {
locale: locale === 'default' ? 'en' : locale as LocaleEnum,
token: token ?? null,
user
}
}
Does anyone know how I can access the request context in the graphql setContext function?

Apollo with multiple GraphQL endpoints

I need to use multiple GraphQL schemas, I currently have installed
expo: sdk 42 "#apollo/client": "^3.4.11", "apollo-link": "^1.2.14", "graphql": "^15.5.3",
As long as I use a single schema everything works fine: Declaration of a single schema
App.js
import { ApolloProvider, ApolloClient, createHttpLink, InMemoryCache } from '#apollo/client';
import { setContext } from '#apollo/client/link/context';
import { ApolloLink } from 'apollo-link';
const httpLink = createHttpLink({
uri: `${serverBaseUrl}/client/graphql`,
});
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
/**#all headers*/
}
}
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
credentials: 'include'
});
<ApolloProvider client={client}>
/**#all*/
Query Example.jsx
import { gql, useQuery } from "#apollo/client";
const E_QUERY = gql`
query{
example(sortBy: { field: "updatedAt", order: DESC }){
_id
}
}
`;
const { loading, data } = useQuery(E_QUERY , {
fetchPolicy: "network-only",
});
All good for now
but when I add multiple schemas it doesn't generate any error, it just keeps loading alone
App.js
const client = new ApolloClient({
link: ApolloLink.split(
operation => operation.getContext().clientName === "adminGQL",
authLink.concat(httpLinkAdmin),
operation => operation.getContext().clientName === "agentGQL",
authLink.concat(httpLinkAgent),
operation => operation.getContext().clientName === "client",
authLink.concat(httpLinkAgent),
),
cache: new InMemoryCache(),
credentials: 'include'
});
Example.jsx
const { loading, data } = useQuery(EXAMPLE_QUERY , {
fetchPolicy: "network-only",
context: { clientName: 'client' }
});
thank you
The first error detected is that ApolloLink.split can only perform a comparison and it only has two possible cases (true or false) so it cannot be added more than two urls, so how will it be to add more than 2 urls?
ApolloLink.d.ts
static split(test: (op: Operation) => boolean, left: ApolloLink | RequestHandler, right?: ApolloLink | RequestHandler): ApolloLink;
Starting from this point we have the solution as follows
const client = new ApolloClient({
link: ApolloLink.split(
operation => operation.getContext().clientName === "adminGQL",
authLink.concat(httpLinkAdmin),
authLink.concat(ApolloLink.split(
operation => operation.getContext().clientName === "agentGQL",
httpLinkAgent,
httpLink
))
),
cache: new InMemoryCache(),
credentials: 'include'
});
It may not be the best solution, but it was the one I was able to build. So new suggestions or solutions may be welcome.
This might not be the best approach, but if all your links use the same config, headers etc, you can pass a function on the URI:
const url = (endpoint) => {
switch(endpoint) {
case 'endpoint1':
return 'https://example.io/v1/graphql'
default:
return 'https://example.io/graphql'
}
const link = new HttpLink({
uri: operation => {
return url(operation.getContext().endpoint);
}
});
Also, it seems like your authLink is mandatory, so you could've done:
/** your other imports */
import { from, ApolloLink } from 'apollo-link';
const client = new ApolloClient({
link: from(
authLink,
httpLink
),
cache: new InMemoryCache(),
credentials: 'include'
});

Apollo useSubscription hook not emitting new data

I have created subscription whenever a new post is added. The subscription works fine on the graphiql interface.
Here is my react code to use useSubscription hook
export const SUSCRIBE_POSTS = gql`
{
subscription
posts {
newPost {
body
id
createdAt
}
}
}
`;
const {
loading: suscriptionLoading,
error: subscriptionError,
data: subscriptionData,
} = useSubscription(SUSCRIBE_POSTS);
when I try to console log subscriptionData I get nothing. when I add a post it is saved in database correctly, the useQuery hook for getting posts also work fine, but when I add a new post I don't see the subscription data. I can't see anything wrong in the console as well. When I log suscriptionLoading, Ido get true at the start. I am not sure how to debug this.
The client setup is done correctly according to the docs https://www.apollographql.com/docs/react/data/subscriptions/
and here is the code
const httpLink = createHttpLink({
uri: "http://localhost:4000/",
});
const wsLink = new WebSocketLink({
uri: `ws://localhost:4000/graphql`,
options: {
reconnect: true,
},
});
const authLink = setContext(() => {
const token = localStorage.getItem("jwtToken");
return {
headers: {
Authorization: token ? `Bearer ${token}` : "",
},
};
});
const link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
authLink.concat(httpLink)
);
const client = new ApolloClient({
link: link,
cache: new InMemoryCache(),
});
export default (
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
export const SUSCRIBE_POSTS = gql`
subscription
posts {
newPost {
body
id
createdAt
}
}
`;
Can you try to remove the most-outside brackets of subscription gql?

How to set variable for each request for apollo-client?

I'm using apollo-client and want to send some variable for each request. Let's call it locale. I don't want to pass it to every Query component, it's not DRY pattern.
This can be achieved by using middleware:
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { ApolloLink, concat } from 'apollo-link';
const httpLink = new HttpLink({ uri: '/graphql' });
const authMiddleware = new ApolloLink((operation, forward) => {
operation.setContext({
headers: {
locale: localStorage.getItem('locale') || 'en-US',
}
});
return forward(operation);
})
const client = new ApolloClient({
link: concat(authMiddleware, httpLink),
});
Checkout the docs: https://www.apollographql.com/docs/react/advanced/network-layer/
You can add a middleware link, which will be modifying your operation on fly
class MiddlewareLink extends ApolloLink {
request (operation: Operation, forward: NextLink) {
operation.variables['prop1'] = 'value1'
return forward(operation)
}
}
...
const client = new ApolloClient({
link: ApolloLink.from([
new MiddlewareLink(),
httpLink
])
})
This way looks like the most correct right now, maybe later apollo-link-http will be extended and will be able to accept some data from the context and pass to variables.

Resources