Using `react-apollo-hooks` and `useSubscription` hook - reactjs

I'm building a simple todo app using React, Apollo and react-apollo-hooks for hooks support, but the useSubscription hook doesnt fire.
I know the actual backend stuff works, because I have a graphiql app set up, and whenever I save a todo, the todoCreated event shows up in graphiql. I also know that the websocket-setup is working properly, because the queries & mutations are going through the websocket. I'm using Elixir, Phoenix, Absinthe, by the way, for the backend stuff.
Here's the Todo-app component:
import React, { useState } from 'react';
import gql from 'graphql-tag';
import { useQuery, useMutation, useSubscription } from 'react-apollo-hooks';
import styles from 'styles.css';
const TODO_FRAGMENT = gql`
fragment TodoFields on Todo {
id
description
}
`;
const GET_TODOS = gql`
{
todos {
...TodoFields
}
}
${TODO_FRAGMENT}
`;
const SAVE_TODO = gql`
mutation createTodo($description: String!) {
createTodo(description: $description) {
...TodoFields
}
}
${TODO_FRAGMENT}
`;
const DELETE_TODO = gql`
mutation deleteTodo($id: ID!) {
deleteTodo(id: $id) {
id
}
}
`;
const NEW_TODO_SUBSCRIPTION = gql`
subscription {
todoCreated {
...TodoFields
}
}
${TODO_FRAGMENT}
`;
const Todos = () => {
const [inputValue, setInputValue] = useState('');
const { data, error, loading } = useQuery(GET_TODOS);
const saveTodo = useMutation(SAVE_TODO, {
update: (proxy, mutationResult) => {
proxy.writeQuery({
query: GET_TODOS,
data: { todos: data.todos.concat([mutationResult.data.createTodo]) },
});
},
});
const deleteTodo = useMutation(DELETE_TODO, {
update: (proxy, mutationResult) => {
const id = mutationResult.data.deleteTodo.id
proxy.writeQuery({
query: GET_TODOS,
data: { todos: data.todos.filter(item => item.id !== id) },
});
},
});
const subData = useSubscription(NEW_TODO_SUBSCRIPTION);
console.log(subData);
if (loading) {
return <div>Loading...</div>;
};
if (error) {
return <div>Error! {error.message}</div>;
};
return (
<>
<h1>Todos</h1>
{data.todos.map((item) => (
<div key={item.id} className={styles.item}>
<button onClick={() => {
deleteTodo({
variables: {
id: item.id,
},
});
}}>Delete</button>
{' '}
{item.description}
</div>
))}
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
type="text"
/>
<button onClick={() => {
saveTodo({
variables: {
description: inputValue,
},
});
setInputValue('');
}}>Save</button>
</>
);
};
export default Todos;
And here's the root component:
import React from 'react';
import { ApolloProvider } from 'react-apollo';
import { ApolloProvider as ApolloHooksProvider } from 'react-apollo-hooks';
import Todos from 'components/Todos';
import apolloClient from 'config/apolloClient';
const App = () => (
<ApolloHooksProvider client={apolloClient}>
<Todos />
</ApolloHooksProvider>
);
export default App;
Anyone have a clue on what I seem to be doing wrong?

Sorry, I figured it out, it was a silly mistake on my part. The problem seems to have been with my apolloClient setup:
import { split } from 'apollo-link';
import { getMainDefinition } from 'apollo-utilities';
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { onError } from 'apollo-link-error';
import { ApolloLink } from 'apollo-link';
import absintheSocketLink from 'config/absintheSocketLink';
const apolloClient = new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
),
);
if (networkError) console.log(`[Network error]: ${networkError}`);
}),
split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
new HttpLink({
uri: 'http://localhost:4000/api/graphql',
credentials: 'same-origin'
}),
absintheSocketLink,
),
]),
cache: new InMemoryCache()
});
export default apolloClient;
The error in the code above is the fact that the line
absintheSocketLink,
is in the wrong place. It should've been before the HttpLink.
Silly me.

I had the same issue my subscription was always sending null data and i had a silly mistake as well.

Related

The react apollo useSubscription hook doesn't work in a function call but works when the same call is assigned to a variable

I am working with apollo graphql client. The subscription in the server is working fine watching for changes.
But in the client, I am not able to log data.
I also tried to mutate but still its resulting in the same thing.
useSubscription(BOOK_ADDED, {
onData: ({ data }) => {
console.log(data)
}
})
The above code doesn't log anything out.
But,
const value = useSubscription(BOOK_ADDED, {
onData: ({ data }) => {
console.log(data)
}
})
console.log(value)
The above code seems to work fine logging out a value.
I am attaching a few codes below for more clarity.
index.js or apollo setup:
import ReactDOM from 'react-dom'
import App from './App'
import {
ApolloClient,
ApolloProvider,
HttpLink,
InMemoryCache,
split,
} from '#apollo/client'
import { setContext } from '#apollo/client/link/context'
import { getMainDefinition } from '#apollo/client/utilities'
import { GraphQLWsLink } from '#apollo/client/link/subscriptions'
import { createClient } from 'graphql-ws'
import Assess from './Asses'
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem('library-user-token')
return {
headers: {
...headers,
authorization: token ? `bearer ${token}` : null,
},
}
})
const httpLink = new HttpLink({ uri: 'http://localhost:4002' })
const wsLink = new GraphQLWsLink(
createClient({
url: 'ws://localhost:4002',
})
)
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
)
},
wsLink,
authLink.concat(httpLink)
)
const client = new ApolloClient({
cache: new InMemoryCache(),
link: splitLink,
})
ReactDOM.render(
<ApolloProvider client={client}>
<Assess />
</ApolloProvider>,
document.getElementById('root')
)
App.js
//App.js
import { useSubscription, useQuery } from "#apollo/client";
import { ALL_BOOKS, BOOK_ADDED } from "./queries";
const App = () => {
console.log(BOOK_ADDED);
const result = useQuery(ALL_BOOKS);
useSubscription(BOOK_ADDED, {
onData: ({ data }) => {
console.log(data);
},
});
console.log(result)
if(result.loading){
return null
}
return (
<div>
{result?.data?.allBooks.map((r) => (
<li key={r.id}>{r.title}</li>
))}
</div>
);
};
export default App
The query and fragment:
const BOOK_DETAILS = gql`
fragment BookDetails on Books {
title
author {
name
}
published
genres
id
}
`;
export const BOOK_ADDED = gql`
subscription {
bookAdded {
...BookDetails
}
}
${BOOK_DETAILS}
`;
After reading the changelogs of #apollo/client. I got to know that the method demonstrated in question is only useful when the version of #apollo/client is >=3.7.0. As my version was #3.6.7 it wasn't logging the value out.
Earlier than this version the function required an onSubscriptionData callback
to perform the same operation, which is now deprecated. I have still demonstrated it below as someone using version <#3.7.0 might find it useful.
useSubscription(BOOK_ADDED,{
onSubscriptionData: ({subscriptionData: data}) =>{
console.log(data)
}
})
You may read the change log here.

Could not identify object - getting error in graphql apollo client

I'm new to graphql I'm building real time chat app. Currently I'm making it offline first.
Using react as front-end.
I'm currently caching the data on localStorage using apollo3-cache-persist. But How do I query the cache data instead of server (when I'm offline) also I want to add messages to the localStorage while I'm offline.
Display the optimistic response when the device is online I want to send the pending data to the server.
my ApolloProvider.js file in client folder
import React from "react";
import {
ApolloClient,
InMemoryCache,
ApolloProvider as Provider,
createHttpLink,
ApolloLink,
split,
} from "#apollo/client";
import { setContext } from "#apollo/client/link/context";
import { RetryLink } from "#apollo/client/link/retry";
import { persistCache, LocalStorageWrapper } from "apollo3-cache-persist";
import { WebSocketLink } from "#apollo/client/link/ws";
import { getMainDefinition } from "#apollo/client/utilities";
import QueueLink from "apollo-link-queue";
let httpLink = createHttpLink({
uri: "http://localhost:4000/",
});
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem("token");
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
},
};
});
httpLink = authLink.concat(httpLink);
const wsLink = new WebSocketLink({
uri: `ws://localhost:4000/`,
options: {
reconnect: true,
connectionParams: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
},
});
const link = new RetryLink();
const queueLink = new QueueLink();
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
httpLink
);
const cache = new InMemoryCache();
const fun = async () =>
await persistCache({
cache,
storage: new LocalStorageWrapper(window.localStorage),
});
fun();
const client = new ApolloClient({
// link: splitLink,
link: ApolloLink.from([splitLink, queueLink, link]),
cache,
name: "chat-app",
version: "1.0.0",
queryDeduplication: false,
defaultOptions: {
watchQuery: {
fetchPolicy: "cache-and-network",
},
},
});
export default function ApolloProvider(props) {
return <Provider client={client} {...props} />;
}
my messages.js file
import React, { Fragment, useEffect, useState } from "react";
import { gql, useLazyQuery, useMutation, InMemoryCache } from "#apollo/client";
import { Col, Form } from "react-bootstrap";
import { useMessageDispatch, useMessageState } from "../../context/message";
import uuid from "react-uuid";
import Message from "./Message";
const SEND_MESSAGE = gql`
mutation sendMessage($uuid: String, $to: String!, $content: String!) {
sendMessage(uuid: $uuid, to: $to, content: $content) {
uuid
from
to
content
createdAt
hasSeen
hasSent
}
}
`;
const GET_MESSAGES = gql`
query getMessages($from: String!) {
getMessages(from: $from) {
uuid
from
to
content
createdAt
hasSeen
}
}
`;
export default function Messages() {
const { users } = useMessageState();
const dispatch = useMessageDispatch();
const [content, setContent] = useState("");
const selectedUser = users?.find((u) => u.selected === true);
const messages = selectedUser?.messages;
const [getMessages, { loading: messagesLoading, data: messagesData }] =
useLazyQuery(GET_MESSAGES, {
update(cache) {
cache.readFragment({});
console.log("reading");
},
});
const [sendMessage] = useMutation(SEND_MESSAGE, {
update(cache, { data: { sendMessage } }) {
cache.modify({
fields: {
getMessages(existingMsg) {
console.log(existingMsg);
const newMsgRef = cache.writeFragment({
data: sendMessage,
fragment: gql`
fragment sendNewMessage on Mutation {
uuid
to
from
content
hasSeen
hasSent
}
`,
});
return existingMsg.push(newMsgRef);
},
},
});
},
onError: (err) => console.log(err),
});
useEffect(() => {
if (selectedUser && !selectedUser.messages) {
getMessages({ variables: { from: selectedUser.username } });
}
}, [selectedUser]);
useEffect(() => {
if (messagesData) {
dispatch({
type: "SET_USER_MESSAGES",
payload: {
username: selectedUser.username,
messages: messagesData.getMessages,
},
});
}
}, [messagesData]);
const submitMessage = (e) => {
e.preventDefault();
if (content.trim() === "" || !selectedUser) return;
let id = uuid();
sendMessage({
variables: { uuid: id, to: selectedUser.username, content },
optimisticResponse: {
sendMessage: {
__typename: "Mutation",
uuid: id,
from: "User",
to: selectedUser.username,
content,
hasSent: false,
hasSeen: false,
createdAt: Date.now(),
},
},
});
setContent("");
};
// Displaying helper text and styling
let selectedChatMarkup;
if (!messages && !messagesLoading) {
selectedChatMarkup = <p className="info-text"> Select a friend</p>;
} else if (messagesLoading) {
selectedChatMarkup = <p className="info-text"> Loading..</p>;
} else if (messages.length > 0) {
selectedChatMarkup = messages.map((message, index) => (
<Fragment key={message.uuid}>
<Message message={message} />
{index === messages.length - 1 && (
<div className="invisible">
<hr className="m-0" />
</div>
)}
</Fragment>
));
} else if (messages.length === 0) {
selectedChatMarkup = (
<p className="info-text">
You are now connected! send your first message!
</p>
);
}
return (
<Col xs={10} md={8}>
<div className="messages-box d-flex flex-column-reverse">
{selectedChatMarkup}
</div>
<div>
<Form onSubmit={submitMessage}>
<Form.Group className="d-flex align-items-center">
<Form.Control
type="text"
className="message-input rounded-pill p-4 bg-secondary border-0"
placeholder="Type a message.."
value={content}
onChange={(e) => setContent(e.target.value)}
/>
<i
className="fas fa-regular fa-paper-plane fa-2x text-primary ml-2"
onClick={submitMessage}
role="button"
></i>
</Form.Group>
</Form>
</div>
</Col>
);
}
But I'm currently getting this error when I try to send the message
react_devtools_backend.js:3973 Invariant Violation: Could not identify object {"uuid":"4855ffc-6b7b-d7c8-a68-2ae0162f80a","from":"User","to":"Fire","content":"example text","createdAt":1648881891383,"hasSeen":false,"hasSent":false,"__typename":"Mutation"}
Also getting error from the mutation error log
Error: Could not identify object {"__typename":"Message","uuid":"4855ffc-6b7b-d7c8-a68-2ae0162f80a","from":"Alan","to":"Fire","content":"example text","createdAt":"2022-04-02T06:44:51.807Z","hasSeen":false,"hasSent":false}
Apollo uses by default __typename and id fields to normalise cache. So in your case, Apollo won't recognise your uuid property as a cache identifier. You can change the uuid uuid property, or add keyFields to your InMemoryCache config.

NextJs GraphQL Subscription: How to set up connect api with Apolo [duplicate]

I am new to NextJS. I have a page that needs to display real-time data pulled from a Hasura GraphQL backend.
In other non-NextJS apps, I have used GraphQL subscriptions with the Apollo client library. Under the hood, this uses websockets.
I can get GraphQL working in NextJS when it's not using subscriptions. I'm pretty sure this is running on the server-side:
import React from "react";
import { AppProps } from "next/app";
import withApollo from 'next-with-apollo';
import { ApolloProvider } from '#apollo/react-hooks';
import ApolloClient, { InMemoryCache } from 'apollo-boost';
import { getToken } from "../util/auth";
interface Props extends AppProps {
apollo: any
}
const App: React.FC<Props> = ({ Component, pageProps, apollo }) => (
<ApolloProvider client={apollo}>
<Component {...pageProps}/>
</ApolloProvider>
);
export default withApollo(({ initialState }) => new ApolloClient({
uri: "https://my_hasura_instance.com/v1/graphql",
cache: new InMemoryCache().restore(initialState || {}),
request: (operation: any) => {
const token = getToken();
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : ''
}
});
}
}))(App);
And I use it this way:
import { useQuery } from '#apollo/react-hooks';
import { gql } from 'apollo-boost';
const myQuery = gql`
query {
...
}
`;
const MyComponent: React.FC = () => {
const { data } = useQuery(myQuery);
return <p>{JSON.stringify(data)}</p>
}
However, I would instead like to do this:
import { useSubscription } from '#apollo/react-hooks';
import { gql } from 'apollo-boost';
const myQuery = gql`
subscription {
...
}
`;
const MyComponent: React.FC = () => {
const { data } = useSubscription(myQuery);
return <p>{JSON.stringify(data)}</p>
}
What I've tried
I've tried splitting the HttpLink and WebsocketLink elements in the ApolloClient, like so:
import React from "react";
import { AppProps } from "next/app";
import { ApolloProvider } from '#apollo/react-hooks';
import withApollo from 'next-with-apollo';
import { InMemoryCache } from "apollo-cache-inmemory";
import ApolloClient from "apollo-client";
import { split } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
import { getToken } from "../util/auth";
interface Props extends AppProps {
apollo: any
}
const App: React.FC<Props> = ({ Component, pageProps, apollo }) => (
<ApolloProvider client={apollo}>
<Component {...pageProps}/>
</ApolloProvider>
);
const wsLink = new WebSocketLink({
uri: "wss://my_hasura_instance.com/v1/graphql",
options: {
reconnect: true,
timeout: 10000,
connectionParams: () => ({
headers: {
authorization: getToken() ? `Bearer ${getToken()}` : ""
}
})
},
});
const httpLink = new HttpLink({
uri: "https://hasura-g3uc.onrender.com/v1/graphql",
});
const link = process.browser ? split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink
) : httpLink;
export default withApollo(({ initialState }) => new ApolloClient({
link: link,
cache: new InMemoryCache().restore(initialState || {}),
}))(App);
But when I load the page, I get an Internal Server Error, and this error in the terminal:
Error: Unable to find native implementation, or alternative implementation for WebSocket!
It seems to me that the ApolloClient is then being generated on the server-side, where there is no WebSocket implementation. How can I make this happen on the client-side?
Found workaround to make it work, take look at this answer https://github.com/apollographql/subscriptions-transport-ws/issues/333#issuecomment-359261024
the reason was due to server-side rendering; these statements must run in the browser, so we test if we have process.browser !!
relevant section from the attached github link:
const wsLink = process.browser ? new WebSocketLink({ // if you instantiate in the server, the error will be thrown
uri: `ws://localhost:4000/subscriptions`,
options: {
reconnect: true
}
}) : null;
const httplink = new HttpLink({
uri: 'http://localhost:3000/graphql',
credentials: 'same-origin'
});
const link = process.browser ? split( //only create the split in the browser
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httplink,
) : httplink;
This answer seems to be more actual
https://github.com/apollographql/subscriptions-transport-ws/issues/333#issuecomment-775578327
You should install ws by npm i ws and add webSocketImpl: ws to WebSocketLink argument.
import ws from 'ws';
const wsLink = new WebSocketLink({
uri: endpoints.ws,
options: {
reconnect: true,
connectionParams: () => ({
...getToken() && {Authorization: getToken()}
})
},
webSocketImpl: ws
});
Solution: Make wsLink a function variable like the code below.
// src/apollo.ts
import { ApolloClient, HttpLink, InMemoryCache } from "#apollo/client";
import { GraphQLWsLink } from "#apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
const httpLink = new HttpLink({
uri: 'http://localhost:3000/graphql'
});
const wsLink = () => {
return new GraphQLWsLink(createClient({
url: 'ws://localhost:3000/graphql'
}));
}
export const apolloClient = new ApolloClient({
link: typeof window === 'undefined' ? httpLink : wsLink(),
cache: new InMemoryCache(),
});
// pages/_app.tsx
import { ApolloProvider } from "#apollo/client";
import { apolloClient } from "../src/apollo";
function MyApp({ Component, pageProps }) {
return (
<ApolloProvider client={apolloClient}>
<Component {...pageProps} />
</ApolloProvider>
);
}

getStaticProps returns an empty object

I'm using nextJS V9.5.5 with wp-graphql and apolloClient to get data from WordPress. Everything works fine, but when I try to return context (in the purpose of getting query) from getStaticProps() like it's described in docs, it returns an empty object.
Custom App:
import React from "react";
import getConfig from "next/config";
import LayoutOuter from "../components/LayoutOuter";
import "bootstrap/dist/css/bootstrap.css";
import { ApolloProvider } from "#apollo/client";
import { useApollo } from "../lib/apolloClient";
import { initializeApollo } from "../lib/apolloClient";
import { gql } from "#apollo/client";
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig();
const { DOMAIN } = publicRuntimeConfig;
function CustomApp({ pageProps, Component, props }) {
const apolloClient = useApollo(pageProps.initialApolloState);
return (
<ApolloProvider client={apolloClient}>
{console.log("_app", props)}
<LayoutOuter>
<Component {...pageProps} />
</LayoutOuter>
</ApolloProvider>
);
}
CustomApp.getInitialProps = async (ctx) => {
const apolloClient = initializeApollo();
await apolloClient.query({
query: gql`
{
// my graphql query here
}
`,
});
return {
props: {
initialApolloState: apolloClient.cache.extract(),
ctx: JSON.stringify(ctx),
},
};
};
export default CustomApp;
One of the page:
import React, { Component, useEffect, useState } from "react";
import getConfig from "next/config";
import { NextSeo } from "next-seo";
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig();
const { DOMAIN, SITENAME } = publicRuntimeConfig;
import { initializeApollo } from "../lib/apolloClient";
import { gql } from "#apollo/client";
import "./services.module.scss";
const Home = (props) => {
let currentPage = Object.values(props.initialApolloState.ROOT_QUERY)[1];
const {
title,
metadesc,
metaRobotsNoindex,
metaRobotsNofollow,
metaRobotsAdv,
opengraphTitle,
opengraphDescription,
opengraphImage,
twitterTitle,
twitterDescription,
twitterImage,
} = currentPage.seo;
return (
<>
{console.log("project", props)}
<NextSeo
noindex={metaRobotsNoindex}
nofollow={metaRobotsNofollow}
title={title != "" ? title : `${props.data.pagetitle} - ${SITENAME}`}
description={metadesc}
canonical={DOMAIN}
openGraph={{
url: DOMAIN,
title:
opengraphTitle != ""
? opengraphTitle
: `${props.data.pagetitle} - Garrison Collection`,
description: opengraphDescription,
images: [
{
url: opengraphImage,
width: 800,
height: 600,
alt: { SITENAME },
},
],
site_name: { SITENAME },
}}
/>
<p>works</p>
</>
);
};
export async function getStaticProps(context) {
const apolloClient = initializeApollo();
await apolloClient.query({
query: gql`
{
project(id: "ca-souls", idType: SLUG) {
seo {
canonical
metaDesc
metaKeywords
metaRobotsNofollow
metaRobotsNoindex
opengraphAuthor
opengraphDescription
opengraphModifiedTime
opengraphPublishedTime
opengraphPublisher
opengraphSiteName
opengraphTitle
opengraphType
opengraphUrl
title
twitterDescription
twitterTitle
}
}
}
`,
});
return {
props: {
initialApolloState: apolloClient.cache.extract(),
context: JSON.stringify(context) || null,
},
revalidate: 1,
};
}
export default Home;
Here is the log result:
How could I get context.query?
The context parameter includes previewData contains the preview data set by setPreviewData. This means including function, therefore, unable to serealize. Take values out from context.params.

How to use Apollo GraphQL subscriptions in the client-side NextJS?

I am new to NextJS. I have a page that needs to display real-time data pulled from a Hasura GraphQL backend.
In other non-NextJS apps, I have used GraphQL subscriptions with the Apollo client library. Under the hood, this uses websockets.
I can get GraphQL working in NextJS when it's not using subscriptions. I'm pretty sure this is running on the server-side:
import React from "react";
import { AppProps } from "next/app";
import withApollo from 'next-with-apollo';
import { ApolloProvider } from '#apollo/react-hooks';
import ApolloClient, { InMemoryCache } from 'apollo-boost';
import { getToken } from "../util/auth";
interface Props extends AppProps {
apollo: any
}
const App: React.FC<Props> = ({ Component, pageProps, apollo }) => (
<ApolloProvider client={apollo}>
<Component {...pageProps}/>
</ApolloProvider>
);
export default withApollo(({ initialState }) => new ApolloClient({
uri: "https://my_hasura_instance.com/v1/graphql",
cache: new InMemoryCache().restore(initialState || {}),
request: (operation: any) => {
const token = getToken();
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : ''
}
});
}
}))(App);
And I use it this way:
import { useQuery } from '#apollo/react-hooks';
import { gql } from 'apollo-boost';
const myQuery = gql`
query {
...
}
`;
const MyComponent: React.FC = () => {
const { data } = useQuery(myQuery);
return <p>{JSON.stringify(data)}</p>
}
However, I would instead like to do this:
import { useSubscription } from '#apollo/react-hooks';
import { gql } from 'apollo-boost';
const myQuery = gql`
subscription {
...
}
`;
const MyComponent: React.FC = () => {
const { data } = useSubscription(myQuery);
return <p>{JSON.stringify(data)}</p>
}
What I've tried
I've tried splitting the HttpLink and WebsocketLink elements in the ApolloClient, like so:
import React from "react";
import { AppProps } from "next/app";
import { ApolloProvider } from '#apollo/react-hooks';
import withApollo from 'next-with-apollo';
import { InMemoryCache } from "apollo-cache-inmemory";
import ApolloClient from "apollo-client";
import { split } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
import { getToken } from "../util/auth";
interface Props extends AppProps {
apollo: any
}
const App: React.FC<Props> = ({ Component, pageProps, apollo }) => (
<ApolloProvider client={apollo}>
<Component {...pageProps}/>
</ApolloProvider>
);
const wsLink = new WebSocketLink({
uri: "wss://my_hasura_instance.com/v1/graphql",
options: {
reconnect: true,
timeout: 10000,
connectionParams: () => ({
headers: {
authorization: getToken() ? `Bearer ${getToken()}` : ""
}
})
},
});
const httpLink = new HttpLink({
uri: "https://hasura-g3uc.onrender.com/v1/graphql",
});
const link = process.browser ? split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink
) : httpLink;
export default withApollo(({ initialState }) => new ApolloClient({
link: link,
cache: new InMemoryCache().restore(initialState || {}),
}))(App);
But when I load the page, I get an Internal Server Error, and this error in the terminal:
Error: Unable to find native implementation, or alternative implementation for WebSocket!
It seems to me that the ApolloClient is then being generated on the server-side, where there is no WebSocket implementation. How can I make this happen on the client-side?
Found workaround to make it work, take look at this answer https://github.com/apollographql/subscriptions-transport-ws/issues/333#issuecomment-359261024
the reason was due to server-side rendering; these statements must run in the browser, so we test if we have process.browser !!
relevant section from the attached github link:
const wsLink = process.browser ? new WebSocketLink({ // if you instantiate in the server, the error will be thrown
uri: `ws://localhost:4000/subscriptions`,
options: {
reconnect: true
}
}) : null;
const httplink = new HttpLink({
uri: 'http://localhost:3000/graphql',
credentials: 'same-origin'
});
const link = process.browser ? split( //only create the split in the browser
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httplink,
) : httplink;
This answer seems to be more actual
https://github.com/apollographql/subscriptions-transport-ws/issues/333#issuecomment-775578327
You should install ws by npm i ws and add webSocketImpl: ws to WebSocketLink argument.
import ws from 'ws';
const wsLink = new WebSocketLink({
uri: endpoints.ws,
options: {
reconnect: true,
connectionParams: () => ({
...getToken() && {Authorization: getToken()}
})
},
webSocketImpl: ws
});
Solution: Make wsLink a function variable like the code below.
// src/apollo.ts
import { ApolloClient, HttpLink, InMemoryCache } from "#apollo/client";
import { GraphQLWsLink } from "#apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
const httpLink = new HttpLink({
uri: 'http://localhost:3000/graphql'
});
const wsLink = () => {
return new GraphQLWsLink(createClient({
url: 'ws://localhost:3000/graphql'
}));
}
export const apolloClient = new ApolloClient({
link: typeof window === 'undefined' ? httpLink : wsLink(),
cache: new InMemoryCache(),
});
// pages/_app.tsx
import { ApolloProvider } from "#apollo/client";
import { apolloClient } from "../src/apollo";
function MyApp({ Component, pageProps }) {
return (
<ApolloProvider client={apolloClient}>
<Component {...pageProps} />
</ApolloProvider>
);
}

Resources