From f74585a30541ece25269925807921c16a8673ab8 Mon Sep 17 00:00:00 2001 From: darko-mijic Date: Thu, 4 Aug 2016 09:29:25 +0200 Subject: [PATCH 1/5] Introduce stopping subscriptions and livequery support --- autorun.js | 37 +++++++++++++++++++++++++++++++++++++ index.js | 38 ++++---------------------------------- observe-cursor.js | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 34 deletions(-) create mode 100644 autorun.js create mode 100644 observe-cursor.js diff --git a/autorun.js b/autorun.js new file mode 100644 index 0000000..797943f --- /dev/null +++ b/autorun.js @@ -0,0 +1,37 @@ +import { Tracker } from 'meteor/tracker'; +import { autorun } from 'mobx'; + +export default (trackerMobxAutorun) => { + let mobxDisposer = null; + let computation = null; + let hasBeenStarted; + let subscriptionHandle; + return { + start() { + let isFirstRun = true; + computation = Tracker.autorun(() => { + if (mobxDisposer) { + mobxDisposer(); + isFirstRun = true; + } + mobxDisposer = autorun(() => { + if (isFirstRun) { + subscriptionHandle = trackerMobxAutorun(); + } else { + computation.invalidate(); + subscriptionHandle && subscriptionHandle.stop(); + } + isFirstRun = false; + }); + }); + hasBeenStarted = true; + }, + stop() { + if (hasBeenStarted) { + computation.stop(); + mobxDisposer(); + subscriptionHandle && subscriptionHandle.stop(); + } + } + }; +}; 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..ee63c81 --- /dev/null +++ b/observe-cursor.js @@ -0,0 +1,38 @@ +import { Tracker } from 'meteor/tracker'; +import { action } from 'mobx'; + +export default (actionPrefix = '', storeAttribute, handle, cursor) => { + + if (handle.ready()) { + // initial fetch... + Tracker.nonreactive(() => { + action(`${actionPrefix}: initial fetch`, (comments) => { + storeAttribute.replace(comments); + })(cursor.fetch()); + }); + + // ...and then observe + cursor.observe({ + // we don't want that the addedAt function is triggered x times at the beginning + // just fetch them once (see above) + _suppress_initial: true, + addedAt: action(`${actionPrefix}: document added`, (document, atIndex) => { + storeAttribute.splice(atIndex, 0, document); + }), + changedAt: action(`${actionPrefix}: document changed`, (newDocument, oldDocument, atIndex) => { + storeAttribute.splice(atIndex, 1, newDocument); + }), + removedAt: action(`${actionPrefix}: document removed`, (oldDocument, atIndex) => { + storeAttribute.splice(atIndex, 1); + }), + movedTo: action(`${actionPrefix}: document moved`, (document, fromIndex, toIndex) => { + storeAttribute.splice(fromIndex, 1); + storeAttribute.splice(toIndex, 0, document); + }), + }); + } else { + action(`${actionPrefix}: initialized`, () => { + storeAttribute.replace([]); + })(); + } +}; From 5fb2431b335836bf559de2b29d1cec9800c03c68 Mon Sep 17 00:00:00 2001 From: darko-mijic Date: Thu, 4 Aug 2016 10:48:58 +0200 Subject: [PATCH 2/5] Remove unnecessary subscription stopping --- autorun.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/autorun.js b/autorun.js index 797943f..535ba50 100644 --- a/autorun.js +++ b/autorun.js @@ -5,7 +5,6 @@ export default (trackerMobxAutorun) => { let mobxDisposer = null; let computation = null; let hasBeenStarted; - let subscriptionHandle; return { start() { let isFirstRun = true; @@ -16,10 +15,9 @@ export default (trackerMobxAutorun) => { } mobxDisposer = autorun(() => { if (isFirstRun) { - subscriptionHandle = trackerMobxAutorun(); + trackerMobxAutorun(); } else { computation.invalidate(); - subscriptionHandle && subscriptionHandle.stop(); } isFirstRun = false; }); @@ -30,7 +28,6 @@ export default (trackerMobxAutorun) => { if (hasBeenStarted) { computation.stop(); mobxDisposer(); - subscriptionHandle && subscriptionHandle.stop(); } } }; From 42fa40d1c4252a83f72fc7bf904a31753f9f9be7 Mon Sep 17 00:00:00 2001 From: darkomijic Date: Thu, 18 Aug 2016 13:07:58 +0200 Subject: [PATCH 3/5] Clear observable cursor using dedicated mobx method --- observe-cursor.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/observe-cursor.js b/observe-cursor.js index ee63c81..5af9358 100644 --- a/observe-cursor.js +++ b/observe-cursor.js @@ -1,13 +1,13 @@ import { Tracker } from 'meteor/tracker'; import { action } from 'mobx'; -export default (actionPrefix = '', storeAttribute, handle, cursor) => { +export default (actionPrefix = '', observableArray, handle, cursor) => { if (handle.ready()) { // initial fetch... Tracker.nonreactive(() => { action(`${actionPrefix}: initial fetch`, (comments) => { - storeAttribute.replace(comments); + observableArray.replace(comments); })(cursor.fetch()); }); @@ -17,22 +17,22 @@ export default (actionPrefix = '', storeAttribute, handle, cursor) => { // just fetch them once (see above) _suppress_initial: true, addedAt: action(`${actionPrefix}: document added`, (document, atIndex) => { - storeAttribute.splice(atIndex, 0, document); + observableArray.splice(atIndex, 0, document); }), changedAt: action(`${actionPrefix}: document changed`, (newDocument, oldDocument, atIndex) => { - storeAttribute.splice(atIndex, 1, newDocument); + observableArray.splice(atIndex, 1, newDocument); }), removedAt: action(`${actionPrefix}: document removed`, (oldDocument, atIndex) => { - storeAttribute.splice(atIndex, 1); + observableArray.splice(atIndex, 1); }), movedTo: action(`${actionPrefix}: document moved`, (document, fromIndex, toIndex) => { - storeAttribute.splice(fromIndex, 1); - storeAttribute.splice(toIndex, 0, document); + observableArray.splice(fromIndex, 1); + observableArray.splice(toIndex, 0, document); }), }); } else { action(`${actionPrefix}: initialized`, () => { - storeAttribute.replace([]); + observableArray.clear(); })(); } }; From 7d5f0e171f38f4b0a650e0773e9620b8daa453de Mon Sep 17 00:00:00 2001 From: darkomijic Date: Thu, 18 Aug 2016 13:36:05 +0200 Subject: [PATCH 4/5] Improve comments --- observe-cursor.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/observe-cursor.js b/observe-cursor.js index 5af9358..8fa64cc 100644 --- a/observe-cursor.js +++ b/observe-cursor.js @@ -4,17 +4,16 @@ import { action } from 'mobx'; export default (actionPrefix = '', observableArray, handle, cursor) => { if (handle.ready()) { - // initial fetch... + // initially fetch all documents and store them in the observable array Tracker.nonreactive(() => { action(`${actionPrefix}: initial fetch`, (comments) => { observableArray.replace(comments); })(cursor.fetch()); }); - // ...and then observe + // observe changes after initial fetch cursor.observe({ - // we don't want that the addedAt function is triggered x times at the beginning - // just fetch them once (see above) + // _suppress_initial suppresses addedAt callback for documents initially fetched _suppress_initial: true, addedAt: action(`${actionPrefix}: document added`, (document, atIndex) => { observableArray.splice(atIndex, 0, document); From 87a7f7f4e30b3370e4600c0473c3c9974827c1e3 Mon Sep 17 00:00:00 2001 From: darkomijic Date: Thu, 18 Aug 2016 13:58:30 +0200 Subject: [PATCH 5/5] Update readme to new api --- README.md | 76 +++++++++++++++---------------------------------------- 1 file changed, 20 insertions(+), 56 deletions(-) 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(); ```