Skip to content
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

Expose useTracker hook and reimplement withTracker HOC on top of it #262

Closed
wants to merge 28 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f9c6ae3
Expose useTracker hook and reimplement withTracker HOC on top of it
yched Nov 4, 2018
eecab69
run the callback non-reactively in the initial render
yched Nov 5, 2018
46c243a
no need for useCallback
yched Nov 5, 2018
a2cc120
setup computation once on mount, deferring subscriptions until didMount
yched Nov 6, 2018
b61f809
stop deferred inital subscriptions on computation invalidate rather t…
yched Nov 8, 2018
2589ac7
Revert last two commits: no reactivity on mount
yched Nov 9, 2018
c8c1c5f
comments
yched Nov 11, 2018
24be369
minor: code style
yched Feb 15, 2019
e509e26
minor: coding style
yched Mar 30, 2019
5d25bf2
Handle Mongo.Cursor warnings checks inside useTracker, and user React…
yched Mar 30, 2019
ae3f323
Add a note about refs BC break and #262
yched Mar 30, 2019
3caa109
bump React minimum version to 16.8
yched Mar 30, 2019
61ef31e
minor: streamline comments
yched Mar 31, 2019
65e619d
minor: streamline useEffect
yched Mar 31, 2019
3841a4c
minor: streamline client / server versions of useTracker
yched Mar 31, 2019
ef64751
default useTracker's deps param to [] to avoid infinte rerender loop …
yched Mar 31, 2019
4486531
Docs
yched Mar 30, 2019
0041ce7
minor: linebreak fix
yched Mar 31, 2019
92f8576
docs : unify headers for useTracker / withTracker sections (include a…
yched Mar 31, 2019
d8922dd
docs : typos
yched Apr 1, 2019
c4e24f2
remove createContainer / ReactMeteorData
yched Apr 1, 2019
b7a92d6
docs : added compatibility notes
yched Apr 1, 2019
c135ef0
docs : adjust formatting / fix unfinished sentence / link to React li…
yched Apr 1, 2019
e79b596
docs : better doc for the react-hooks/exhaustive-deps ESLint config
yched Apr 3, 2019
17322a5
remove dependency on tmeasday:check-npm-versions
yched Apr 3, 2019
f0a0328
optim : only warn about Mongo.Cursor in dev environment
yched Apr 3, 2019
eb55a16
forward references to the inner component (supercedes #266)
yched Apr 3, 2019
514a87d
fix checkCursor() when reactiveFn returns null
yched Apr 23, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
111 changes: 90 additions & 21 deletions packages/react-meteor-data/README.md
Expand Up @@ -18,42 +18,111 @@ npm install --save react

### Usage

This package exports a symbol `withTracker`, which you can use to wrap your components with data returned from Tracker reactive functions.
This package provides two ways to use Tracker reactive data in your React components:
- a hook: `useTracker` (v2 only, requires React `^16.8`)
- a higher-order component (HOC): `withTracker` (v1 and v2).

The `useTracker` hook, introduced in version 2.0.0, is slightly more straightforward to use (lets you access reactive data sources directly within your componenent, rather than adding them from an external wrapper), and slightly more performant (avoids adding wrapper layers in the React tree). But, like all React hooks, it can only be used in function components, not in class components.
The `withTracker` HOC can be used with all components, function or class.

It is not necessary to rewrite existing applications to use the `useTracker` hook instead of the existing `withTracker` HOC. But for new components, it is suggested to prefer the `useTracker` hook when dealing with function components.

#### `useTracker(reactiveFn, deps)` hook

You can use the `useTracker` hook to get the value of a Tracker reactive function in your (function) components. The reactive function will get re-run whenever its reactive inputs change, and the component will re-render with the new value.

Arguments:
- `reactiveFn`: a Tracker reactive function (with no parameters)
- `deps`: an array of "dependencies" of the reactive function, i.e. the list of values that, when changed, need to stop the current Tracker computation and start a new one - for example, the value of a prop used in a subscription or a Minimongo query; see example below. This array typically includes all variables from the outer scope "captured" in the closure passed as the 1st argument. This is very similar to how the `deps` argument for [React's built-in `useEffect`, `useCallback` or `useMemo` hooks](https://reactjs.org/docs/hooks-reference.html) work.
If omitted, it defaults to `[]` (no dependency), and the Tracker computation will run unchanged until the component is unmounted.

```js
import { useTracker } from 'meteor/react-meteor-data';

// React function component.
function Foo({ listId }) {
// This computation uses no value from the outer scope,
// and thus does not needs to pass a 'deps' argument (same as passing []).
const currentUser = useTracker(() => Meteor.user());
// The following two computations both depend on the 'listId' prop,
// and thus need to specify it in the 'deps' argument,
// in order to subscribe to the expected 'todoList' subscription
// or fetch the expected Tasks when the 'listId' prop changes.
const listLoading = useTracker(() => {
// Note that this subscription will get cleaned up when your component is unmounted.
const handle = Meteor.subscribe('todoList', listId);
return !handle.ready();
}, [listId]);
const tasks = useTracker(() => Tasks.find({ listId }).fetch(), [listId]);

return (
<h1>Hello {currentUser.username}</h1>
{listLoading ?
<div>Loading</div> :
<div>
Here is the Todo list {listId}:
<ul>{tasks.map(task => <li key={task._id}>{task.label}</li>)}</ul>
</div}
);
}
```

**Note:** the [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks) package provides ESLint hints to help detect missing values in the `deps` argument of React built-in hooks. It can be configured to also validate the `deps` argument of the `useTracker` hook or some other hooks, with the following `eslintrc` config:

```
"react-hooks/exhaustive-deps": ["warn", { "additionalHooks": "useTracker|useSomeOtherHook|..." }]
```

#### `withTracker(reactiveFn)` higher-order component

You can use the `withTracker` HOC to wrap your components and pass them additional props values from a Tracker reactive function. The reactive function will get re-run whenever its reactive inputs change, and the wrapped component will re-render with the new values for the additional props.

Arguments:
- `reactiveFn`: a Tracker reactive function, getting the props as a parameter, and returning an object of additional props to pass to the wrapped component.

```js
import { withTracker } from 'meteor/react-meteor-data';

// React component
function Foo({ currentUser, listLoading, tasks }) {
// ...
// React component (function or class).
function Foo({ listId, currentUser, listLoading, tasks }) {
return (
<h1>Hello {currentUser.username}</h1>
{listLoading ?
<div>Loading</div> :
<div>
Here is the Todo list {listId}:
<ul>{tasks.map(task => <li key={task._id}>{task.label}</li>)}</ul>
</div}
);
}

export default withTracker(props => {
// Do all your reactive data access in this method.
export default withTracker(({ listId }) => {
// Do all your reactive data access in this function.
// Note that this subscription will get cleaned up when your component is unmounted
const handle = Meteor.subscribe('todoList', props.id);
const handle = Meteor.subscribe('todoList', listId);

return {
currentUser: Meteor.user(),
listLoading: !handle.ready(),
tasks: Tasks.find({ listId: props.id }).fetch(),
tasks: Tasks.find({ listId }).fetch(),
};
})(Foo);
```
The first argument to `withTracker` is a reactive function that will get re-run whenever its reactive inputs change.
yched marked this conversation as resolved.
Show resolved Hide resolved

The returned component will, when rendered, render `Foo` (the "lower-order" component) with its provided `props` in addition to the result of the reactive function. So `Foo` will receive `FooContainer`'s `props` as well as `{currentUser, listLoading, tasks}`.
The returned component will, when rendered, render `Foo` (the "lower-order" component) with its provided props in addition to the result of the reactive function. So `Foo` will receive `{ listId }` (provided by its parent) as well as `{ currentUser, listLoading, tasks }` (added by the `withTracker` HOC).

For more information, see the [React article](http://guide.meteor.com/react.html) in the Meteor Guide.

### Note on `withTracker` and `createContainer`

The new `withTracker` function replaces `createContainer` (however it remains for backwards compatibility). For `createContainer` usage, please [see prior documentation](https://github.com/meteor/react-packages/blob/ac251a6d6c2d0ddc22daad36a7484ef04b11862e/packages/react-meteor-data/README.md). The purpose of the new function is to better allow for container composability. For example when combining Meteor data with Redux and GraphQL:

```js
const FooWithAllTheThings = compose(
connect(...), // some Redux
graphql(...), // some GraphQL
withTracker(...), // some Tracker data
)(Foo);
```
### Version compatibility notes

- `react-meteor-data` v2.x :
- `useTracker` hook + `withTracker` HOC
- Requires React `^16.8`.
- Implementation is compatible with the forthcoming "React Suspense" features.
- The `withTracker` HOC is strictly backwards-compatible with the one provided in v1.x, the major version number is only motivated by the bump of React version requirement.
Provided they use a compatible React version, existing Meteor apps leveraging the `withTracker` HOC can freely upgrade from v1.x to v2.x, and gain compatibility with future React versions.

- `react-meteor-data` v1.x / v0.x :
- `withTracker` HOC (+ `createContainer`, kept for backwards compatibility with early v0.x releases)
- Requires React `^15.3` or `^16.0`.
- Implementation relies on React lifecycle methods (`componentWillMount` / `componentWillUpdate`) that are [marked for deprecation in future React versions](https://reactjs.org/blog/2018/03/29/react-v-16-3.html#component-lifecycle-changes) ("React Suspense").
188 changes: 0 additions & 188 deletions packages/react-meteor-data/ReactMeteorData.jsx

This file was deleted.

21 changes: 0 additions & 21 deletions packages/react-meteor-data/createContainer.jsx

This file was deleted.

11 changes: 11 additions & 0 deletions packages/react-meteor-data/index.js
@@ -0,0 +1,11 @@
import React from 'react';

if (Meteor.isDevelopment) {
const v = React.version.split('.');
if (v[0] < 16 || v[1] < 8) {
console.warn('react-meteor-data 2.x requires React version >= 16.8.');
}
}

export { default as withTracker } from './withTracker.jsx';
export { default as useTracker } from './useTracker.js';
5 changes: 1 addition & 4 deletions packages/react-meteor-data/package.js
Expand Up @@ -10,9 +10,6 @@ Package.onUse(function (api) {
api.versionsFrom('1.3');
api.use('tracker');
api.use('ecmascript');
api.use('tmeasday:check-npm-versions@0.3.2');

api.export(['ReactMeteorData']);

api.mainModule('react-meteor-data.jsx');
api.mainModule('index.js');
});
9 changes: 0 additions & 9 deletions packages/react-meteor-data/react-meteor-data.jsx

This file was deleted.