Skip to content

useFind and useSubscribe #332

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Dec 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/react-meteor-data/.meteorignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
package.json
package-lock-json
63 changes: 31 additions & 32 deletions packages/react-meteor-data/.versions
Original file line number Diff line number Diff line change
@@ -1,57 +1,56 @@
[email protected]
babel-compiler@7.6.1
babel-compiler@7.7.0
[email protected]
[email protected]
[email protected]
blaze@2.3.4
blaze@2.5.0
[email protected]
callback-hook@1.3.0
callback-hook@1.4.0
[email protected]
[email protected]
ddp-client@2.4.0
ddp-client@2.5.0
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
dynamic-import@0.6.0
ecmascript@0.15.1
ecmascript-runtime@0.7.0
ecmascript-runtime-client@0.11.0
ecmascript-runtime-server@0.10.0
dynamic-import@0.7.2
ecmascript@0.16.0
ecmascript-runtime@0.8.0
ecmascript-runtime-client@0.12.1
ecmascript-runtime-server@0.11.0
[email protected]
[email protected]
[email protected]
htmljs@1.0.11
[email protected].0
htmljs@1.1.1
[email protected].1
[email protected]
local-test:react-meteor-data@2.3.3
logging@1.2.0
meteor@1.9.3
minimongo@1.6.2
[email protected].5
modules@0.16.0
local-test:react-meteor-data@2.4.0
logging@1.3.1
meteor@1.10.0
minimongo@1.7.0
[email protected].7
modules@0.17.0
[email protected]
mongo@1.11.0
mongo@1.13.0
[email protected]
[email protected]
[email protected].7
[email protected].0
[email protected].16
[email protected].8
[email protected].1
[email protected].19
[email protected]
promise@0.11.2
promise@0.12.0
[email protected]
react-fast-refresh@0.1.0
react-meteor-data@2.3.3
react-fast-refresh@0.2.0
react-meteor-data@2.4.0
[email protected]
[email protected]
[email protected]
[email protected]
[email protected].0
socket-stream-client@0.3.1
test-helpers@1.2.0
tinytest@1.1.0
[email protected].1
socket-stream-client@0.4.0
test-helpers@1.3.0
tinytest@1.2.0
[email protected]
typescript@4.2.2
typescript@4.4.0
[email protected]
webapp@1.10.1
webapp@1.13.0
[email protected]
3 changes: 3 additions & 0 deletions packages/react-meteor-data/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# CHANGELOG
## v2.4.0, 2021-12-02
* Added `useSubscribe` and `useFind` hooks

## v2.3.3, 2021-07-14
* Fixes a publication issue in v2.3.2

Expand Down
73 changes: 73 additions & 0 deletions packages/react-meteor-data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,79 @@ export default withTracker({
})(Foo);
```

#### `useSubscribe(subName, ...args)` A convenient wrapper for subscriptions

`useSubscribe` is a convenient short hand for setting up a subscription. It is particularly useful when working with `useFind`, which should NOT be used for setting up subscriptions. At its core, it is a very simple wrapper around `useTracker` (with no deps) to create the subscription in a safe way, and allows you to avoid some of the cerimony around defining a factory and defining deps. Just pass the name of your subscription, and your arguments.

`useSubscribe` returns an `isLoading` function. You can call `isLoading()` to react to changes in the subscription's loading state. The `isLoading` function will both return the loading state of the subscription, and set up a reactivity for the loading state change. If you don't call this function, no re-render will occur when the loading state changes.

```jsx
// Note: isLoading is a function!
const isLoading = useSubscribe('posts', groupId);
const posts = useFind(() => Posts.find({ groupId }), [groupId]);

if (isLoading()) {
return <Loading />
} else {
return <ul>
{posts.map(post => <li key={post._id}>{post.title}</li>)}
</ul>
}
```

If you want to conditionally subscribe, you can set the `name` field (the first argument) to a falsy value to bypass the subscription.

```jsx
const needsData = false;
const isLoading = useSubscribe(needsData ? "my-pub" : null);

// When a subscription is not used, isLoading() will always return false
```

#### `useFind(cursorFactory, deps)` Accellerate your lists

The `useFind` hook can substantially speed up the rendering (and rerendering) of lists coming from mongo queries (subscriptions). It does this by controlling document object references. By providing a highly tailored cursor management within the hook, using the `Cursor.observe` API, `useFind` carefully updates only the object references changed during a DDP update. This approach allows a tighter use of core React tools and philosophies to turbo charge your list renders. It is a very different approach from the more general purpose `useTracker`, and it requires a bit more set up. A notable difference is that you should NOT call `.fetch()`. `useFind` requires its factory to return a `Mongo.Cursor` object. You may also return `null`, if you want to conditionally set up the Cursor.

Here is an example in code:

```jsx
import React, { memo } from 'react'
import { useFind } from 'meteor/react-meteor-data'
import TestDocs from '/imports/api/collections/TestDocs'

// Memoize the list item
const ListItem = memo(({doc}) => {
return (
<li>{doc.id},{doc.updated}</li>
)
})

const Test = () => {
const docs = useFind(() => TestDocs.find(), [])
return (
<ul>
{docs.map(doc =>
<ListItem key={doc.id} doc={doc} />
)}
</ul>
)
}

// Later on, update a single document - notice only that single component is updated in the DOM
TestDocs.update({ id: 2 }, { $inc: { someProp: 1 } })
```

If you want to conditionally call the find method based on some props configuration or anything else, return `null` from the factory.

```jsx
const docs = useFind(() => {
if (props.skip) {
return null
}
return TestDocs.find()
}, [])
```

### Concurrent Mode, Suspense and Error Boundaries

There are some additional considerations to keep in mind when using Concurrent Mode, Suspense and Error Boundaries, as each of these can cause React to cancel and discard (toss) a render, including the result of the first run of your reactive function. One of the things React developers often stress is that we should not create "side-effects" directly in the render method or in functional components. There are a number of good reasons for this, including allowing the React runtime to cancel renders. Limiting the use of side-effects allows features such as concurrent mode, suspense and error boundaries to work deterministically, without leaking memory or creating rogue processes. Care should be taken to avoid side effects in your reactive function for these reasons. (Note: this caution does not apply to Meteor specific side-effects like subscriptions, since those will be automatically cleaned up when `useTracker`'s computation is disposed.)
Expand Down
2 changes: 2 additions & 0 deletions packages/react-meteor-data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ if (Meteor.isDevelopment) {

export { default as useTracker } from './useTracker';
export { default as withTracker } from './withTracker.tsx';
export { useFind } from './useFind';
export { useSubscribe } from './useSubscribe';
Loading