UNPKG

@talend/react-cmf

Version:

A framework built on top of best react libraries

384 lines (378 loc) 13.2 kB
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } /** * This module connect your component in the CMF environment. * @module react-cmf/lib/cmfConnect * @example import { cmfConnect } from '@talend/react-cmf'; function MyComponent(props) { const onClick = (event) => { props.dispatchActionCreator('myaction', event, { props: props }); }; return <button onClick={onClick}>Edit {props.foo.name}</button>; } function mapStateToProps(state) { return { foo: state.cmf.collection.get('foo', { name: 'world' }), }; } export default cmfConnect({ mapStateToProps, }); */ import PropTypes from 'prop-types'; import { useState, useContext, useEffect, forwardRef } from 'react'; import hoistStatics from 'hoist-non-react-statics'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect, useStore } from 'react-redux'; import { randomUUID } from '@talend/utils'; import actions from './actions'; import actionCreator from './actionCreator'; import component from './component'; import CONST from './constant'; import expression from './expression'; import onEvent from './onEvent'; import { initState, getStateAccessors, getStateProps } from './componentState'; import { mapStateToViewProps } from './settings'; import omit from './omit'; import { RegistryContext } from './RegistryProvider'; import { jsx as _jsx } from "react/jsx-runtime"; export function getComponentName(WrappedComponent) { return WrappedComponent.displayName || WrappedComponent.name || 'Component'; } export function getComponentId(componentId, props) { if (typeof componentId === 'function') { return componentId(props) || 'default'; } else if (typeof componentId === 'string') { return componentId; } else if (props.componentId) { return props.componentId; } return 'default'; } export function getStateToProps({ defaultProps, componentId, ownProps, state, mapStateToProps, WrappedComponent }) { const props = { ...defaultProps }; const cmfProps = getStateProps(state, getComponentName(WrappedComponent), getComponentId(componentId, ownProps)); _extends(props, cmfProps); const viewProps = mapStateToViewProps(state, ownProps, getComponentName(WrappedComponent), getComponentId(componentId, ownProps)); _extends(props, viewProps); let userProps = {}; if (mapStateToProps) { userProps = mapStateToProps(state, { ...ownProps, ...props }, cmfProps); } _extends(props, userProps); _extends(props, expression.mapStateToProps(state, { ...ownProps, ...props })); return props; } export function getDispatchToProps({ defaultState, dispatch, componentId, mapDispatchToProps, ownProps, WrappedComponent }) { const cmfProps = getStateAccessors(dispatch, getComponentName(WrappedComponent), getComponentId(componentId, ownProps), defaultState); cmfProps.dispatch = dispatch; cmfProps.getComponent = component.get; cmfProps.dispatchActionCreator = (actionId, event, data, context) => { dispatch(actionCreator.get(context, actionId)(event, data, context)); }; let userProps = {}; if (mapDispatchToProps) { if (process.env.NODE_ENV === 'development') { // eslint-disable-next-line no-console console.warn(`DEPRECATION WARNING: mapDispatchToProps will be removed from cmfConnect. Please use the injectedProps dispatchActionCreator or dispatch`); } userProps = mapDispatchToProps(dispatch, ownProps, cmfProps); } return { ...cmfProps, ...userProps }; } /** * Internal: you should not have to use this * return the merged props which cleanup expression props * call mergeProps if exists after the cleanup * @param {object} options { mergeProps, stateProps, dispatchProps, ownProps } */ export function getMergeProps({ mergeProps, stateProps, dispatchProps, ownProps }) { if (mergeProps) { return mergeProps(expression.mergeProps(stateProps), expression.mergeProps(dispatchProps), expression.mergeProps(ownProps)); } return { ...expression.mergeProps(ownProps), ...expression.mergeProps(dispatchProps), ...expression.mergeProps(stateProps) }; } /** * this function wrap your component to inject CMF props * @example * The following props are injected: * - props.state * - props.setState * - props.initState (you should never have to call it your self) * - dispatch(action) * - dispatchActionCreator(id, event, data, [context]) * * support for the following props * - initialState (called by props.initState) * - didMountActionCreator (id or array of id) * - willUnMountActionCreator (id or array of id) * - componentId (or will use uuid) * - keepComponentState (boolean, overrides the keepComponentState defined in container) * - didMountActionCreator (string called as action creator in didMount) * - view (string to inject the settings as props with ref support) * - whateverExpression (will inject `whatever` props and will remove it) * @example * options has the following shape: { componentId, // string or function(props) to compute the id in the store defaultState, // the default state when the component is mount keepComponent, // boolean, when the component is unmount, to keep it's state in redux store mapStateToProps, // function(state, ownProps) that should return the props (same as redux) mapDispatchToProps, // same as redux connect arg, you should use dispatchActionCreator instead mergeProps, // same as redux connect } * @param {object} options Option objects to configure the redux connect * @return {ReactComponent} */ export default function cmfConnect({ componentId, defaultState, defaultProps, keepComponentState, mapStateToProps, mapDispatchToProps, mergeProps, omitCMFProps = true, withComponentRegistry = false, withDispatch = false, withDispatchActionCreator = false, withComponentId = false, ...rest } = {}) { const propsToOmit = []; if (omitCMFProps) { if (!defaultState) { propsToOmit.push(...CONST.INJECTED_STATE_PROPS); } if (!withComponentRegistry) { propsToOmit.push('getComponent'); } if (!withComponentId) { propsToOmit.push('componentId'); } if (!withDispatch) { propsToOmit.push('dispatch'); } if (!withDispatchActionCreator) { propsToOmit.push('dispatchActionCreator'); } } let displayNameWarning = true; return function wrapWithCMF(WrappedComponent) { if (!WrappedComponent.displayName && displayNameWarning) { displayNameWarning = false; // eslint-disable-next-line no-console console.warn(`${WrappedComponent.name} has no displayName. Please read https://jira.talendforge.org/browse/TUI-302`); } function getState(state, id = 'default') { return state.cmf.components.getIn([getComponentName(WrappedComponent), id], defaultState); } function getSetStateAction(state, id, type) { return { type: type || `${getComponentName(WrappedComponent)}.setState`, cmf: { componentState: actions.components.mergeState(getComponentName(WrappedComponent), id, state) } }; } function CMFContainer(props, ref) { const [instanceId] = useState(randomUUID()); const registry = useContext(RegistryContext); const store = useStore(); function dispatchActionCreator(actionCreatorId, event, data, extraContext) { const extendedContext = { registry, store, ...extraContext }; props.dispatchActionCreator(actionCreatorId, event, data, extendedContext); } useEffect(() => { initState(props); if (props.saga) { dispatchActionCreator('cmf.saga.start', { type: 'DID_MOUNT', componentId: instanceId }, { ...props, // DEPRECATED componentId: getComponentId(componentId, props) }); } if (props.didMountActionCreator) { dispatchActionCreator(props.didMountActionCreator, null, props); } return () => { if (props.willUnmountActionCreator) { dispatchActionCreator(props.willUnmountActionCreator, null, props); } // if the props.keepComponentState is present we have to stick to it if (props.keepComponentState === false || props.keepComponentState === undefined && !keepComponentState) { props.deleteState(props.initialState); } if (props.saga) { dispatchActionCreator('cmf.saga.stop', { type: 'WILL_UNMOUNT', componentId: instanceId }, props); } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); function getOnEventProps() { return Object.keys(props).reduce((acc, key) => { // TODO check how to replace the this onEvent.addOnEventSupport(onEvent.DISPATCH, { props }, acc, key); onEvent.addOnEventSupport(onEvent.ACTION_CREATOR, { props }, acc, key); onEvent.addOnEventSupport(onEvent.SETSTATE, { props }, acc, key); return acc; }, { toOmit: [], dispatchActionCreator }); } if (props.renderIf === false) { return null; } const { toOmit, spreadCMFState, ...handlers } = getOnEventProps(); // remove all internal props already used by the container delete handlers.dispatchActionCreator; toOmit.push(...CONST.CMF_PROPS, ...propsToOmit); if (props.omitRouterProps) { toOmit.push('omitRouterProps', ...CONST.INJECTED_ROUTER_PROPS); } let spreadedState = {}; if ((spreadCMFState || props.spreadCMFState) && props.state) { spreadedState = props.state.toJS(); } const newProps = { ...omit(props, toOmit), ...handlers, ...spreadedState }; if (newProps.dispatchActionCreator && toOmit.indexOf('dispatchActionCreator') === -1) { // override to inject CMFContainer context newProps.dispatchActionCreator = dispatchActionCreator; } if (!newProps.state && defaultState && toOmit.indexOf('state') === -1) { newProps.state = defaultState; } if (rest.forwardRef) { return /*#__PURE__*/_jsx(WrappedComponent, { ...newProps, ref: ref }); } return /*#__PURE__*/_jsx(WrappedComponent, { ...newProps }); } let CMFWithRef = hoistStatics(CMFContainer, WrappedComponent); CMFContainer.displayName = `CMF(${getComponentName(WrappedComponent)})`; CMFContainer.propTypes = { ...cmfConnect.propTypes }; CMFContainer.WrappedComponent = WrappedComponent; CMFContainer.getState = getState; // eslint-disable-next-line @typescript-eslint/default-param-last CMFContainer.setStateAction = function setStateAction(state, id = 'default', type) { if (typeof state !== 'function') { return getSetStateAction(state, id, type); } return (_, getReduxState) => getSetStateAction(state(getState(getReduxState(), id)), id, type); }; if (rest.forwardRef) { CMFWithRef = /*#__PURE__*/forwardRef(CMFWithRef); CMFWithRef.displayName = `ForwardRef(${CMFContainer.displayName})`; CMFWithRef.WrappedComponent = CMFContainer.WrappedComponent; CMFWithRef.getState = CMFContainer.getState; CMFWithRef.setStateAction = CMFContainer.setStateAction; } const Connected = connect((state, ownProps) => getStateToProps({ componentId, defaultProps, defaultState, ownProps, state, mapStateToProps, WrappedComponent }), (dispatch, ownProps) => getDispatchToProps({ defaultState, dispatch, componentId, mapDispatchToProps, ownProps, WrappedComponent }), (stateProps, dispatchProps, ownProps) => getMergeProps({ mergeProps, stateProps, dispatchProps, ownProps }), { ...rest })(CMFWithRef); Connected.CMFContainer = CMFContainer; return Connected; }; } cmfConnect.INJECTED_PROPS = CONST.INJECTED_PROPS; cmfConnect.INJECTED_STATE_PROPS = CONST.INJECTED_STATE_PROPS; cmfConnect.INJECTED_ROUTER_PROPS = CONST.INJECTED_ROUTER_PROPS; cmfConnect.ALL_INJECTED_PROPS = CONST.INJECTED_PROPS.concat(['getComponent', 'componentId']); cmfConnect.omit = omit; cmfConnect.omitAllProps = props => cmfConnect.omit(props, cmfConnect.ALL_INJECTED_PROPS); cmfConnect.propTypes = { state: ImmutablePropTypes.map, initialState: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.object]), getComponent: PropTypes.func, setState: PropTypes.func, initState: PropTypes.func, dispatchActionCreator: PropTypes.func, dispatch: PropTypes.func }; //# sourceMappingURL=cmfConnect.js.map