quave:react-data
quave:react-data
is a Meteor package that allows you to subscribe to publications and also call methods.
Features
- Call methods with await
- Subscribe to data with skip logic
- Deps array to resubscribe
Why
Almost every Meteor application with React is using Subscriptions and Methods, so it's helpful to provide React Hooks ready to use in common cases.
Installation
meteor add quave:react-data
Usage
Both methods listed below rely on the idea of providing a single object to Meteor.call
and also to Meteor.subscribe
as the second parameter.
It means that you should send the data to the server putting in the arg field.
For example, instead of using Meteor.call('myMethod', param1, param2);
you should do Meteor.call('myMethod', { param1, param2 });
. Of course, using the method
provided instead of Meteor.call
.
The same for Meteor.subscribe
but also using useData
and in this case, as we have many ways to use it, you should use a named property called arg
to send your arguments to the server.
We have decided this way because of our long experience with Meteor projects and as these calls are creating contracts (APIs) between the client and the server clear named objects are better in the long term than positional arguments. This will make your Meteor project more reliable in the long term and easier to maintain.
useMethod
Return a method
function that is async. You can call it with a method name and an argument.
Example:
1import { useMethod } from 'meteor/quave:react-data'; 2 3export const Snapshot = () => { 4 const { method } = useMethod(); 5 6 const save = async () => { 7 const snapshot = await method('saveSnapshot', { 8 snapshot: { 9 _id: snapshotId, 10 items, 11 }, 12 }); 13 clear(); 14 }; 15 16 // continue to render... 17};
You can also provide options for useMethod
such as:
onError
: it's called with the error, only when it's not expected. It's useful when you want to log errors for example. The promise will also be rejected with this error right after this is invoked. One argument will be passed with the following properties:error
: in case of errorsmethodName
: the method namearg
: the arg sent to the method
onSuccess
: it's called with the result of your method when it finishes without errors. The promise will also be resolved with this result right after this is invoked. One argument will be passed with the following properties:result
: the response of the methodmethodName
: the method namearg
: the arg sent to the method
onExpectedError
: it's called with the error, only when it's expected. Two arguments will be passed with the following properties:- first: the
expectedReason
, usually a String, of the error when provided in anEXPECTED_ERROR
. Otherwise, you can also set a custom default text withQuaveReactData.setDefaultExpectedErrorReason
method expected errors or it will passUnknown error
text. - second: is an object with the following properties:
error
: the error objectmethodName
: the method namearg
: the arg sent to the method
openAlert
(deprecated): it will work because onExpectedError has the same behavior, so if will provide openAlert option we will consider it the same as onExpectedError, preferonExpectedError
to avoid breaking changes in the future.
- first: the
onFinally
: it's called after any of the other callbacks are called. One argument will be passed with the following properties:result
: the response of the method when success, can be undefinederror
: the error object, can be undefinedexpectedReason
: in case of expected errors, can be undefinedmethodName
: the method namearg
: the arg sent to the method
useData
Subscribe to a publication and find the data.
Example:
1import { useData } from 'meteor/quave:react-data'; 2import { useLoggedUser } from 'meteor/quave:logged-user-react'; 3 4export const Home = () => { 5 const { loggedUser } = useLoggedUser(); 6 7 const { data: snapshots, loading } = useData({ 8 publicationName: 'mySnapshots', 9 skip: !loggedUser, 10 find: () => SnapshotsCollection.find({}, { sort: { at: -1 } }), 11 }); 12 13 // continue to render... 14};
A more complex example:
1import { useData } from 'meteor/quave:react-data'; 2 3export const Snapshot = () => { 4 const navigate = useNavigate(); 5 const snapshotId = useParams()[RouteParams.SNAPSHOT_ID]; 6 7 const { data: snapshotItems } = useData({ 8 publicationName: 'mySnapshots', 9 arg: { snapshotId }, 10 skip: !snapshotId, 11 deps: [snapshotId], 12 dataReturnWhenLoading: [], 13 find: () => 14 SnapshotItemsCollection.find({ snapshotId }, { sort: { at: -1 } }), 15 }); 16 17 // continue to render... 18};
shouldSkip
property is also available, it works like skip, but it is a function instead of a static property.
useDataSubscribe
Subscribe to a publication but without finding the data.
Example:
1import { useDataSubscribe } from 'meteor/quave:react-data'; 2import { useLoggedUser } from 'meteor/quave:logged-user-react'; 3 4export const Home = () => { 5 const { loggedUser } = useLoggedUser(); 6 7 const { loading } = useDataSubscribe({ 8 publicationName: 'mySnapshots', 9 skip: !loggedUser, 10 }); 11 12 // continue to render... 13 // and in other component you can find the data 14 // (see more hooks below) 15};
useFindData
Find data from a collection that is already published to Minimongo.
Example:
1import { useFindData } from 'meteor/quave:react-data'; 2 3const Chats = ({ isPrivate }) => { 4 const chats = useFindData({ 5 skip: !isPrivate, 6 deps: [isPrivate], 7 find: () => ChatsCollection.find({ isPrivate: !!isPrivate }), 8 }); 9 10 // chats is an array of ChatsCollection documents 11 // continue to render... 12};
useFindOneData
Find data from a collection that is already published to Minimongo but only
the first one, like a findOne
.
Example:
1import { useFindOneData } from 'meteor/quave:react-data'; 2 3const Chat = ({ isPrivate }) => { 4 const chat = useFindOneData({ 5 skip: !isPrivate, 6 deps: [isPrivate], 7 find: () => ChatsCollection.find({ isPrivate: !!isPrivate }), 8 }); 9 10 // chats is just one document of ChatsCollection documents 11 // see that the find is still a find as we need to create a cursor 12 // continue to render... 13};
Extra Features
EXPECTED_ERROR
When you call a method and the server returns an error, we check if the
error is an expected error. To throw this error in the server you can use
this constant EXPECTED_ERROR
. It is exported from the package.
then you need to throw a Meteor.Error
where the first argument is
EXPECTED_ERROR
and the second argument is the reason for the error,
usually a friendly error message for the final user.
Example:
1import { EXPECTED_ERROR } from 'meteor/quave:react-data'; 2 3Meteor.methods({ 4 updateMyProfile({ profileData }) { 5 if (!profileData.email) { 6 throw new Meteor.Error( 7 EXPECTED_ERROR, 8 'Email field is required to update your profile' 9 ); 10 } 11 }, 12});
meteorCallPromisified
To avoid workarounds and non-standard way to call simple methods when you don't have a React component.
It implements just a Meteor.call
wrapped in a Promise to get result or error.
setGetAdditionalArgsFunction
In some cases is nice to inject some argument in all the method calls and subscribes, for example, providing the language from the client or timezone.
We export a method called setGetAdditionalArgsFunction
so you can provide additional args for all the calls and subscribe in a single place.
Example:
1import React from 'react'; 2import { Meteor } from 'meteor/meteor'; 3import { createRoot } from 'react-dom/client'; 4import { App } from '../app/general/App'; 5import { setGetAdditionalArgsFunction } from 'meteor/quave:react-data'; 6import { getLanguage } from '../imports/infra/languages'; 7 8setGetAdditionalArgsFunction(() => { 9 const language = getLanguage(); 10 return { filter: { language } }; 11}); 12 13Meteor.startup(() => { 14 const root = createRoot(document.getElementById('app')); 15 root.render(<App />); 16});
Available Imports
To understand which named exports are available, check the client.js and server.js files.
License
MIT