UNPKG

redux-devshare

Version:

[![NPM version][npm-image]][npm-url] [![NPM downloads][npm-downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url] [![Code Climate][climate-image]][climate-url] [![Code Coverage][coverage-i

397 lines (371 loc) 12.3 kB
import { size, map, some, first, drop, mapValues, reduce, isString, defaultsDeep } from 'lodash' import { getPopulateObjs } from './utils/populate' import { metaParams, paramSplitChar } from './constants' /** * @description Detect whether items are loaded yet or not * @param {Object} item - Item to check loaded status of. A comma seperated list is also acceptable. * @return {Boolean} Whether or not item is loaded * @example * import React, { Component, PropTypes } from 'react' * import { connect } from 'react-redux' * import { firebaseConnect, isLoaded, dataToJS } from 'react-redux-firebase' * * @firebaseConnect(['/todos']) * @connect( * ({ firebase }) => ({ * todos: dataToJS(firebase, '/todos'), * }) * ) * class Todos extends Component { * static propTypes = { * todos: PropTypes.object * } * * render() { * const { todos } = this.props; * * // Show loading while todos are loading * if(!isLoaded(todos)) { * return <span>Loading...</span> * } * * return <ul>{todosList}</ul> * } * } */ export const isLoaded = function () { if (!arguments || !arguments.length) { return true } return map(arguments, a => a !== undefined).reduce((a, b) => a && b) } /** * @description Detect whether items are empty or not * @param {Object} item - Item to check loaded status of. A comma seperated list is also acceptable. * @return {Boolean} Whether or not item is empty * @example * import React, { Component, PropTypes } from 'react' * import { connect } from 'react-redux' * import { firebaseConnect, isEmpty, dataToJS } from 'react-redux-firebase' * * @firebaseConnect(['/todos']) * @connect( * ({ firebase }) => ({ * todos: dataToJS(firebase, '/todos'), * }) * ) * class Todos extends Component { * static propTypes = { * todos: PropTypes.object * } * * render() { * const { todos } = this.props; * * // Message for if todos are empty * if(isEmpty(todos)) { * return <span>No Todos Found</span> * } * * return <ul>{todosList}</ul> * } * } */ export const isEmpty = data => !(data && size(data)) /** * @description Fix path by adding "/" to path if needed * @param {String} path - Path string to fix * @return {String} - Fixed path * @private */ export const fixPath = path => ((path.substring(0, 1) === '/') ? '' : '/') + path /** * @description Convert Immutable Map to a Javascript object * @param {Object} data - Immutable Map to be converted to JS object (state.firebase) * @return {Object} data - Javascript version of Immutable Map * @return {Object} Data located at path within Immutable Map */ export const toJS = data => data && data.toJS ? data.toJS() : data /** * @description Convert parameter from Immutable Map to a Javascript object * @param {Map} firebase - Immutable Map to be converted to JS object (state.firebase) * @param {String} path - Path from state.firebase to convert to JS object * @param {Object|String|Boolean} notSetValue - Value to use if data is not available * @return {Object} Data located at path within Immutable Map * @example <caption>Basic</caption> * import { connect } from 'react-redux' * import { firebaseConnect, pathToJS } from 'react-redux-firebase' * * @firebaseConnect() * @connect(({ firebase }) => ({ * profile: pathToJS(firebase, 'profile'), * auth: pathToJS(firebase, 'auth') * }) * export default class MyComponent extends Component { * ... */ export const pathToJS = (data, path, notSetValue) => { if (!data) { return notSetValue } const pathArr = fixPath(path).split(/\//).slice(1) if (data.getIn) { // Handle meta params (stored by string key) if (some(metaParams, (v) => pathArr.indexOf(v) !== -1)) { return toJS( data.getIn([ first(pathArr), drop(pathArr).join(paramSplitChar) ], notSetValue) ) } return toJS(data.getIn(pathArr, notSetValue)) } return data } /** * @description Convert parameter under "data" path of Immutable Map to a Javascript object. * **NOTE:** Setting a default value will cause `isLoaded` to always return true * @param {Map} firebase - Immutable Map to be converted to JS object (state.firebase) * @param {String} path - Path of parameter to load * @param {Object|String|Boolean} notSetValue - Value to return if value is not * found in redux. This will cause `isLoaded` to always return true (since * value is set from the start). * @return {Object} Data located at path within Immutable Map * @example <caption>Basic</caption> * import { connect } from 'react-redux' * import { firebaseConnect, dataToJS } from 'react-redux-firebase' * * @firebaseConnect(['/todos']) * @connect(({ firebase }) => ({ * // this.props.todos loaded from state.firebase.data.todos * todos: dataToJS(firebase, 'todos') * }) * @example <caption>Default Value</caption> * import { connect } from 'react-redux' * import { firebaseConnect, dataToJS } from 'react-redux-firebase' * const defaultValue = { * 1: { * text: 'Example Todo' * } * } * @firebaseConnect(['/todos']) * @connect(({ firebase }) => ({ * // this.props.todos loaded from state.firebase.data.todos * todos: dataToJS(firebase, 'todos', defaultValue) * }) */ export const dataToJS = (data, path, notSetValue) => { if (!data) { return notSetValue } const pathArr = `/data${fixPath(path)}`.split(/\//).slice(1) if (data.getIn) { return toJS(data.getIn(pathArr, notSetValue)) } return data } /** * @description Convert parameter under "ordered" path of Immutable Map to a * Javascript array. This preserves order set by query. * @param {Map} firebase - Immutable Map to be converted to JS object (state.firebase) * @param {String} path - Path of parameter to load * @param {Object|String|Boolean} notSetValue - Value to return if value is not found * @return {Object} Data located at path within Immutable Map * @example <caption>Basic</caption> * import { connect } from 'react-redux' * import { firebaseConnect, orderedToJS } from 'react-redux-firebase' * * @firebaseConnect([ * { * path: 'todos', * queryParams: ['orderByChild=text'] // order alphabetically based on text * }, * ]) * @connect(({ firebase }) => ({ * // this.props.todos loaded from state.firebase.ordered.todos * todos: orderedToJS(firebase, 'todos') * }) */ export const orderedToJS = (data, path, notSetValue) => { if (!data) { return notSetValue } const pathArr = `/ordered${fixPath(path)}`.split(/\//).slice(1) if (data.getIn) { return toJS(data.getIn(pathArr, notSetValue)) } return data } /** * @private * @description Build child list based on populate * @param {Map} data - Immutable Map to be converted to JS object (state.firebase) * @param {Object} list - Path of parameter to load * @param {Object} populate - Object with population settings */ export const buildChildList = (data, list, p) => mapValues(list, (val, key) => { let getKey = val // Handle key: true lists if (val === true) { getKey = key } const pathString = p.childParam ? `${p.root}/${getKey}/${p.childParam}` : `${p.root}/${getKey}` // Set to child under key if populate child exists if (dataToJS(data, pathString)) { return p.keyProp ? { [p.keyProp]: getKey, ...dataToJS(data, pathString) } : dataToJS(data, pathString) } // Populate child does not exist return val === true ? val : getKey }) /** * @description Convert parameter under "data" path of Immutable Map to a * Javascript object with parameters populated based on populates array * @param {Map} firebase - Immutable Map to be converted to JS object (state.firebase) * @param {String} path - Path of parameter to load * @param {Array} populates - Array of populate objects * @param {Object|String|Boolean} notSetValue - Value to return if value is not found * @return {Object} Data located at path within Immutable Map * @example <caption>Basic</caption> * import { connect } from 'react-redux' * import { firebaseConnect, helpers } from 'react-redux-firebase' * const { dataToJS } = helpers * const populates = [{ child: 'owner', root: 'users' }] * * const fbWrapped = firebaseConnect([ * { path: '/todos', populates } // load "todos" and matching "users" to redux * ])(App) * * export default connect(({ firebase }) => ({ * // this.props.todos loaded from state.firebase.data.todos * // each todo has child 'owner' populated from matching uid in 'users' root * // for loading un-populated todos use dataToJS(firebase, 'todos') * todos: populatedDataToJS(firebase, 'todos', populates), * }))(fbWrapped) */ export const populatedDataToJS = (data, path, populates, notSetValue) => { if (!data) { return notSetValue } // Handle undefined child if (!dataToJS(data, path, notSetValue)) { return dataToJS(data, path, notSetValue) } const populateObjs = getPopulateObjs(populates) // reduce array of populates to object of combined populated data return reduce( map(populateObjs, (p, obj) => { // single item with iterable child if (dataToJS(data, path)[p.child]) { // populate child is key if (isString(dataToJS(data, path)[p.child])) { const key = dataToJS(data, path)[p.child] const pathString = p.childParam ? `${p.root}/${key}/${p.childParam}` : `${p.root}/${key}` if (dataToJS(data, pathString)) { return { [p.child]: p.keyProp ? { [p.keyProp]: key, ...dataToJS(data, pathString) } : dataToJS(data, pathString) } } // matching child does not exist return dataToJS(data, path) } return { [p.child]: buildChildList(data, dataToJS(data, path)[p.child], p) } } // list with child param in each item return mapValues(dataToJS(data, path), (child, i) => { // no matching child parameter if (!child || !child[p.child]) { return child } // populate child is key if (isString(child[p.child])) { const key = child[p.child] const pathString = p.childParam ? `${p.root}/${key}/${p.childParam}` : `${p.root}/${key}` if (dataToJS(data, pathString)) { return { [p.child]: p.keyProp ? { [p.keyProp]: key, ...dataToJS(data, pathString) } : dataToJS(data, pathString) } } // matching child does not exist return child } // populate child list return { [p.child]: buildChildList(data, child[p.child], p) } }) }), (obj, v) => { // combine data from all populates to one object const combined = defaultsDeep(obj, v) return defaultsDeep(combined, dataToJS(data, path)) }) } /** * @description Load custom object from within store * @param {Map} firebase - Immutable Map to be converted to JS object (state.firebase) * @param {String} path - Path of parameter to load * @param {String} customPath - Part of store from which to load * @param {Object|String|Boolean} notSetValue - Value to return if value is not found * @return {Object} Data located at path within state * @example <caption>Basic</caption> * import { connect } from 'react-redux' * import { firebaseConnect, helpers } from 'react-redux-firebase' * const { customToJS } = helpers * * const fbWrapped = firebaseConnect(['/todos'])(App) * * export default connect(({ firebase }) => ({ * // this.props.todos loaded from state.firebase.data.todos * requesting: customToJS(firebase, 'todos', 'requesting') * }))(fbWrapped) */ export const customToJS = (data, path, custom, notSetValue) => { if (!data) { return notSetValue } const pathArr = `/${custom}${fixPath(path)}`.split(/\//).slice(1) if (data.getIn) { return toJS(data.getIn(pathArr, notSetValue)) } return data } export default { toJS, pathToJS, dataToJS, orderedToJS, populatedDataToJS, customToJS, isLoaded, isEmpty }