enisdenjo / graphql-sse

Zero-dependency, HTTP/1 safe, simple, GraphQL over Server-Sent Events Protocol server and client.
https://the-guild.dev/graphql/sse
MIT License
384 stars 19 forks source link

GraphQL SSE Subscriptions in a React-Native App. #67

Closed CrispenGari closed 1 year ago

CrispenGari commented 1 year ago

I'm using the urql implementation to with graphql-sse to create a graphql client as follows:

import {
  Client,
  cacheExchange,
  fetchExchange,
  subscriptionExchange,
} from "urql";
import { KEYS, serverDomain } from "../constants";
import { del, retrieve } from "../utils";
import { createClient as createSSEClient } from "graphql-sse";
import { authExchange } from "@urql/exchange-auth";
import { getToken, setToken } from "../state/token";

const sseClient = createSSEClient({
  url: `http://${serverDomain}/graphql`,
});
export const client = new Client({
  url: `http://${serverDomain}/graphql`,
  requestPolicy: "network-only",
  exchanges: [
    cacheExchange,
    fetchExchange,
    authExchange(async (utils) => {
      const jwt = await retrieve(KEYS.TOKEN_KEY);
      setToken(jwt);
      return {
        addAuthToOperation(operation) {
          if (jwt) {
            return utils.appendHeaders(operation, {
              Authorization: `Bearer ${jwt}`,
            });
          }
          return operation;
        },
        willAuthError(_operation) {
          return !jwt;
        },
        didAuthError(error, _operation) {
          return error.graphQLErrors.some(
            (e) => e.extensions?.code === "FORBIDDEN"
          );
        },
        async refreshAuth() {
          setToken(null);
          await del(KEYS.TOKEN_KEY);
        },
      };
    }),
    subscriptionExchange({
      forwardSubscription(operation) {
        return {
          subscribe: (sink) => {
            const dispose = sseClient.subscribe(operation as any, sink);
            return {
              unsubscribe: dispose,
            };
          },
        };
      },
    }),
  ],
  fetchOptions: () => {
    const token = getToken();
    return {
      headers: { authorization: `Bearer ${token || ""}` },
    };
  },
});

Then when try to use the useSubscription hook as follows to listen to new incoming subscriptions in my component as follows:

import { COLORS, FONTS } from "../../constants";
import { AppParamList } from "../../params";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { HomeStack } from "./home";
import { FriendsStack } from "./friends";
import { NotificationsStack } from "./notifications";
import { SettingsStack } from "./settings";
import TabIcon from "../../components/TabIcon/TabIcon";
import {
  MaterialCommunityIcons,
  MaterialIcons,
  Ionicons,
} from "@expo/vector-icons";
import { useMeStore } from "../../store";
import { useSubscription } from "urql";
const Tab = createBottomTabNavigator<AppParamList>();

const Doc = `
  subscription OnNewFriendRequest($input: OnNewFriendRequestInputType!) {
    onNewFriendRequest(input: $input) {
      message
      friend {
        id
        nickname
      }
    }
  }
`;
export const AppTabs = () => {
  const { me } = useMeStore();
  const [{ data, fetching, error }] = useSubscription({
    query: Doc,
    variables: {
      input: { id: "ef7c5256-80f0-4de0-9893-51f3e3e9926e" },
    },
  });

  console.log(JSON.stringify({ data, fetching, error }, null, 2));
  return (
    <Tab.Navigator
      initialRouteName="Home"
      screenOptions={{
        headerShown: false,
        tabBarHideOnKeyboard: true,
        tabBarStyle: {
          elevation: 0,
          shadowOpacity: 0,
          borderTopWidth: 0,
          borderColor: "transparent",
          backgroundColor: COLORS.primary,
          paddingVertical: 10,
          height: 80,
          width: "auto",
        },
        tabBarShowLabel: false,
        tabBarBadgeStyle: {
          backgroundColor: "cornflowerblue",
          color: "white",
          fontSize: 10,
          maxHeight: 20,
          maxWidth: 20,
          marginLeft: 3,
        },
        tabBarVisibilityAnimationConfig: {
          hide: {
            animation: "timing",
          },
          show: {
            animation: "spring",
          },
        },
        tabBarItemStyle: {
          width: "auto",
        },
      }}
    >
      <Tab.Screen
        options={{
          tabBarIcon: (props) => (
            <TabIcon
              {...props}
              title="home"
              Icon={{
                name: "home-account",
                IconComponent: MaterialCommunityIcons,
              }}
            />
          ),
        }}
        name="Home"
        component={HomeStack}
      />
      <Tab.Screen
        options={{
          tabBarIcon: (props) => (
            <TabIcon
              {...props}
              title="friends"
              Icon={{
                name: "person-search",
                IconComponent: MaterialIcons,
              }}
            />
          ),
        }}
        name="Friends"
        component={FriendsStack}
      />
      <Tab.Screen
        options={{
          tabBarIcon: (props) => (
            <TabIcon
              {...props}
              title="notifications"
              Icon={{
                name: "notifications",
                IconComponent: Ionicons,
              }}
            />
          ),
        }}
        name="Notifications"
        component={NotificationsStack}
      />
      <Tab.Screen
        options={{
          tabBarIcon: (props) => (
            <TabIcon
              {...props}
              title="settings"
              Icon={{
                name: "settings",
                IconComponent: Ionicons,
              }}
            />
          ),
        }}
        name="Settings"
        component={SettingsStack}
      />
    </Tab.Navigator>
  );
};

In my logs in a react-native application i'm only getting the loading state to true as follows:

{
  "fetching": true
}

But in my React web app when a new subscription is fired i'm getting the expected results as follows:

{
  "data": {
    "onNewFriendRequest": null
  },
  "fetching": false
}

Why am i getting this different behaviour in react-native?.

enisdenjo commented 1 year ago

Converted to a discussion #68.