UNPKG

@react-firebase/firestore

Version:

Efficiently Render & mutate Firestore data in your react(or react-native) app.

735 lines (713 loc) 35.2 kB
import { createElement, Fragment, Component, createContext } from 'react'; import { renderAndAddProps } from 'render-and-add-props'; import get from 'lodash/get'; import produce from 'immer'; import set from 'lodash/set'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ /* global Reflect, Promise */ var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __awaiter(thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } var _a; var firestoreDefaultContext = { listenTo: function (a) { }, stopListeningTo: function (path) { }, dataTree: {}, firebase: {}, firestore: null }; var FirestoreContextProvider = (_a = createContext(firestoreDefaultContext), _a.Provider), FirestoreContextConsumer = _a.Consumer; var FirestoreCollectionContextConsumerLifeCycle = (function (_super) { __extends(FirestoreCollectionContextConsumerLifeCycle, _super); function FirestoreCollectionContextConsumerLifeCycle() { return _super !== null && _super.apply(this, arguments) || this; } FirestoreCollectionContextConsumerLifeCycle.prototype.listenToNode = function () { var _a = this.props, dataTree = _a.dataTree, path = _a.path, listenTo = _a.listenTo; if (path === null) { return; } listenTo(this.props, "collection"); }; FirestoreCollectionContextConsumerLifeCycle.prototype.stopListeningToNode = function () { var _a = this.props, stopListeningTo = _a.stopListeningTo, path = _a.path, dataTree = _a.dataTree; if (path === null || path in dataTree) { return; } stopListeningTo(path); }; FirestoreCollectionContextConsumerLifeCycle.prototype.componentDidMount = function () { this.listenToNode(); }; FirestoreCollectionContextConsumerLifeCycle.prototype.shouldComponentUpdate = function (nextProps) { var propsThatCanChange = [ "path", "limit", "startAfter", "startAt", "endAt", "endBefore", "where" ]; for (var _i = 0, propsThatCanChange_1 = propsThatCanChange; _i < propsThatCanChange_1.length; _i++) { var propName = propsThatCanChange_1[_i]; if (this.props[propName] !== nextProps[propName]) { return true; } } return false; }; FirestoreCollectionContextConsumerLifeCycle.prototype.componentDidUpdate = function () { this.listenToNode(); }; FirestoreCollectionContextConsumerLifeCycle.prototype.componentWillUnmount = function () { this.stopListeningToNode(); }; FirestoreCollectionContextConsumerLifeCycle.prototype.render = function () { return null; }; return FirestoreCollectionContextConsumerLifeCycle; }(Component)); var FirestoreCollection = (function (_super) { __extends(FirestoreCollection, _super); function FirestoreCollection() { return _super !== null && _super.apply(this, arguments) || this; } FirestoreCollection.prototype.render = function () { var _this = this; var _a = this.props, children = _a.children, path = _a.path; if (path === null) { console.warn("path not provided to FirestoreNode ! Not rendering."); return null; } return (createElement(FirestoreContextConsumer, null, function (context) { return (createElement(Fragment, null, createElement(FirestoreCollectionContextConsumerLifeCycle, __assign({}, context, { path: path }, _this.props)), renderAndAddProps(children, { path: path, value: get(context, "dataTree[" + path + "].value", null), ids: get(context, "dataTree[" + path + "].ids", null), isLoading: get(context, "dataTree[" + path + "].isLoading", true) }))); })); }; return FirestoreCollection; }(Component)); var FirestoreDocumentContextConsumerLifeCycle = (function (_super) { __extends(FirestoreDocumentContextConsumerLifeCycle, _super); function FirestoreDocumentContextConsumerLifeCycle() { return _super !== null && _super.apply(this, arguments) || this; } FirestoreDocumentContextConsumerLifeCycle.prototype.listenToNodeIfNotInContext = function () { var _a = this.props, dataTree = _a.dataTree, path = _a.path, listenTo = _a.listenTo; if (path === null || path in dataTree) { return; } listenTo(this.props, "document"); }; FirestoreDocumentContextConsumerLifeCycle.prototype.stopListeningToNode = function () { var _a = this.props, stopListeningTo = _a.stopListeningTo, path = _a.path, dataTree = _a.dataTree; if (path === null || path in dataTree) { return; } stopListeningTo(path); }; FirestoreDocumentContextConsumerLifeCycle.prototype.componentDidMount = function () { this.listenToNodeIfNotInContext(); }; FirestoreDocumentContextConsumerLifeCycle.prototype.componentDidUpdate = function () { this.listenToNodeIfNotInContext(); }; FirestoreDocumentContextConsumerLifeCycle.prototype.componentWillUnmount = function () { this.stopListeningToNode(); }; FirestoreDocumentContextConsumerLifeCycle.prototype.render = function () { return null; }; return FirestoreDocumentContextConsumerLifeCycle; }(Component)); var FirestoreDocument = (function (_super) { __extends(FirestoreDocument, _super); function FirestoreDocument() { return _super !== null && _super.apply(this, arguments) || this; } FirestoreDocument.prototype.render = function () { var _this = this; var _a = this.props, children = _a.children, path = _a.path; if (path === null) { console.warn("path not provided to FirestoreNode ! Not rendering."); return null; } return (createElement(FirestoreContextConsumer, null, function (context) { return (createElement(Fragment, null, createElement(FirestoreDocumentContextConsumerLifeCycle, __assign({}, context, { path: path }, _this.props)), renderAndAddProps(children, { path: path, value: context.dataTree[path] && context.dataTree[path].value, isLoading: context.dataTree[path] && context.dataTree[path].isLoading }))); })); }; return FirestoreDocument; }(Component)); var initializeFirebaseApp = function (_a) { var firebase = _a.firebase, authDomain = _a.authDomain, databaseURL = _a.databaseURL, projectId = _a.projectId, storageBucket = _a.storageBucket, messagingSenderId = _a.messagingSenderId, apiKey = _a.apiKey, appId = _a.appId, measurementId = _a.measurementId; try { firebase.initializeApp({ apiKey: apiKey, authDomain: authDomain, databaseURL: databaseURL, projectId: projectId, storageBucket: storageBucket, messagingSenderId: messagingSenderId, appId: appId, measurementId: measurementId }); } catch (err) { if (err.code !== "app/duplicate-app") { throw err; } } }; var getFirestoreRefFromPath = function (_a) { var path = _a.path, firestore = _a.firestore; var firestoreRef = firestore; var chunkedPath = path .split("/") .filter(function (el) { return el && el.length > 0; }); for (var i = 0; i < chunkedPath.length; i += 1) { var currentChunk = chunkedPath[i]; if (i % 2 === 0) { var currentRef = firestoreRef; firestoreRef = currentRef.collection(currentChunk); } else { var currentRef = firestoreRef; firestoreRef = currentRef.doc(currentChunk); } } var leaf = firestoreRef; return leaf; }; var getFirestoreQuery = function (_a) { var _b = _a.firestore, firestore = _b === void 0 ? null : _b, _c = _a.path, path = _c === void 0 ? null : _c, _d = _a.where, where = _d === void 0 ? null : _d, _e = _a.orderBy, orderBy = _e === void 0 ? null : _e, _f = _a.limit, limit = _f === void 0 ? null : _f, _g = _a.startAt, startAt = _g === void 0 ? null : _g, _h = _a.endAt, endAt = _h === void 0 ? null : _h, _j = _a.startAfter, startAfter = _j === void 0 ? null : _j, _k = _a.endBefore, endBefore = _k === void 0 ? null : _k; if (firestore === null) { throw new Error("Need to supply firestore argument to getFirestoreQuery"); } if (path === null) { throw new Error("Need to supply path argument to getFirestoreQuery"); } var ref = getFirestoreRefFromPath({ path: path, firestore: firestore }); if (where !== null) { ref = ref.where(where.field, where.operator, where.value); } if (orderBy !== null) { for (var _i = 0, orderBy_1 = orderBy; _i < orderBy_1.length; _i++) { var currentOrderBy = orderBy_1[_i]; var field = currentOrderBy.field, type = currentOrderBy.type; ref = ref.orderBy(field, type); } } if (startAt !== null) { var startAtArray = Array.isArray(startAt) ? startAt : [startAt]; ref = ref.startAt.apply(ref, startAtArray); } if (startAfter !== null) { ref = ref.startAfter(startAfter); } if (endBefore !== null) { ref = ref.endBefore(endBefore); } if (endAt !== null) { ref = ref.endAt(endAt); } if (limit !== null) { ref = ref.limit(limit); } return ref; }; function stateReducer(state, actionArgs, operation) { switch (operation) { case "add": { return actions.addPathToData(state, actionArgs); } case "delete": { return actions.removePathFromData(state, actionArgs); } default: { throw new Error("Unsupported operation " + operation + ". \n Supported state reducer operations are 'add' & 'delete'."); } } } var actions = { addPathToData: function (state, _a) { var path = _a.path, newData = _a.value, ids = _a.ids, unsub = _a.unsub, isLoading = _a.isLoading, documentOrCollection = _a.documentOrCollection, snapshot = _a.snapshot; return produce(state, function (newState) { var snapshotID = get(snapshot, "id", null); set(newState, "__id", snapshotID); set(newData, "__id", snapshotID); if (documentOrCollection === "document") { set(newState, "dataTree." + path, { value: newData, id: snapshotID, unsub: unsub, isLoading: isLoading }); } else { set(newState, "dataTree." + path, { value: newData, ids: ids, unsub: unsub, isLoading: isLoading }); } return newState; }); }, removePathFromData: function (state, _a) { var path = _a.path; return produce(state, function (newState) { set(newState, "dataTree." + path, undefined); return newState; }); } }; var FirestoreProvider = (function (_super) { __extends(FirestoreProvider, _super); function FirestoreProvider(props) { var _this = _super.call(this, props) || this; _this.listenTo = function (firestoreQuery, documentOrCollection) { if (documentOrCollection === void 0) { documentOrCollection = "document"; } return __awaiter(_this, void 0, void 0, function () { var path, onNext, onError, ref, unsub; var _this = this; return __generator(this, function (_a) { path = firestoreQuery.path; if (path === null) { return [2]; } if (path in this.state.dataTree) { this.state.dataTree[path].unsub(); } onNext = function (snapshot) { if (documentOrCollection === "document") { var docSnapshot_1 = snapshot; _this.setState(function (state) { return stateReducer(state, { path: path, value: docSnapshot_1.exists ? docSnapshot_1.data() : null, documentOrCollection: documentOrCollection, snapshot: snapshot, unsub: unsub, isLoading: false }, "add"); }); } else { var collectionSnapshot_1 = snapshot; var docs_1 = collectionSnapshot_1.docs.map(function (docSnapshot) { return docSnapshot.exists ? docSnapshot.data() : null; }); var ids_1 = collectionSnapshot_1.docs.map(function (docSnapshot) { return docSnapshot.exists ? docSnapshot.id : null; }); _this.setState(function (state) { return stateReducer(state, { path: "" + path, value: docs_1, ids: ids_1, isLoading: false, documentOrCollection: documentOrCollection, unsub: unsub, snapshot: collectionSnapshot_1 }, "add"); }); return; } }; onError = function (err) { _this.setState(function (state) { console.error(err); return stateReducer(state, { unsub: function () { }, documentOrCollection: documentOrCollection, value: null, path: path, isLoading: false }, "add"); }); }; ref = getFirestoreQuery(Object.assign({}, firestoreQuery, { firestore: this.state.firestore })); unsub = ref.onSnapshot(onNext, onError); return [2]; }); }); }; if (props.apiKey) { initializeFirebaseApp(props); } var firestore = props.firebase.firestore(); _this.state = { firebase: props.firebase, firestore: props.firebase.app().firestore(), dataTree: {}, listenTo: _this.listenTo.bind(_this), stopListeningTo: _this.stopListeningTo.bind(_this) }; return _this; } FirestoreProvider.prototype.stopListeningTo = function (path) { if (!(path in this.state.dataTree)) { return; } this.state.dataTree[path].unsub(); this.setState(function (state) { return stateReducer(state, { path: path }, "delete"); }); }; FirestoreProvider.prototype.componentWillUnmount = function () { var dataTree = this.state.dataTree; Object.keys(dataTree).forEach(function (key) { return dataTree[key].unsub && dataTree[key].unsub(); }); }; FirestoreProvider.prototype.render = function () { var children = this.props.children; return (createElement(FirestoreContextProvider, { value: this.state }, renderAndAddProps(children, {}))); }; return FirestoreProvider; }(Component)); var FirestoreMutationWithContext = (function (_super) { __extends(FirestoreMutationWithContext, _super); function FirestoreMutationWithContext() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.createMutationRunner = function () { var _a = _this.props, type = _a.type, path = _a.path; var docReference = getFirestoreQuery(_this.props); switch (type) { case "set": { var runMutation = function (value, options) { if (options === void 0) { options = { merge: true }; } return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4, docReference.set(value, options).then(function (val) { return ({ response: val, path: path, value: value, type: type, key: null }); })]; case 1: return [2, _a.sent()]; } }); }); }; return runMutation; } case "update": { var runMutation = function (value) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4, docReference.update(value).then(function (val) { return ({ response: val, path: path, value: value, type: type, key: null }); })]; case 1: return [2, _a.sent()]; } }); }); }; return runMutation; } case "add": { var runMutation = function (value) { return __awaiter(_this, void 0, void 0, function () { var collectionRef, newDocRef; return __generator(this, function (_a) { switch (_a.label) { case 0: collectionRef = getFirestoreQuery(this.props); newDocRef = collectionRef.doc(); return [4, newDocRef.set(value).then(function (val) { return ({ response: val, path: path, value: value, type: type, key: newDocRef.id }); })]; case 1: return [2, _a.sent()]; } }); }); }; return runMutation; } case "delete": { var runMutation = function (value) { return __awaiter(_this, void 0, void 0, function () { var docRef; return __generator(this, function (_a) { switch (_a.label) { case 0: docRef = getFirestoreQuery(this.props); return [4, docRef.delete().then(function (val) { return ({ response: val, path: path, value: value, type: type, key: null }); })]; case 1: return [2, _a.sent()]; } }); }); }; return runMutation; } default: { throw new Error("Unsupported mutation type " + type + ". \nPlease file an issue if you'd like to see us support it !"); } } }; return _this; } FirestoreMutationWithContext.prototype.shouldComponentUpdate = function (nextProps) { return (nextProps.path !== this.props.path || nextProps.firebase !== this.props.firebase); }; FirestoreMutationWithContext.prototype.render = function () { var runMutation = this.createMutationRunner(); return renderAndAddProps(this.props.children, __assign({}, this.props, { runMutation: runMutation })); }; return FirestoreMutationWithContext; }(Component)); var FirestoreMutation = (function (_super) { __extends(FirestoreMutation, _super); function FirestoreMutation() { return _super !== null && _super.apply(this, arguments) || this; } FirestoreMutation.prototype.render = function () { var _a = this.props, children = _a.children, path = _a.path, type = _a.type; if (path === null) { console.warn("path not provided to FirestoreNode ! Not rendering."); return null; } return (createElement(FirestoreContextConsumer, null, function (context) { return (createElement(FirestoreMutationWithContext, __assign({ type: type, path: path }, context, { children: children }))); })); }; return FirestoreMutation; }(Component)); var FirestoreBatchedWriteWithContext = (function (_super) { __extends(FirestoreBatchedWriteWithContext, _super); function FirestoreBatchedWriteWithContext() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.createMutationBatch = function () { var firestore = _this.props.firestore; var batch = firestore.batch(); var addMutationToBatch = function (_a) { var path = _a.path, value = _a.value, type = _a.type; var ref = getFirestoreRefFromPath({ firestore: firestore, path: path }); batch[type](ref, value); }; var commit = function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4, batch.commit()]; case 1: _a.sent(); batch = firestore.batch(); return [2]; } }); }); }; return { addMutationToBatch: addMutationToBatch, commit: commit }; }; return _this; } FirestoreBatchedWriteWithContext.prototype.shouldComponentUpdate = function (nextProps) { return nextProps.firebase !== this.props.firebase; }; FirestoreBatchedWriteWithContext.prototype.render = function () { var _a = this.createMutationBatch(), addMutationToBatch = _a.addMutationToBatch, commit = _a.commit; return renderAndAddProps(this.props.children, __assign({}, this.props, { addMutationToBatch: addMutationToBatch, commit: commit })); }; return FirestoreBatchedWriteWithContext; }(Component)); var FirestoreBatchedWrite = (function (_super) { __extends(FirestoreBatchedWrite, _super); function FirestoreBatchedWrite() { return _super !== null && _super.apply(this, arguments) || this; } FirestoreBatchedWrite.prototype.render = function () { var children = this.props.children; return (createElement(FirestoreContextConsumer, null, function (context) { return (createElement(FirestoreBatchedWriteWithContext, __assign({}, context, { children: children }))); })); }; return FirestoreBatchedWrite; }(Component)); var FirestoreTranasactionWithContext = (function (_super) { __extends(FirestoreTranasactionWithContext, _super); function FirestoreTranasactionWithContext() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.createTransactionRunner = function () { var firestore = _this.props.firestore; var runTransaction = function (transactionLogic) { return __awaiter(_this, void 0, void 0, function () { var transactionLogicWithHelpers; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: transactionLogicWithHelpers = function (transaction) { var get$$1 = function (query) { return __awaiter(_this, void 0, void 0, function () { var ref; return __generator(this, function (_a) { switch (_a.label) { case 0: ref = getFirestoreQuery(__assign({}, query, { firestore: firestore })); return [4, transaction.get(ref)]; case 1: return [2, _a.sent()]; } }); }); }; var update = function (path, value) { return __awaiter(_this, void 0, void 0, function () { var ref; return __generator(this, function (_a) { switch (_a.label) { case 0: ref = getFirestoreQuery({ path: path, firestore: firestore }); return [4, transaction.update(ref, value)]; case 1: return [2, _a.sent()]; } }); }); }; var set$$1 = function (path, value, options) { return __awaiter(_this, void 0, void 0, function () { var ref; return __generator(this, function (_a) { switch (_a.label) { case 0: ref = getFirestoreQuery({ path: path, firestore: firestore }); return [4, transaction.set(ref, value, options)]; case 1: return [2, _a.sent()]; } }); }); }; var fDelete = function (path, precondition) { return __awaiter(_this, void 0, void 0, function () { var ref; return __generator(this, function (_a) { switch (_a.label) { case 0: ref = getFirestoreQuery({ path: path, firestore: firestore }); return [4, transaction.delete(ref)]; case 1: return [2, _a.sent()]; } }); }); }; return transactionLogic({ transaction: transaction, get: get$$1, update: update, set: set$$1, fDelete: fDelete }); }; return [4, firestore.runTransaction(transactionLogicWithHelpers)]; case 1: _a.sent(); return [2]; } }); }); }; return runTransaction; }; return _this; } FirestoreTranasactionWithContext.prototype.shouldComponentUpdate = function (nextProps) { return nextProps.firebase !== this.props.firebase; }; FirestoreTranasactionWithContext.prototype.render = function () { var runTransaction = this.createTransactionRunner(); return renderAndAddProps(this.props.children, __assign({}, this.props, { runTransaction: runTransaction })); }; return FirestoreTranasactionWithContext; }(Component)); var FirestoreTransaction = (function (_super) { __extends(FirestoreTransaction, _super); function FirestoreTransaction() { return _super !== null && _super.apply(this, arguments) || this; } FirestoreTransaction.prototype.render = function () { var children = this.props.children; return (createElement(FirestoreContextConsumer, null, function (context) { return (createElement(FirestoreTranasactionWithContext, __assign({}, context, { children: children }))); })); }; return FirestoreTransaction; }(Component)); export { FirestoreCollection, FirestoreDocument, FirestoreProvider, FirestoreMutation, FirestoreBatchedWrite, FirestoreTransaction };