How to use Meteor collections pub/sub with react hooks
clean wrapping of meteor subscription data with the useTracker hook
Let's start with a simple collection (for example: /imports/api/articles/index.ts
)
import { Mongo } from 'meteor/mongo';
export type Article = {
slug: string,
title: string,
description: string,
text: string,
}
export const ArticlesCollection = new Mongo.Collection<Article>('articles');
// security best practice: deny all client-side updates by default
ArticlesCollection.deny({
insert() { return true; },
update() { return true; },
remove() { return true; },
});
Now define some server-side publications (for example: /imports/api/articles/server/publications.ts
).
import { Meteor } from "meteor/meteor";
import { ArticlesCollection } from "..";
Meteor.publish('articles', () => {
return ArticlesCollection.find({})
});
Meteor.publish('article', ({ slug }) => {
return ArticlesCollection.find({ slug })
});
Remember that you have to include this in the actual server code, like in /server/main.ts
:
import '../imports/api/shared/articles/server/publications';
Now to the interesting part, let's define two hooks to actually fetch the data
(for example: /imports/api/articles/client/hooks.tsx
)
import { Meteor } from "meteor/meteor";
import { useTracker } from "meteor/react-meteor-data";
import { Article, ArticlesCollection } from "..";
export const useArticle = (slug: string): Article | undefined => {
return useTracker(() => {
const sub = Meteor.subscribe('article', { slug });
if (sub.ready()) {
return ArticlesCollection.findOne({ slug })
} else {
return undefined
}
}, [slug])
}
export const useArticles = (): Article[] => {
return useTracker(() => {
const sub = Meteor.subscribe('articles');
if (sub.ready()) {
return ArticlesCollection.find().fetch()
} else {
return []
}
})
}
Note that the last argument of the useTracker
hook (similar to useEffect
)
does declare a reactive dependency on the slug
in the first hook. It may be
undefined
on the first run (like, when you fetch the slug from react-router with a hook first).
Now rendering some react pages is as easy as this example:
import React from 'react';
import { useParams } from "react-router-dom";
import { useArticle } from '/imports/api/articles/client/hooks';
import { Template } from '/imports/ui/templates/ArticleTemplate';
export default () => {
const { slug } = useParams<{ slug: string }>();
const article = useArticle(slug);
if (article) {
return (<Template article={article} />)
} else {
return (<div>loading data...</div>)
}
};