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.
Related
I get issues when using #apollo/client: 3.5.10, aws-appsync:4.1.5.
There is my config
import { ApolloClient, ApolloLink, createHttpLink, InMemoryCache } from '#apollo/client';
import { createAuthLink } from 'aws-appsync-auth-link';
import { createSubscriptionHandshakeLink } from 'aws-appsync-subscription-link';
// Config
import { AWS_APPSYNC } from '../config';
const { graphqlEndpoint, region, apiKey } = AWS_APPSYNC;
const auth = {
type: AWS_APPSYNC.authenticationType,
apiKey: apiKey,
};
const httpLink = createHttpLink({ uri: graphqlEndpoint });
const link = ApolloLink.from([
// #ts-ignore
createAuthLink({ graphqlEndpoint, region, auth }),
// #ts-ignore
createSubscriptionHandshakeLink({ graphqlEndpoint, region, auth }, httpLink),
]);
export const client = new ApolloClient({
link,
cache: new InMemoryCache(),
});
and I am using
const {
data: subscription_message_data,
loading: subscription_message_loading,
error: subscription_message_error
} = useSubscription(
SUBSCRIPTION_NEW_MESSAGE, {
variables: { conversationId: conversationId }
});
But I got an error form useSubscription is: "Subscribe only available for AWS AppSync endpoint"
Does anyone have experience with this issue?
Had the exact same issue, and was baffled by this error message.
At the end the issue turned out to be the incorrect property name for the url used in the createAuthLink and createSubscriptionHandshakeLink input objects.
This was the fix that worked for me:
const link = ApolloLink.from([
createAuthLink({ url: graphQLEndpoint, region, auth }),
createSubscriptionHandshakeLink({ url: graphQLEndpoint, region, auth }, httpLink),
]);
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'
});
I want to refractor the current code to use the ApolloClient constructor to cache information and only query for new data if the requesting data isn't already cache –
Currently I'm using fetchPolicy to cache users Id but from what I've seen there is a way to cache using apollo.
async fetchRecipients(userIds: string[]) {
//TODO: How to refactor and use apollo cache?
const result = await client?.query({
query: MembersBySFIDs,
variables: {sfids: userIds},
fetchPolicy: 'cache-first',
});
if (result?.data?.membersBySFIDs) {
await dispatch.newChatMessage.setRecipients(result.data.membersBySFIDs);
} else {
throw new Error('Members not found');
}
}
Here is what I tried so far,
I don't think I am using it correctly, any help is appreciated:
import { InMemoryCache, ApolloClient } from '#apollo/client';
const result = new ApolloClient({
cache: new InMemoryCache()
});
async fetchRecipients(userIds: string[]) {
const result = await client?.query({
query: MembersBySFIDs,
variables: {sfids: userIds},
fetchPolicy: 'cache-and-network'
});
if (result?.data?.membersBySFIDs) {
await dispatch.newChatMessage.setRecipients(result.data.membersBySFIDs);
} else {
throw new Error('Members not found');
}
}
You can set the default behavior of client.query by configuring ApolloClient's the default fetchPolicy to cache-first in defaultOptions as shown here.
Something like this:
import { InMemoryCache, ApolloClient } from '#apollo/client';
const client = new ApolloClient({
cache: new InMemoryCache(),
defaultOptions: {
query: {
fetchPolicy: "cache-first"
}
}
});
async fetchRecipients(userIds: string[]) {
const result = await client?.query({
query: MembersBySFIDs,
variables: {sfids: userIds},
});
// do whatever you want with the result here
}
Hope you found this helpful.
So I am following the graphql + apollo tutorial at https://www.howtographql.com/react-apollo/6-more-mutations-and-updating-the-store/ and I have a bunch of "Link" components that when upvoted should show the new number of votes. So far it does not re-render when upvoted and I have to refresh the page.
I try to do this by calling store.writeQuery() with the updated data. I checked and the data object I passed in is indeed different from the old:
Here's my initial set-up:
import { ApolloProvider } from "react-apollo";
import { ApolloClient } from "apollo-client";
import { createHttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory"
import { setContext } from "apollo-link-context";
import App from './components/App';
import {AUTH_TOKEN} from "./constants.js";
const authLink = setContext((_, { headers }) =>
{
const token = localStorage.getItem(AUTH_TOKEN);
return {
headers: {
...headers,
authorization: (token ? "Bearer " + token : ""),
}
}
});
const httpLink = createHttpLink({
uri: "http://localhost:4000"
});
const apolloClient = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
})
Here's the mutation component:
<Mutation
mutation={VOTE_MUTATION}
variables={{id: this.props.link.id}}
update={(store, { data: { upVote } }) =>
{
const data = store.readQuery({ query: FEED_QUERY });
const votedLink = data.feed.links.find(link => link.id === this.props.link.id);
votedLink.votes = upVote.link.votes;
console.log(data);
store.writeQuery({
query: FEED_QUERY,
data,
});
} }
>
{
mutationCallback =>
(<div className="ml1 gray f11" style={{cursor: "pointer"}} onClick={mutationCallback}>
▲
</div>)
}
</Mutation>
Here's the query that fetches all the links:
const FEED_QUERY = gql`
{
feed
{
links
{
id url description createdAt
createdBy{
name email
}
votes{
id
}
}
}
}
`;
And I call store.readQuery() and store.writeQuery() with the expectation that the upvoted link will rerender to show the new number of votes. I also log the passed in data object to ensure it has been updated, and it has. But no rerendering. What could be wrong?
add to the apolloClient dataIdFromObject for more info here the: documentation
const apolloClient = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
dataIdFromObject: o => o.id
})
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)