I’m working on a project using graphql with React Native in the frontend and Nodejs in the backend with mongodb as my database. In this project, I have posts that are created and shown on a screen. I’ve done this using graphql subscriptions for real time updating of posts, but I need the user to also be able to delete their own posts and for that update to be shown in real time. Currently, I have:
Backend:
import User from '../../models/User.js';
import Posts from '../../models/Posts.js';
import { requireAuth } from '../../services/auth.js';
import { PubSub } from 'graphql-subscriptions';
import { ObjectId } from "mongodb";
// need to change for product PubSub is not advised for production!!!
const pubsub = new PubSub();
const NEW_MESSAGE_EVENT = 'newMessage';
export default {
getPost: async (_, { offset, limit, topic },{user}) => {
try {
const post = await Posts
.find({"post.topic":topic})
.skip(offset)
.sort({"post.dateSent": -1 })
.limit(Math.min(limit, 100))
return post
} catch (error) {
throw error;
}
},
createPost: async (_, { input }) => {
try {
const post = new Posts(input);
await post.save();
pubsub.publish('NEW_POST_EVENT', { newPost: post });
return post;
} catch (error) {
throw error;
}
},
newPost: {
subscribe: () => pubsub.asyncIterator(['NEW_MESSAGE_EVENT'])
},
};
Frontend:
...
export const GET_POST = gql`
query ($offset: Int!, $topic: String) {
getMessage(offset: $offset, limit: 20, topic: $topic) {
_id
sender {
senderId
}
post {
text
topic
dateSent
}
}
}
`;
export const CREATE_POST_MUTATION = gql`
mutation createPost($input: PostInput!) {
createPost(input: $input) {
post {
text
dateSent
}
}
}
`;
export const POST_SUBSCRIPTION = gql`
subscription {
_id
newPost {
sender {
senderId
}
post {
text
topic
dateSent
}
}
}
`;
export default function PostScreen() {
const [createPost,{ data: createPostDate, error:createPostError}] = useMutation(CREATE_POST_MUTATION)
async function createPostHandler (item: any) {
createPost({
variables: {
input: {
...
},
}})
}
const {data, loading, error, subscribeToMore, fetchMore: fetchMorePosts} = useQuery(GET_POST, {fetchPolicy: 'cache-and-network', variables: { offset: 0, topic: ''}})
useEffect(() => {
subscribeToMore({
document: POST_SUBSCRIPTION,
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData) {
return prev;
}
const newPost = subscriptionData.data.newPost;
return {
getPost:[newPost, ...prev.getPost]
};
},
onError: err => console.error(err)
});
}, []);
const fetchMoreAndUpdateCache = useMemo(() => {
return async (offset: number) => {
await fetchMorePosts({
variables: { offset },
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) {
return prev;
}
const moreItems = fetchMoreResult.getPost;
if(moreItems.length < 1) {
setStopFetchMore(true);
}
return {
getPost: [...prev.getPost, ...moreItems]
};
}
});
};
}, []);
const fetchMore = useCallback(() => {
if(!data || loading || isStopFetchMore) {
return;
}
const offset = data.getPost.length;
if (offset >= 20) {
fetchMoreAndUpdateCache(offset);
}
}, [isStopFetchMore, loading, data]);
return (
...
)
}
I’ve thought of two ways of being able to delete and have it update with the subscription, but I’m not sure if either is the correct way. The first way would be changing newPost
to updatePost
and have something like:
Backend:
....
type PostSubscription {
data: Post
action: String
}
type Subscription {
updatePost: PostSubscription
}
deletePost: async (_, { ids}) => {
try {
const deletePosts = await Posts.deleteMany({_id:{$in:[...ids]}});
pubsub.publish('UPDATE_POST_EVENT', {
updatePost: {
action: 'DELETE',
data: deletePosts,
}
})
return deletePosts;
} catch (error) {
throw error;
}
},
updatePost: {
subscribe: () => pubsub.asyncIterator(['UPDATE_POST_EVENT'])
},
in the backend and try to check for changes in useEffect(() => {subscribeToMore({ document: POST_SUBSCRIPTION,...
in the frontend , where maybe I would have to filter out deletePost
from the getPost query.
export const POST_SUBSCRIPTION = gql`
subscription {
_id
updatePost {
data{
sender {
senderId
}
post {
text
topic
dateSent
}
}
action
}
}
`;
...
useEffect(() => {
subscribeToMore({
document: POST_SUBSCRIPTION,
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData) {
return prev;
}
const updatePost = subscriptionData.data.updatePost.data;
const action = subscriptionData.data.updatePost.action;
if (action === "CREATE") {
return {
getPost:[updatePost, ...prev.getPost]
};
}
else if (action === "DELETE"){
getPost ...
},
onError: err => console.error(err)
});
}, []);
The second way would be by having a whole separate subscription event: deletePost: { subscribe: () => pubsub.asyncIterator(['DELETE_POST_EVENT'])}
, but I’m not 100% sure on how I would use that to update the data in the frontend. I would really appreciate any help or advice on how to do this. Thank you!