Home Manual Reference Source Repository

src/index.js

import { eventChannel } from 'redux-saga';
import { put, fork, call, take } from 'redux-saga/effects';

const EVENT_TYPES = ['child_added', 'child_removed'];

const newOpts = (name = 'data') => {
	const opts = {};
	const chan = eventChannel(emit => {
		opts.handler = obj => {
			emit({ [name]: obj });
		};
		return () => {};
	});

	chan.handler = opts.handler;
	return chan;
};

const newKey = (path) => firebase.database().ref().child(path).push().key;

/**
 * Fetches a record specified by the key from the database
 *
 * @param path
 * @param key
 * @returns {*|any}
 * import { get } from 'firebase-saga';
 *
 * const posts = yield call(get, 'posts', '1234');
 */
export function* get(path, key) {
	const ref = firebase.database().ref(`${path}/${key}`);
	const data = yield call([ref, ref.once], 'value');

	return data.val();
}

/**
 * Fetches entire snapshot of the database
 *
 * @param path
 * @returns {*|any}
 * @example
 * import { getAll } from 'firebase-saga';
 *
 * const posts = yield call(getAll, 'posts');
 */
export function* getAll(path) {
	const ref = firebase.database().ref(path);
	const data = yield call([ref, ref.once], 'value');

	return data.val();
}

/**
 * Saves new data to the database with `set()`
 *
 * @param path
 * @param fn
 * @example
 * import { create } from 'firebase-saga';
 *
 * yield call(create, 'posts', () => ({
 *              [`posts/1234`]: {
 *                   title: 'My Second Post',
 *                   body: 'Second post details',
 *                   timestamp: +new Date
 *               }
 *           })
 *);
 */
export function* create(path, fn) {
	const key = yield call(newKey, path);
	const payload = yield call(fn, key);
	const opts = newOpts('error');
	const ref = firebase.database().ref();
	const [ _, { error } ] = yield [
		call([ref, ref.update], payload, opts.handler),
		take(opts)
	];
	return error;
}

export function* update(path, key, payload) {
	if (typeof payload === 'function') {
		payload = yield call(payload);
	}
	const opts = newOpts('error');
	const ref = firebase.database().ref(`${path}/${key}`);
	const [ _, { error } ] = yield [
		call([ref, ref.update], payload, opts.handler),
		take(opts)
	];
	return error;
}

/**
 * Generates a new child location using a unique key
 *
 * @param path
 * @param fn
 * @example
 * import { push } from 'firebase-saga';
 *
 * yield call(push, 'posts', () => ({
 *             title: formData.title,
 *             body: formData.body,
 *             timestamp: formData.timestamp
 *       })
 *);
 */
export function* push(path, fn) {
    const key = yield call(newKey, path);
    const payload = yield call(fn, key);
    const opts = newOpts('error');
    const ref = firebase.database().ref(path);
    const [ _, { error } ] = yield [
        call([ref, ref.push], payload, opts.handler),
        take(opts)
    ];
    return error;
}

/**
 * Deletes a given child location using a unique key
 *
 * @param path
 * @param key
 * @example
 * import { push } from 'firebase-saga';
 *
 * yield call(push, 'posts', () => ({
 *             title: formData.title,
 *             body: formData.body,
 *             timestamp: formData.timestamp
 *       })
 *);
 */

/**
 * Deletes a given child location using a unique key
 *
 * @param path
 * @param key
 * @returns {*}
 * @example
 * import { remove } from 'firebase-saga';
 *
 * yield call(remove, 'posts', '1234')
 */
export function* remove(path, key) {
    const opts = newOpts('error');
    const ref = firebase.database().ref(`${path}/${key}`);
    const [ _, { error } ] = yield [
        call([ref, ref.remove], opts.handler),
        take(opts)
    ];
    return error;
}

function* runSync(ref, eventType, creator) {
	const opts = newOpts();
	yield call([ref, ref.on], eventType, opts.handler);

	while (true) {
		const { data } = yield take(opts);
		yield put(creator({ data }));
	}
}

export function* sync(path, mapEventToAction = {}, limit = 20) {
	const ref = firebase.database().ref(path).limitToLast(limit);

	for (let type of EVENT_TYPES) {
		const action = mapEventToAction[type];

		if (typeof action === 'function') {
			yield fork(runSync, ref, type, action);
		}
	}
}