Skip to content

Instantly share code, notes, and snippets.

@flowstate
Created September 14, 2018 01:29
Show Gist options
  • Save flowstate/09661219199553339e966d0c1e7000bc to your computer and use it in GitHub Desktop.
Save flowstate/09661219199553339e966d0c1e7000bc to your computer and use it in GitHub Desktop.
MST / FireStore syncing approach
import { Person } from "./PersonStore";
const Message = t.model("MessageModel", {
id: t.identifier,
author: t.late(() => t.reference(Person)),
body: t.string,
title: t.string,
toPeople: t.array(t.late(() => t.reference(Person))),
read: false
})
const MessageStore = t.model("MessageStore", {
messages: t.map(Message), {},
isLoading: true
})
.views(self => ({
get fs(){
return getParent(self).firestore
}
}))
.actions(self => {
function afterAttach() {
self.fs().collection('messages').onSnapshot(self.handleCollectionSnapshot);
}
function handleCollectionSnapshot(snapshot) {
snapshot.docChanges().forEach((change) => {
const data = change.doc.data();
data.id = change.doc.id;
switch (change.type) {
case 'added':
case 'modified':
self.messages.put(data);
break;
case 'removed':
const doomed = self.messages.get(data.id);
destroy(doomed);
break;
default:
break;
}
});
}
function setLoading(loading) {
self.isLoading = loading
}
const addMessage = flow(function* addMessage(msg) {
try {
const messageRef = self.fs().collection('messages').doc();
msg.id = messageRef.id;
const writeResult = yield self.fs().collection('people').doc(person.id).set(msg);
return { wasSuccessful: () => true, message: `message ${msg.title} was added to the platform.` };
} catch (e) {
console.error(e);
return { wasSuccessful: () => false, message: `error adding message: ${e.message}` };
}
});
return {
setLoading,
loadMessages,
addMessage,
removeMessage,
}
}));
export function LateMessage(){return Message};
export { Message, MessageStore };
import {Message} from './MessageStore'
const Person = types
.model('Person', {
id: types.identifier,
fullname: types.string,
messages: types.array(types.reference(types.late(() => Message)))
})
.actions((self) => ({
addMessage(msg) {
self.messages.push(msg);
// APPROACH 1: updating here seems really expensive and bad.
self.store.updatePerson(self)
},
removeMessage(msg) {
self.messages.remove(msg);
// because I have to do it everywhere
self.store.updatePerson(self)
},
}));
const PersonStore = types
.model('PersonStore', {
isLoading: true,
people: types.map(Person)
})
.views(self => ({
get fs(){
return getParent(self).firestore
}
}))
.actions((self) => {
function afterAttach() {
// APPROACH 2: listener. This is where a ton of doubling comes from, but I don't know how else to get live updates
self.fs().collection('people').onSnapshot(self.handleCollectionSnapshot);
}
function setLoading(loading) {
self.isLoading = loading;
}
function handleCollectionSnapshot(snapshot) {
snapshot.docChanges().forEach((change) => {
const data = change.doc.data();
data.id = change.doc.id;
switch (change.type) {
case 'added':
self.people.put(data);
break;
case 'modified':
// this is bad
// and writing a 'merge' seems ... brittle?
self.people.put(data);
break;
case 'removed':
const doomed = self.people.get(data.id);
destroy(doomed);
break;
default:
break;
}
});
}
const loadFromFireStore = flow(function* loadFromFireStore() {
try {
setLoading(true);
const collectionSnap = yield self.fs().collection('people').get();
collectionSnap.forEach((doc) => {
const data = doc.data();
const id = doc.id;
data.id = id;
self.people.put(data);
});
} catch (err) {
console.error('failed to load people', err);
} finally {
setLoading(false);
}
});
// of course, if I call this, I also get a hit on the update from my listener
const addPerson = flow(function* addPerson(person) {
try {
const personRef = self.fs().collection('people').doc();
person.id = personRef.id;
const writeResult = yield self.fs().collection('people').doc(person.id).set(person);
return { wasSuccessful: () => true, message: `person ${person.fullname} was added to the platform.` };
} catch (e) {
console.error(e);
return { wasSuccessful: () => false, message: `error adding person: ${e.message}` };
}
});
const updatePerson = flow(function* updatePerson(person) {
try {
const out = yield self.fs().collection('people').doc(person.id).set(person, { merge: true });
} catch (e) {
console.error(e);
}
});
return {
afterAttach,
addPerson,
handleCollectionSnapshot,
loadFromFireStore,
updatePerson,
};
});
import { getEnv, types as t, flow } from "mobx-state-tree";
import { MessageStore } from './MessageStore';
import { PersonStore } from './PersonStore';
const PhaseStore = t
.model("PhaseStore", {
personStore: t.optional(PersonStore, { people: {} }),
messageStore: t.optional(MessageStore, { posts: {} }),
})
.actions(self => {
function afterCreate() {
self.loadAll()
}
// USAGE PROBLEM: this ends up blowing out stack depth due to the nested calls to update each person
function sendMessage(msg) {
const message = self.messageStore.addMessage(msg)
msg.author.addMessage(message)
message.isToPeople && message.toPeople.forEach(person => {
person.addMessage(message)
})
return {wasSuccessful: () => true}
}
return {
afterCreate,
sendMessage
}
});
export default PhaseStore;
@jonesnc
Copy link

jonesnc commented Sep 19, 2018

You might be able to prevent the doubling issue by looking at doc.metadata.hasPendingWrites.

https://firebase.google.com/docs/firestore/query-data/listen

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment