@taraai/read-write
Version:
Synchronous NoSQL/Firestore for React
447 lines (431 loc) • 14.2 kB
JavaScript
import { pick, omit, get, isObject } from 'lodash'
import { setWith, assign } from 'lodash/fp'
import { actionTypes } from './constants'
import {
getSlashStrPath,
getDotStrPath,
recursiveUnset,
combineReducers,
preserveValuesFromState
} from './utils/reducers'
const {
START,
SET,
SET_PROFILE,
MERGE,
LOGIN,
LOGOUT,
LOGIN_ERROR,
CLEAR_ERRORS,
REMOVE,
NO_VALUE,
SET_LISTENER,
UNSET_LISTENER,
AUTHENTICATION_INIT_STARTED,
AUTHENTICATION_INIT_FINISHED,
AUTH_EMPTY_CHANGE,
AUTH_LINK_SUCCESS,
UNAUTHORIZED_ERROR,
AUTH_UPDATE_SUCCESS,
AUTH_RELOAD_SUCCESS,
PROFILE_UPDATE_SUCCESS
} = actionTypes
/**
* Reducer for isInitializing state. Changed by `AUTHENTICATION_INIT_STARTED`
* and `AUTHENTICATION_INIT_FINISHED` actions.
* @param {object} [state=false] - Current isInitializing redux state
* @param {object} action - Object containing the action that was dispatched
* @param {string} action.type - Type of action that was dispatched
* @returns {object} Profile state after reduction
*/
export function isInitializingReducer(state = false, action) {
switch (action.type) {
case AUTHENTICATION_INIT_STARTED:
return true
case AUTHENTICATION_INIT_FINISHED:
return false
default:
return state
}
}
/**
* Reducer for requesting state.Changed by `START`, `NO_VALUE`, and `SET` actions.
* @param {object} [state={}] - Current requesting redux state
* @param {object} action - Object containing the action that was dispatched
* @param {string} action.type - Type of action that was dispatched
* @param {string} action.path - Path of action that was dispatched
* @returns {object} Profile state after reduction
*/
export function requestingReducer(state = {}, { type, path }) {
switch (type) {
case START:
return {
...state,
[getSlashStrPath(path)]: true
}
case NO_VALUE:
case SET:
return {
...state,
[getSlashStrPath(path)]: false
}
default:
return state
}
}
/**
* Reducer for requested state. Changed by `START`, `NO_VALUE`, and `SET` actions.
* @param {object} [state={}] - Current requested redux state
* @param {object} action - Object containing the action that was dispatched
* @param {string} action.type - Type of action that was dispatched
* @param {string} action.path - Path of action that was dispatched
* @returns {object} Profile state after reduction
*/
export function requestedReducer(state = {}, { type, path }) {
switch (type) {
case START:
return {
...state,
[getSlashStrPath(path)]: false
}
case NO_VALUE:
case SET:
return {
...state,
[getSlashStrPath(path)]: true
}
default:
return state
}
}
/**
* Reducer for timestamps state. Changed by `START`, `NO_VALUE`, and `SET` actions.
* @param {object} [state={}] - Current timestamps redux state
* @param {object} action - Object containing the action that was dispatched
* @param {string} action.type - Type of action that was dispatched
* @param {string} action.path - Path of action that was dispatched
* @returns {object} Profile state after reduction
*/
export function timestampsReducer(state = {}, { type, path }) {
switch (type) {
case START:
case NO_VALUE:
case SET:
return {
...state,
[getSlashStrPath(path)]: Date.now()
}
default:
return state
}
}
/**
* Creates reducer for data state. Used to create data and ordered reducers.
* Changed by `SET` or `SET_ORDERED` (if actionKey === 'ordered'), `MERGE`,
* `NO_VALUE`, and `LOGOUT` actions.
* @param {string} actionKey - Key of state for which to make reducer (data or ordered)
* @returns {Function} Data reducer
* @private
*/
function createDataReducer(actionKey = 'data') {
/**
* Creates reducer for data state. Used to create data and ordered reducers.
* Changed by `SET` or `SET_ORDERED` (if actionKey === 'ordered'), `MERGE`,
* `NO_VALUE`, and `LOGOUT` actions.
* @param {object} [state={}] - Current data redux state
* @param {object} action - Object containing the action that was dispatched
* @param {string} action.type - Type of action that was dispatched
* @param {string} action.path - Path of action that was dispatched
* @returns {object} Data state after reduction
* @private
*/
return function dataReducer(state = {}, action) {
switch (action.type) {
case SET:
return setWith(
Object,
getDotStrPath(action.path),
action[actionKey],
state
)
case MERGE:
const previousData = get(state, getDotStrPath(action.path), {}) // eslint-disable-line no-case-declarations
const mergedData = assign(previousData, action[actionKey]) // eslint-disable-line no-case-declarations
return setWith(Object, getDotStrPath(action.path), mergedData, state)
case NO_VALUE:
return setWith(Object, getDotStrPath(action.path), null, state)
case REMOVE:
if (actionKey === 'data') {
return recursiveUnset(getDotStrPath(action.path), state)
}
return state
case LOGOUT:
// support keeping data when logging out - #125
if (action.preserve) {
if (Array.isArray(action.preserve)) {
return pick(state, action.preserve) // pick returns a new object
} else if (isObject(action.preserve)) {
return action.preserve[actionKey]
? preserveValuesFromState(state, action.preserve[actionKey], {})
: {}
}
throw new Error(
'Invalid preserve parameter. It must be an Object or an Array'
)
}
return {}
default:
return state
}
}
}
/**
* Reducer for auth state. Changed by `LOGIN`, `LOGOUT`, and `LOGIN_ERROR` actions.
* @param {object} [state={isLoaded: false}] - Current auth redux state
* @param {object} action - Object containing the action that was dispatched
* @param {string} action.type - Type of action that was dispatched
* @returns {object} Profile state after reduction
*/
export function authReducer(
state = { isLoaded: false, isEmpty: true },
action
) {
switch (action.type) {
case LOGIN:
case AUTH_UPDATE_SUCCESS:
if (!action.auth) {
return {
isEmpty: true,
isLoaded: true
}
}
const auth = action.auth.toJSON ? action.auth.toJSON() : action.auth // eslint-disable-line no-case-declarations
// Support keeping data
if (action.preserve && action.preserve.auth) {
return preserveValuesFromState(state, action.preserve.auth, {
...auth,
isEmpty: false,
isLoaded: true
})
}
return { ...auth, isEmpty: false, isLoaded: true }
case AUTH_LINK_SUCCESS:
case AUTH_RELOAD_SUCCESS:
if (!action.payload) {
return {
isEmpty: true,
isLoaded: true
}
}
return {
...(action.payload.toJSON ? action.payload.toJSON() : action.payload),
isEmpty: false,
isLoaded: true
}
case LOGIN_ERROR:
case AUTH_EMPTY_CHANGE:
case LOGOUT:
// If it's reauthenticate keep user datas
if (action.reauthenticate) {
return preserveValuesFromState(state, true, {})
}
// Support keeping data when logging out
if (action.preserve && action.preserve.auth) {
return preserveValuesFromState(state, action.preserve.auth, {
isLoaded: true,
isEmpty: true
})
}
return { isLoaded: true, isEmpty: true }
default:
return state
}
}
/**
* Reducer for authError state. Changed by `LOGIN`, `LOGOUT`, `LOGIN_ERROR`, and
* `UNAUTHORIZED_ERROR` actions.
* @param {object} [state={}] - Current authError redux state
* @param {object} action - Object containing the action that was dispatched
* @param {string} action.type - Type of action that was dispatched
* @returns {object} authError state after reduction
*/
export function authErrorReducer(state = null, action) {
switch (action.type) {
case LOGIN:
case LOGOUT:
return null
case LOGIN_ERROR:
case UNAUTHORIZED_ERROR:
return action.authError
default:
return state
}
}
/**
* Reducer for profile state. Changed by `SET_PROFILE`, `LOGOUT`, and
* `LOGIN_ERROR` actions.
* @param {object} [state={isLoaded: false}] - Current profile redux state
* @param {object} action - Object containing the action that was dispatched
* @param {string} action.type - Type of action that was dispatched
* @returns {object} Profile state after reduction
*/
export function profileReducer(
state = { isLoaded: false, isEmpty: true },
action
) {
switch (action.type) {
case SET_PROFILE:
if (!action.profile) {
return {
...state,
isEmpty: true,
isLoaded: true
}
}
return {
...action.profile,
isEmpty: false,
isLoaded: true
}
case PROFILE_UPDATE_SUCCESS:
return Object.assign({}, state, action.payload)
case LOGIN:
// Support keeping data when logging out
if (action.preserve && action.preserve.profile) {
return preserveValuesFromState(state, action.preserve.profile, {
isLoaded: true,
isEmpty: true
})
}
return {
isEmpty: true,
isLoaded: false
}
case LOGOUT:
case AUTH_EMPTY_CHANGE:
// Support keeping data when logging out
if (action.preserve && action.preserve.profile) {
return preserveValuesFromState(state, action.preserve.profile, {
isLoaded: true,
isEmpty: true
})
}
return { isLoaded: true, isEmpty: true }
default:
return state
}
}
/**
* Reducer for errors state. Changed by `UNAUTHORIZED_ERROR`, `CLEAR_ERRORS`,
* and `LOGOUT` actions.
* @param {object} [state=[]] - Current errors redux state
* @param {object} action - Object containing the action that was dispatched
* @param {string} action.type - Type of action that was dispatched
* @param {Function} action.preserve - `not required` Filter function for
* preserving errors
* @returns {object} Profile state after reduction
*/
export function errorsReducer(state = [], action) {
switch (action.type) {
case LOGIN_ERROR:
case UNAUTHORIZED_ERROR:
if (!Array.isArray(state)) {
throw new Error('Errors state must be an array')
}
return [...state, action.authError]
case LOGOUT:
case CLEAR_ERRORS:
// Support keeping errors through a filter function
if (action.preserve && action.preserve.errors) {
if (typeof action.preserve.errors !== 'function') {
throw new Error(
'Preserve for the errors state currently only supports functions'
)
}
return state.filter(action.preserve.errors) // run filter function on state
}
return []
default:
return state
}
}
/**
* Reducer for listeners ids. Changed by `SET_LISTENER` and `UNSET_LISTENER`
* actions.
* @param {object} [state={}] - Current listenersById redux state
* @param {object} action - Object containing the action that was dispatched
* @param {string} action.type - Type of action that was dispatched
* @param {string} action.path - Path of action that was dispatched
* @param {object} action.payload - Payload object
* @returns {object} listenersById state after reduction (used in listeners)
* @private
*/
function listenersById(state = {}, { type, path, payload }) {
switch (type) {
case SET_LISTENER:
return {
...state,
[payload.id]: {
id: payload.id,
path
}
}
case UNSET_LISTENER:
return omit(state, [payload.id])
default:
return state
}
}
/**
* Reducer for listeners state. Changed by `UNAUTHORIZED_ERROR`
* and `LOGOUT` actions.
* @param {object} [state=[]] - Current allListeners redux state
* @param {object} action - Object containing the action that was dispatched
* @param {string} action.type - Type of action that was dispatched
* @param {string} action.path - Path of action that was dispatched
* @param {object} action.payload - Payload object
* @returns {object} allListeners state after reduction (used in listeners)
* @private
*/
function allListeners(state = [], { type, path, payload }) {
switch (type) {
case SET_LISTENER:
return [...state, payload.id]
case UNSET_LISTENER:
return state.filter((lId) => lId !== payload.id)
default:
return state
}
}
/**
* Reducer for listeners state. Changed by `UNAUTHORIZED_ERROR`
* and `LOGOUT` actions.
* @param {object} [state=[]] - Current listeners redux state
* @param {object} action - Object containing the action that was dispatched
* @param {string} action.type - Type of action that was dispatched
* @returns {object} Profile state after reduction
*/
export const listenersReducer = combineReducers({
byId: listenersById,
allIds: allListeners
})
/**
* Reducer for data state. Changed by `SET`, `SET_ORDERED`,`NO_VALUE`, and
* `LOGOUT` actions.
* @param {object} [state={}] - Current data redux state
* @param {object} action - Object containing the action that was dispatched
* @param {string} action.type - Type of action that was dispatched
* @param {string} action.path - Path of action that was dispatched
* @returns {object} Data state after reduction
*/
export const dataReducer = createDataReducer()
/**
* Reducer for ordered state. Changed by `SET`, `SET_ORDERED`,`NO_VALUE`, and
* `LOGOUT` actions.
* @param {object} [state={}] - Current data redux state
* @param {object} action - Object containing the action that was dispatched
* @param {string} action.type - Type of action that was dispatched
* @param {string} action.path - Path of action that was dispatched
* @returns {object} Data state after reduction
*/
export const orderedReducer = createDataReducer('ordered')