From 156a27042b605a8372404629634bf5348ae64b93 Mon Sep 17 00:00:00 2001 From: klis87 Date: Sat, 20 Feb 2021 23:25:06 +0100 Subject: [PATCH] Added subscriptions to graphql example --- examples/graphql/dev-server.js | 46 +++++++++++++++++++++++-- examples/graphql/src/components/app.jsx | 30 ++++++++++++++-- examples/graphql/src/store/actions.js | 31 +++++++++++++++++ examples/graphql/src/store/constants.js | 2 ++ examples/graphql/src/store/index.js | 12 +++++-- 5 files changed, 114 insertions(+), 7 deletions(-) diff --git a/examples/graphql/dev-server.js b/examples/graphql/dev-server.js index e688ecd71..20637772e 100644 --- a/examples/graphql/dev-server.js +++ b/examples/graphql/dev-server.js @@ -1,11 +1,15 @@ require('@babel/polyfill'); +const { createServer } = require('http'); + const express = require('express'); const webpack = require('webpack'); const webpackDevMiddleware = require('webpack-dev-middleware'); -const { ApolloServer, gql } = require('apollo-server-express'); +const { ApolloServer, gql, PubSub } = require('apollo-server-express'); const webpackConfig = require('./webpack.config'); +const pubsub = new PubSub(); + const app = express(); app.use( @@ -29,6 +33,8 @@ const books = [ }, ]; +let numberOfBookLikes = 0; + const typeDefs = gql` type Book { id: ID! @@ -46,6 +52,7 @@ const typeDefs = gql` type Query { books: [Book!]! book(id: ID!): Book + numberOfBookLikes: Int! } type Mutation { @@ -55,14 +62,26 @@ const typeDefs = gql` singleUpload(file: Upload!): File! multipleUpload(files: [Upload!]!): [File!]! } + + type Subscription { + onBookLiked: Int! + } `; const findBookById = id => books.find(book => book.id === id); +const ON_BOOK_LIKED = 'ON_BOOK_LIKED'; + const resolvers = { Query: { books: () => books, book: (_, args) => findBookById(args.id), + numberOfBookLikes: () => numberOfBookLikes, + }, + Subscription: { + onBookLiked: { + subscribe: () => pubsub.asyncIterator([ON_BOOK_LIKED]), + }, }, Mutation: { deleteBook: (_, args) => findBookById(args.id), @@ -74,6 +93,8 @@ const resolvers = { } book.liked = true; + numberOfBookLikes += 1; + pubsub.publish(ON_BOOK_LIKED, { onBookLiked: numberOfBookLikes }); return book; }, unlikeBook: (_, args) => { @@ -99,13 +120,32 @@ const resolvers = { }, }; +// const sleep = () => new Promise(resolve => setTimeout(resolve, 50)); + const server = new ApolloServer({ typeDefs, resolvers, + subscriptions: { + // onConnect: async connectionParams => { + // if (connectionParams.token === 'pass') { + // await sleep(); + + // return { + // currentUser: 'user', + // }; + // } + + // throw new Error('Missing auth token!'); + // }, + keepAlive: 10000, + }, }); -server.applyMiddleware({ app }); +server.applyMiddleware({ app, path: '/graphql' }); + +const httpServer = createServer(app); +server.installSubscriptionHandlers(httpServer); -app.listen(3000, () => { +httpServer.listen(3000, () => { console.log('Listening on port 3000!'); }); diff --git a/examples/graphql/src/components/app.jsx b/examples/graphql/src/components/app.jsx index d496a9982..faa9c745b 100644 --- a/examples/graphql/src/components/app.jsx +++ b/examples/graphql/src/components/app.jsx @@ -1,8 +1,15 @@ import React from 'react'; -import { useDispatch } from 'react-redux'; -import { Query, Mutation } from '@redux-requests/react'; +import { useDispatch, useSelector } from 'react-redux'; +import { getWebsocketState } from '@redux-requests/core'; +import { + Query, + Mutation, + useQuery, + useSubscription, +} from '@redux-requests/react'; import { + fetchNumberOfBookLikes, fetchBooks, fetchBook, deleteBook, @@ -10,14 +17,17 @@ import { unlikeBook, uploadFile, uploadFiles, + onBookLiked, } from '../store/actions'; import { + FETCH_NUMBER_OF_BOOK_LIKES, LIKE_BOOK, UNLIKE_BOOK, FETCH_BOOK, FETCH_BOOKS, UPLOAD_FILE, UPLOAD_FILES, + ON_BOOK_LIKED, } from '../store/constants'; import Spinner from './spinner'; @@ -30,10 +40,26 @@ const RequestError = () => ( const App = () => { const dispatch = useDispatch(); + const websocketState = useSelector(getWebsocketState); + const { data } = useQuery({ + type: FETCH_NUMBER_OF_BOOK_LIKES, + action: fetchNumberOfBookLikes, + autoLoad: true, + }); + + useSubscription({ + type: ON_BOOK_LIKED, + action: onBookLiked, + }); return (

Redux Requests GraphQL example

+
+ WS: {websocketState.connected ? 'connected' : 'disconnected'},{' '} + {websocketState.pristine ? 'pristine' : 'not pristine'}{' '} +
+ {data &&
Number of likes: {data.numberOfBookLikes}
}

In order to see aborts in action, you should set network throttling in your browser diff --git a/examples/graphql/src/store/actions.js b/examples/graphql/src/store/actions.js index 85f2f483e..1f53112c8 100644 --- a/examples/graphql/src/store/actions.js +++ b/examples/graphql/src/store/actions.js @@ -1,11 +1,13 @@ import { gql } from '@redux-requests/graphql'; import { + FETCH_NUMBER_OF_BOOK_LIKES, FETCH_BOOKS, FETCH_BOOK, DELETE_BOOK, LIKE_BOOK, UNLIKE_BOOK, + ON_BOOK_LIKED, UPLOAD_FILE, UPLOAD_FILES, } from './constants'; @@ -18,6 +20,17 @@ const bookFragment = gql` } `; +export const fetchNumberOfBookLikes = () => ({ + type: FETCH_NUMBER_OF_BOOK_LIKES, + request: { + query: gql` + { + numberOfBookLikes + } + `, + }, +}); + export const fetchBooks = () => ({ type: FETCH_BOOKS, request: { @@ -125,6 +138,24 @@ export const unlikeBook = id => ({ }, }); +export const onBookLiked = () => ({ + type: ON_BOOK_LIKED, + subscription: { + query: gql` + subscription { + onBookLiked + } + `, + }, + meta: { + mutations: { + [FETCH_NUMBER_OF_BOOK_LIKES]: (data, subscriptionData) => ({ + numberOfBookLikes: subscriptionData.onBookLiked, + }), + }, + }, +}); + const fileFragment = gql` fragment FileFragment on File { filename diff --git a/examples/graphql/src/store/constants.js b/examples/graphql/src/store/constants.js index 2a1d9efd1..b62ee271e 100644 --- a/examples/graphql/src/store/constants.js +++ b/examples/graphql/src/store/constants.js @@ -1,7 +1,9 @@ export const FETCH_BOOKS = 'FETCH_BOOKS'; export const FETCH_BOOK = 'FETCH_BOOK'; +export const FETCH_NUMBER_OF_BOOK_LIKES = 'FETCH_NUMBER_OF_BOOK_LIKES'; export const DELETE_BOOK = 'DELETE_BOOK'; export const LIKE_BOOK = 'LIKE_BOOK'; export const UNLIKE_BOOK = 'UNLIKE_BOOK'; export const UPLOAD_FILE = 'UPLOAD_FILE'; export const UPLOAD_FILES = 'UPLOAD_FILES'; +export const ON_BOOK_LIKED = 'ON_BOOK_LIKED'; diff --git a/examples/graphql/src/store/index.js b/examples/graphql/src/store/index.js index c1f03a19e..6a632900b 100644 --- a/examples/graphql/src/store/index.js +++ b/examples/graphql/src/store/index.js @@ -1,10 +1,18 @@ import { createStore, applyMiddleware, combineReducers, compose } from 'redux'; import { handleRequests } from '@redux-requests/core'; -import { createDriver } from '@redux-requests/graphql'; +import { createDriver, createSubscriber } from '@redux-requests/graphql'; export const configureStore = () => { const { requestsReducer, requestsMiddleware } = handleRequests({ - driver: createDriver({ url: 'http://localhost:3000/graphql' }), + driver: createDriver({ + url: 'http://localhost:3000/graphql', + }), + subscriber: createSubscriber({ + url: 'ws://localhost:3000/graphql', + // lazy: false, + heartbeatTimeout: 12, + useHeartbeat: true, + }), }); const reducers = combineReducers({