diff --git a/README.md b/README.md index d2f8d4e..8a6312d 100644 --- a/README.md +++ b/README.md @@ -10,71 +10,35 @@ MobX and [Tracker](https://docs.meteor.com/api/tracker.html#Tracker-autorun) can ### Usage ```javascript -// my-store.js -import { observable } from 'mobx'; - -export default observable({ - selectedProjectId: null, - projectTodos: [] -}); - -// autorun/todos.js -import state from '../my-store'; -import * as Collections from '../../infrastructure/collections'; import { Meteor } from 'meteor/meteor'; +import { observable, useStrict } from 'mobx'; +import autorun, { observe } from 'meteor/space:tracker-mobx-autorun'; -export default () => { - const projectId = state.selectedProjectId; - Meteor.subscribe('todos', {projectId}); - // MobX ObservableArray.replace() needs to be used instad of direct assignment - // to keep observable array instance and prevent re-rendering of the whole array - state.projectTodos.replace(Collections.Todos.find({projectId}).fetch()); -}; - -// index.js -import autorun from 'meteor/space:tracker-mobx-autorun'; -import todos from './autorun/todos'; +// Optionally MobX strict mode makes state in the store immutable, in that case +// state can ony be changed by MobX actions +useStrict(true); -export const todosAutorun = autorun(todos); +const Todos = new Mongo.Collection('todos'); -Meteor.startup(function() { - if (Meteor.isClient) { - todosAutorun.start(); - } +const state = observable({ + selectedProjectId: null, +  projectTodos: [] }); -``` - -### Usage with MobX _strict mode_ -Using MobX [actions](https://mobxjs.github.io/mobx/refguide/action.html) -is mandatory when strict mode is enabled. - -```javascript -// index.js - enabling MobX strict mode -import { useStrict } from 'mobx'; -useStrict(true); - -// actions/projects.js -import state from '../store'; -import { action } from 'mobx'; +const syncProjectTodos = autorun(() => { +  const projectId = state.selectedProjectId; +  const handle = Meteor.subscribe('todos', { projectId }); + const cursor = Todos.find({ projectId }); + observe('todosAutorun', state.projectTodos, handle, cursor); +}); -export const selectProject = action('selectProject', (projectId) => { - state.selectedProjectId = projectId; +// Starting autorun on startup or when needed +Meteor.startup(() => { +  if (Meteor.isClient) syncProjectTodos().start(); }); -// autorun/todos.js -import state from '../my-store'; -import * as Collections from '../../infrastructure/collections'; -import { Meteor } from 'meteor/meteor'; -import { action } from 'mobx'; - -export default () => { - const projectId = state.selectedProjectId; - Meteor.subscribe('todos', {projectId}); - action('updateTodosFromAutorun', (todos) => { - state.projectTodos.replace(todos); - })(projectId ? Collections.Todos.find({projectId}).fetch() : []); -}; +// Stopping autorun +syncProjectTodos().stop(); ``` diff --git a/autorun.js b/autorun.js new file mode 100644 index 0000000..535ba50 --- /dev/null +++ b/autorun.js @@ -0,0 +1,34 @@ +import { Tracker } from 'meteor/tracker'; +import { autorun } from 'mobx'; + +export default (trackerMobxAutorun) => { + let mobxDisposer = null; + let computation = null; + let hasBeenStarted; + return { + start() { + let isFirstRun = true; + computation = Tracker.autorun(() => { + if (mobxDisposer) { + mobxDisposer(); + isFirstRun = true; + } + mobxDisposer = autorun(() => { + if (isFirstRun) { + trackerMobxAutorun(); + } else { + computation.invalidate(); + } + isFirstRun = false; + }); + }); + hasBeenStarted = true; + }, + stop() { + if (hasBeenStarted) { + computation.stop(); + mobxDisposer(); + } + } + }; +}; diff --git a/index.js b/index.js index 3700110..92b7e2b 100644 --- a/index.js +++ b/index.js @@ -1,40 +1,10 @@ -import { Tracker } from 'meteor/tracker'; import { checkNpmVersions } from 'meteor/tmeasday:check-npm-versions'; +import observeCursor from './observe-cursor'; +import autorun from './autorun'; checkNpmVersions({ 'mobx': '2.x' }, 'space:tracker-mobx-autorun'); -const { autorun } = require('mobx'); - -export default (trackerMobxAutorun) => { - let mobxDisposer = null; - let computation = null; - let hasBeenStarted; - return { - start() { - let isFirstRun = true; - computation = Tracker.autorun(() => { - if (mobxDisposer) { - mobxDisposer(); - isFirstRun = true; - } - mobxDisposer = autorun(() => { - if (isFirstRun) { - trackerMobxAutorun(); - } else { - computation.invalidate(); - } - isFirstRun = false; - }); - }); - hasBeenStarted = true; - }, - stop() { - if (hasBeenStarted) { - computation.stop(); - mobxDisposer(); - } - } - }; -}; +export default autorun; +export const observe = observeCursor; diff --git a/observe-cursor.js b/observe-cursor.js new file mode 100644 index 0000000..8fa64cc --- /dev/null +++ b/observe-cursor.js @@ -0,0 +1,37 @@ +import { Tracker } from 'meteor/tracker'; +import { action } from 'mobx'; + +export default (actionPrefix = '', observableArray, handle, cursor) => { + + if (handle.ready()) { + // initially fetch all documents and store them in the observable array + Tracker.nonreactive(() => { + action(`${actionPrefix}: initial fetch`, (comments) => { + observableArray.replace(comments); + })(cursor.fetch()); + }); + + // observe changes after initial fetch + cursor.observe({ + // _suppress_initial suppresses addedAt callback for documents initially fetched + _suppress_initial: true, + addedAt: action(`${actionPrefix}: document added`, (document, atIndex) => { + observableArray.splice(atIndex, 0, document); + }), + changedAt: action(`${actionPrefix}: document changed`, (newDocument, oldDocument, atIndex) => { + observableArray.splice(atIndex, 1, newDocument); + }), + removedAt: action(`${actionPrefix}: document removed`, (oldDocument, atIndex) => { + observableArray.splice(atIndex, 1); + }), + movedTo: action(`${actionPrefix}: document moved`, (document, fromIndex, toIndex) => { + observableArray.splice(fromIndex, 1); + observableArray.splice(toIndex, 0, document); + }), + }); + } else { + action(`${actionPrefix}: initialized`, () => { + observableArray.clear(); + })(); + } +};