UNPKG

react-redux-fetch

Version:

A declarative and customizable way to fetch data for React components and manage that data in the Redux state

180 lines (150 loc) 6.2 kB
// @flow import React, { Component } from 'react'; import type { ComponentType } from 'react'; import { connect as reduxConnect } from 'react-redux'; import { bindActionCreators } from 'redux'; import type { Dispatch } from 'redux'; import isFunction from 'lodash/isFunction'; import isObject from 'lodash/isObject'; import merge from 'lodash/merge'; import each from 'lodash/each'; import reduce from 'lodash/reduce'; // import reactReduxFetchActions from '../actions'; import { getModel } from '../reducers/selectors'; // import container from '../container'; import capitalizeFirstLetter from '../utils/capitalizeFirstLetter'; import buildActionsFromMappings, { ensureResourceIsObject, validateResourceObject, } from '../utils/buildActionsFromMappings'; import helpers from '../utils/helpers'; import type { reduxAction } from '../types'; // const defaultRequestType = 'get'; type Props = { dispatch: Dispatch<reduxAction>, fetchData: Object, }; type State = { dispatchFunctions?: Object, } type FuncOrArr = Function | Array<*>; type OptionalFuncOrObj = Function | Object | null; function getDisplayName(WrappedComponent: ComponentType<*>): string { return WrappedComponent.displayName || WrappedComponent.name || 'Component'; } function connect( mapPropsToRequestsToProps: FuncOrArr, componentMapStateToProps?: OptionalFuncOrObj = null, componentMapDispatchToProps?: OptionalFuncOrObj = null, ) { return function wrapWithReactReduxFetch(WrappedComponent: ComponentType<*>) { class ReactReduxFetch extends Component<Props, State> { constructor(props) { super(props); // if mapPropsToRequestsToProps is a function, // we can't cache our dispatch functions because they might depend on the props. // We could support this in the future by building the functions again when te props change. if (typeof mapPropsToRequestsToProps === 'function') { return; } this.state = { dispatchFunctions: this.actionsFromProps(props.dispatch, mapPropsToRequestsToProps), }; } state = {}; /** * @param {Object} fetchData The complete react-redux-fetch state leaf * @param {Array} mappings Array of objects with shape: * {resource: ..., method: ..., request: ...} * @return {Object} all the resources the WrappedComponent requested */ getFilteredFetchData = (fetchData, mappings) => { const data = {}; each(mappings, (mapping) => { const resource = ensureResourceIsObject(mapping); validateResourceObject(resource); const resourceName = resource.name; data[`${resourceName}Fetch`] = fetchData[resourceName] || {}; }); return data; }; /** * @param {Function} dispatch Redux dispatch function * @param {Array} mappings Array of objects with shape: * {resource: ..., method: ..., request: ...} * @return {Object} functions for the WrappedComponent e.g.: 'dispatchUserFetch()' * */ actionsFromProps = (dispatch, mappings: Array<*>): Object => reduce( buildActionsFromMappings(mappings), (actions, actionCreator, key) => Object.assign({}, actions, { [`dispatch${capitalizeFirstLetter(key)}`]: (...args) => { const action = actionCreator(...args); if (action) { dispatch(action); } }, }), {}, ); /** * If the value passed to connect() is a function, * execute the function and pass the props + context * @param {Object} props React props * @param {Object} context React context * @return {Array} an array with request configurations * */ buildMappings = (props: Props, context: Object): Array<*> => { const finalProps = props || this.props; const finalContext = context || this.context || {}; return isFunction(mapPropsToRequestsToProps) ? // $FlowFixMe mapPropsToRequestsToProps(finalProps, finalContext) : // $FlowFixMe mapPropsToRequestsToProps; }; render() { const { fetchData, ...other } = this.props; const { dispatchFunctions } = this.state; const mappings = this.buildMappings(this.props, this.context); const actions = dispatchFunctions || this.actionsFromProps(this.props.dispatch, mappings); const data = this.getFilteredFetchData(fetchData, mappings); return <WrappedComponent {...other} {...actions} {...data} />; } } ReactReduxFetch.displayName = `ReactReduxFetch.connect(${getDisplayName(WrappedComponent)})`; // ReactReduxFetch.propTypes = { // dispatch: PropTypes.func.isRequired, // fetchData: PropTypes.object.isRequired, // }; const mapStateToProps = (state, props) => merge( { fetchData: getModel(state) }, helpers.ensureObject(componentMapStateToProps, [state, props]), ); let mapDispatchToProps; if (isFunction(componentMapDispatchToProps)) { mapDispatchToProps = (dispatch: Dispatch<reduxAction>) => // $FlowFixMe: suppressing until assert issue is resolved (https://github.com/facebook/flow/issues/34) merge({ dispatch }, componentMapDispatchToProps(dispatch)); } else if (isObject(componentMapDispatchToProps)) { mapDispatchToProps = (dispatch: Dispatch<reduxAction>) => // $FlowFixMe: suppressing until assert issue is resolved (https://github.com/facebook/flow/issues/34) merge({ dispatch }, bindActionCreators(componentMapDispatchToProps, dispatch)); } return reduxConnect(mapStateToProps, mapDispatchToProps)(ReactReduxFetch); }; } // function factory(defaults = {}, options = {}) { function factory() { function connectImpl( map: FuncOrArr, mapStateToProps?: OptionalFuncOrObj, mapDispatchToProps?: OptionalFuncOrObj, ) { return connect(map, mapStateToProps, mapDispatchToProps); } return connectImpl; } export default factory();