edtoken/redux-tide

View on GitHub
src/selector.js

Summary

Maintainability
A
3 hrs
Test Coverage
/**
 * @namespace selector
 */

import { createSelector } from 'reselect'
import { denormalize } from './helper'
import {
  ACTIONS_REDUCER_NAME,
  ENTITIES_REDUCER_NAME,
  IS_TEST_ENVIRONMENT
} from './config'

const defaultActionDataOutput = {
  status: '',
  time: '',
  hasError: '',
  errorText: '',
  isFetching: false
}

const deepCopy = obj => {
  return JSON.parse(JSON.stringify(obj))
}

const makeDefaultActionData = () => {
  return deepCopy(defaultActionDataOutput)
}

const getPayloadIds = (dataKey, isArray, actionState, stateKey) => {
  if (!dataKey) {
    return undefined
  }

  const payloadIds = actionState.get(stateKey || dataKey)

  if (!payloadIds) {
    return undefined
  }

  return isArray ? payloadIds : [payloadIds]
}

const makeActionDenormalizedPayload = (
  isArray,
  payloadIds,
  schema,
  entities
) => {
  if (!payloadIds) {
    return undefined
  }

  const result = denormalize(payloadIds, [schema], entities)
    .filter(v => v)
    .map(item => item.toJS())
  return isArray ? result : result[0]
}

const makeActionDenormalizedPayloads = (
  isFetching,
  actionSchema,
  entities,
  payloadIsArray,
  actionDataKey,
  entityState,
  actionState
) => {
  if (!actionDataKey) {
    return {}
  }

  if (!entityState) {
    return {
      payload: payloadIsArray ? [] : undefined,
      prevPayload: payloadIsArray ? [] : undefined
    }
  }

  const actionPayloadIds = getPayloadIds(
    actionDataKey,
    payloadIsArray,
    actionState
  )
  const actionPrevPayloadIds = getPayloadIds(
    actionDataKey,
    payloadIsArray,
    actionState,
    `prev${actionDataKey}`
  )

  return {
    payload: makeActionDenormalizedPayload(
      payloadIsArray,
      actionPayloadIds,
      actionSchema,
      entities
    ),
    prevPayload: makeActionDenormalizedPayload(
      payloadIsArray,
      actionPrevPayloadIds,
      actionSchema,
      entities
    )
  }
}

const _makeGetActionData = (action, actionId, entityName, actionSchema) => {
  return createSelector(
    [
      state => state[ACTIONS_REDUCER_NAME].get(actionId),
      state => state[ENTITIES_REDUCER_NAME].get(entityName),
      state => state[ENTITIES_REDUCER_NAME]
    ],
    (actionState, entityState, entities) => {
      if (!actionState) {
        return makeDefaultActionData()
      }

      const payloadIsArray = actionState.get('isArrayData')
      const dataKey = actionState.get('actionDataKey')
      const isFetching = actionState.get('status') === 'pending'

      return Object.assign(
        makeDefaultActionData(),
        {
          actionId,
          sourceResult: actionState.get('sourceResult'),
          status: actionState.get('status'),
          time: actionState.get('time'),
          hasError: actionState.get('hasError'),
          errorText: actionState.get('errorText'),
          isFetching: actionState.get('isFetching')
        },
        makeActionDenormalizedPayloads(
          isFetching,
          actionSchema,
          entities,
          payloadIsArray,
          dataKey,
          entityState,
          actionState
        )
      )
    }
  )
}

export const getActionData = action => {
  const actionId = action.actionId()
  const entityName = action.getEntityName()
  const actionEntitySchema = action.getSchema()

  return (state, props) => {
    const selectorGetActionData = _makeGetActionData(
      action,
      actionId,
      entityName,
      actionEntitySchema
    )
    return selectorGetActionData(state, props)
  }
}

export const getMergedActionsData = (...actions) => {
  return createSelector(
    actions.map(action => getActionData(action)),
    (...actionsData) => {
      const sortedByUpate = actionsData.sort((a, b) => a.time - b.time)

      return sortedByUpate.reduce((memo, item) => {
        return Object.assign(memo, item)
      })
    }
  )
}

export const getActionsReducer = state => state[ACTIONS_REDUCER_NAME]

export const getEntityReducer = state => state[ENTITIES_REDUCER_NAME]

export const getEntityItemsBySchema = entitySchema => {
  return createSelector([getEntityReducer], entities => {
    return entities.get(entitySchema.key)
  })
}

export const getEntityItemsByAction = action => {
  return createSelector([getEntityReducer], entities => {
    return entities.get(action.getEntityName())
  })
}

export const getEntityItemsByEntityName = name => {
  return createSelector([getEntityReducer], entities => {
    return entities.get(name)
  })
}

export const denomalizeEntityItemById = (id, schema, entities) => {
  return denormalize(id, schema, entities)
}

if (IS_TEST_ENVIRONMENT) {
  module.exports.defaultActionDataOutput = defaultActionDataOutput
  module.exports._makeGetActionData = _makeGetActionData
}