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
})
Related
This is my apollo client code.
import {
ApolloClient,
ApolloLink,
createHttpLink,
InMemoryCache
} from "#apollo/client"
import { setContext } from "#apollo/client/link/context"
let uri = `${process.env.NEXT_PUBLIC_API_URL}/wp/graphql`
const httpLink = createHttpLink({ uri, credentials: "include" })
const authLink = setContext((_, { headers }) => {
headers
})
let client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
defaultOptions: {
query: {
fetchPolicy: "no-cache"
}
}
})
export { client }
This is my page routing in next where i am trying a simple GraphQL Query.
export default function PrivatePath(props: any) {
console.log("props:", props)
const { data, loading, error } = useQuery(gql`
query MyQuery {
page(id: "/min-sida", idType: URI) {
status
title
}
}
`)
console.log("data:", data)
return (
<ApolloProvider client={client}>
<AuthProvider>
<div></div>
</AuthProvider>
</ApolloProvider>
)
}
export async function getServerSideProps(context: any) {
const slugs = context.query.path[0]
const query = gql`
query MyQuery {
page(id: "/min-sida", idType: URI) {
status
title
}
}
`
const data = await client.query({ query: query })
return {
props: data
}
}
What is interesting to me is that the hook useQuery, does what is expected and when logged in delivers the page title and status.
The client.query, however, does not ever return page title and status, even when logged in it simple returns page: { null }.
My initial thought was that it was because getStatiProps in next won't be able to get the data no matter what, but it seems getServerSideProps is unable to do so as well?
Does anyone have a decent idea for solving this?
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'
});
Decided to switch from normal React to NextJS after watching various videos and reading articles. I'm currently trying to implement Apollo Client but am getting this (title) error and was hoping to get some help. The way my withData is currently set is
import { ApolloClient } from 'apollo-client';
import { ApolloLink } from 'apollo-link';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { hasSubscription } from '#jumpn/utils-graphql';
import * as AbsintheSocket from '#absinthe/socket';
import withApollo from 'next-with-apollo';
import { createAbsintheSocketLink } from '#absinthe/socket-apollo-link';
import { Socket as PhoenixSocket } from 'phoenix';
let apolloClient = null;
const HTTP_ENDPOINT = 'http://localhost:4000/api/v1/graphiql';
const WS_ENDPOINT = 'ws://localhost:4000/api/v1/socket';
const httpLink = createHttpLink({
url: HTTP_ENDPOINT
});
const socketLink = createAbsintheSocketLink(AbsintheSocket.create(new PhoenixSocket(WS_ENDPOINT)));
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem('auth-item');
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : ''
}
};
});
const link = new ApolloLink.split(
(operation) => hasSubscription(operation.query),
socketLink,
authLink.concat(httpLink)
);
const create = (initialState) => {
return new ApolloClient({
link: link,
cache: new InMemoryCache().restore(initialState || {})
});
};
const initApollo = (initialState) => {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === 'undefined') {
return create(initialState);
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = create(initialState);
}
return apolloClient;
};
export default withApollo(initApollo);
All help is appreciated to understand what I did wrong and what is a better approach should there be one.
The issue is because nextJs will run the code above in the server and WebSocket is a property that exists only in the browser, to get this fixed you can do:
const socketLink =
process.browser &&
createAbsintheSocketLink(
AbsintheSocket.create(
new PhoenixSocket(WS_URI)
)
)
By checking for process.browser you make sure that this function is executed only on client-side.
I would like to set up a graphql client with React for both uploading file and handle subscriptions from a graphql server.
The file upload and the other queries work well. The problem is with subscriptions. I get in the browser console the following error:
WebSocket connection to 'ws://localhost:3001/subscriptions' failed: Connection closed before receiving a handshake response
I have used apollo-upload-client for file upload and apollo-link-ws for subscriptions.
I can see that subscriptions-transport-ws suggests using createNetworkInterface and addGraphQLSubscriptions but this approach is not compatible with apollo-upload-client that only supports createUploadLink.
I'm stuck. Please help.
I setup my client like this:
import React from 'react';
import ApolloClient from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
import { createUploadLink } from 'apollo-upload-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink, Observable, split } from 'apollo-link';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
const cache = new InMemoryCache();
const request = async (operation) => {
const token = localStorage.getItem('token');
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : '',
},
});
};
const httpLink = createUploadLink({ uri: 'http://localhost:3001/graphql' });
// Create a WebSocket link:
const wsLink = new WebSocketLink({
uri: 'ws://localhost:3001/subscriptions',
options: {
reconnect: true
},
});
// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
const link = split(
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httpLink,
);
const requestLink = new ApolloLink((operation, forward) =>
new Observable((observer) => {
let handle;
Promise.resolve(operation)
.then(oper => request(oper))
.then(() => {
handle = forward(operation).subscribe({
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer),
});
})
.catch(observer.error.bind(observer));
return () => {
if (handle) handle.unsubscribe();
};
}));
const apolloClient = new ApolloClient({
link: ApolloLink.from([
requestLink,
link,
]),
cache,
});
export const withApolloClient = App => (
<ApolloProvider client={apolloClient}>
<App client={apolloClient} />
</ApolloProvider>
);
export default apolloClient;
I am using a similar config but instead of importing WebSocketLink from apollo-link-ws I imported it from #apollo/client.
With that setup i had both the subscription and upload working.
import { WebSocketLink } from "#apollo/client/link/ws";
I would suggest to use graphql-server-express like this
So I'm attempting to stitch multiple remote GraphCMS endpoints together on the clientside of a Next.js app, and after trying/combining about every example on the face of the internet, I've gotten it to a place that's worth asking about. My error:
TypeError: this.getClient(...).watchQuery is not a function at GraphQL.createQuery
github repo here, where you can see this initApollo.js in context:
import { ApolloClient } from 'apollo-client'
import {
makeRemoteExecutableSchema,
mergeSchemas,
introspectSchema
} from 'graphql-tools'
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import fetch from 'node-fetch'
import { Observable, ApolloLink } from 'apollo-link'
import { graphql, print } from 'graphql'
import { createApolloFetch } from 'apollo-fetch'
let apolloClient = null
if (!process.browser) {
global.fetch = fetch
}
const PRIMARY_API = 'https://api.graphcms.com/simple/v1/cjfipt3m23x9i0190pgetwf8c'
const SECONDARY_API = 'https://api.graphcms.com/simple/v1/cjfipwwve7vl901427mf2vkog'
const ALL_ENDPOINTS = [PRIMARY_API, SECONDARY_API]
async function createClient (initialState) {
const AllLinks = ALL_ENDPOINTS.map(endpoint => {
return new HttpLink({
uri: endpoint,
fetch
})
})
const allSchemas = []
for (let link of AllLinks) {
try {
allSchemas.push(
makeRemoteExecutableSchema({
schema: await introspectSchema(link),
link
})
)
} catch (e) {
console.log(e)
}
}
const mergedSchema = mergeSchemas({
schemas: allSchemas
})
const mergedLink = operation => {
return new Observable(observer => {
const { query, variables, operationName } = operation
graphql(mergedSchema, print(query), {}, {}, variables, operationName)
.then(result => {
observer.next(result)
observer.complete()
})
.catch(e => observer.error(e))
})
}
return new ApolloClient({
connectToDevTools: process.browser,
ssrMode: !process.browser,
link: mergedLink,
cache: new InMemoryCache().restore(initialState || {})
})
}
export default function initApollo (initialState) {
if (!process.browser) {
return createClient(initialState)
}
if (!apolloClient) {
apolloClient = createClient(initialState)
}
console.log('\x1b[37m%s\x1b[0m', apolloClient)
return apolloClient
}
I'm getting useful data all the way up into the .then() inside the Observable, where I can log the result
This is a shot in the dark, but initApollo isn't async so it returns a promise (not an ApolloClient object) which is then being passed into client prop of the ApolloProvider. watchQuery doesn't exist as a function on the Promise type, hence the error.
I think if you make initApollo async and then await those calls or find a way to make client creation synchronous, you should be able to address this issue.