New Slot property passthrough

This commit is contained in:
Chi Vinh Le
2018-03-09 17:33:34 +01:00
parent e0dc871367
commit 1b55684a73
4 changed files with 238 additions and 53 deletions
@@ -2,6 +2,7 @@ import React, { Children } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { getShallowChanges } from 'coral-framework/utils';
import omit from 'lodash/omit';
class IfSlotIsEmpty extends React.Component {
static contextTypes = {
@@ -9,9 +10,23 @@ class IfSlotIsEmpty extends React.Component {
};
shouldComponentUpdate(next) {
const changes = getShallowChanges(this.props, next);
// Handle special `passthrough` props.
const passthroughIndex = changes.indexOf('passthrough');
if (passthroughIndex !== -1) {
if (!this.props.passthrough || next.passthrough) {
return true;
}
if (
getShallowChanges(this.props.passthrough, next.passthrough).lenght === 0
) {
changes.splice(passthroughIndex, 1);
}
}
// Prevent Slot from rerendering when only reduxState has changed and
// it does not result in a change.
const changes = getShallowChanges(this.props, next);
if (changes.length === 1 && changes[0] === 'reduxState') {
return this.isSlotEmpty(this.props) !== this.isSlotEmpty(next);
}
@@ -20,19 +35,58 @@ class IfSlotIsEmpty extends React.Component {
return changes.length !== 0;
}
getPassthrough(props = this.props) {
const slotProps = omit(props, [
'slot',
'children',
'reduxState',
'passthrough',
'dispatch',
]);
// @Deprecated
if (process.env.NODE_ENV !== 'production') {
if (Object.keys(slotProps).length) {
/* eslint-disable no-console */
console.warn(
`IfSlotIsEmpty '${
props.fill
}' passing through unknown props is deprecated, please use 'passthrough' instead`,
slotProps
);
/* eslint-enable no-console */
}
}
if (props.passthrough) {
return props.passthrough;
}
if (props.queryData) {
if (process.env.NODE_ENV !== 'production') {
/* eslint-disable no-console */
console.warn(
`Slot '${
props.fill
}' property 'queryData' is deprecated, please use 'passthrough' instead`
);
/* eslint-enable no-console */
}
return {
...props.queryData,
...slotProps,
};
}
return slotProps;
}
isSlotEmpty(props = this.props) {
const {
slot,
className: _a,
reduxState,
component: _b = 'div',
children: _c,
queryData,
...rest
} = props;
const { slot, reduxState } = props;
const passthrough = this.getPassthrough(props);
const slots = Array.isArray(slot) ? slot : [slot];
return slots.every(slot =>
this.context.plugins.isSlotEmpty(slot, reduxState, rest, queryData)
this.context.plugins.isSlotEmpty(slot, reduxState, passthrough)
);
}
@@ -44,6 +98,8 @@ class IfSlotIsEmpty extends React.Component {
IfSlotIsEmpty.propTypes = {
slot: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
children: PropTypes.node.isRequired,
passthrough: PropTypes.object,
};
const mapStateToProps = state => ({
@@ -2,6 +2,7 @@ import React, { Children } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { getShallowChanges } from 'coral-framework/utils';
import omit from 'lodash/omit';
class IfSlotIsNotEmpty extends React.Component {
static contextTypes = {
@@ -9,9 +10,23 @@ class IfSlotIsNotEmpty extends React.Component {
};
shouldComponentUpdate(next) {
const changes = getShallowChanges(this.props, next);
// Handle special `passthrough` props.
const passthroughIndex = changes.indexOf('passthrough');
if (passthroughIndex !== -1) {
if (!this.props.passthrough || next.passthrough) {
return true;
}
if (
getShallowChanges(this.props.passthrough, next.passthrough).lenght === 0
) {
changes.splice(passthroughIndex, 1);
}
}
// Prevent Slot from rerendering when only reduxState has changed and
// it does not result in a change.
const changes = getShallowChanges(this.props, next);
if (changes.length === 1 && changes[0] === 'reduxState') {
return this.isSlotEmpty(this.props) !== this.isSlotEmpty(next);
}
@@ -20,19 +35,58 @@ class IfSlotIsNotEmpty extends React.Component {
return changes.length !== 0;
}
getPassthrough(props = this.props) {
const slotProps = omit(props, [
'slot',
'children',
'reduxState',
'passthrough',
'dispatch',
]);
// @Deprecated
if (process.env.NODE_ENV !== 'production') {
if (Object.keys(slotProps).length) {
/* eslint-disable no-console */
console.warn(
`IfSlotIsEmpty '${
props.fill
}' passing through unknown props is deprecated, please use 'passthrough' instead`,
slotProps
);
/* eslint-enable no-console */
}
}
if (props.passthrough) {
return props.passthrough;
}
if (props.queryData) {
if (process.env.NODE_ENV !== 'production') {
/* eslint-disable no-console */
console.warn(
`Slot '${
props.fill
}' property 'queryData' is deprecated, please use 'passthrough' instead`
);
/* eslint-enable no-console */
}
return {
...props.queryData,
...slotProps,
};
}
return slotProps;
}
isSlotEmpty(props = this.props) {
const {
slot,
className: _a,
reduxState,
component: _b = 'div',
children: _c,
queryData,
...rest
} = props;
const { slot, reduxState } = props;
const passthrough = this.getPassthrough(props);
const slots = Array.isArray(slot) ? slot : [slot];
return slots.every(slot =>
this.context.plugins.isSlotEmpty(slot, reduxState, rest, queryData)
this.context.plugins.isSlotEmpty(slot, reduxState, passthrough)
);
}
@@ -44,6 +98,8 @@ class IfSlotIsNotEmpty extends React.Component {
IfSlotIsNotEmpty.propTypes = {
slot: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
children: PropTypes.node.isRequired,
passthrough: PropTypes.object,
};
const mapStateToProps = state => ({
+60 -7
View File
@@ -20,6 +20,20 @@ class Slot extends React.Component {
// Prevent Slot from rerendering when only reduxState has changed and
// it does not result in a change of slot children.
const changes = getShallowChanges(this.props, next);
// Handle special `passthrough` props.
const passthroughIndex = changes.indexOf('passthrough');
if (passthroughIndex !== -1) {
if (!this.props.passthrough || next.passthrough) {
return true;
}
if (
getShallowChanges(this.props.passthrough, next.passthrough).lenght === 0
) {
changes.splice(passthroughIndex, 1);
}
}
if (changes.length === 1 && changes[0] === 'reduxState') {
const prevChildrenKeys = this.getChildren(this.props).map(
child => child.key
@@ -32,8 +46,8 @@ class Slot extends React.Component {
return changes.length !== 0;
}
getSlotProps(props = this.props) {
return omit(props, [
getPassthrough(props = this.props) {
const slotProps = omit(props, [
'fill',
'inline',
'className',
@@ -43,7 +57,45 @@ class Slot extends React.Component {
'queryData',
'childFactory',
'component',
'passthrough',
'dispatch',
]);
// @Deprecated
if (process.env.NODE_ENV !== 'production') {
if (Object.keys(slotProps).length) {
/* eslint-disable no-console */
console.warn(
`Slot '${
props.fill
}' passing through unknown props is deprecated, please use 'passthrough' instead`,
slotProps
);
/* eslint-enable no-console */
}
}
if (props.passthrough) {
return props.passthrough;
}
if (props.queryData) {
if (process.env.NODE_ENV !== 'production') {
/* eslint-disable no-console */
console.warn(
`Slot '${
props.fill
}' property 'queryData' is deprecated, please use 'passthrough' instead`
);
/* eslint-enable no-console */
}
return {
...props.queryData,
...slotProps,
};
}
return slotProps;
}
getChildren(props = this.props) {
@@ -53,8 +105,7 @@ class Slot extends React.Component {
return plugins.getSlotElements(
props.fill,
props.reduxState,
this.getSlotProps(props),
props.queryData,
this.getPassthrough(props),
{ size }
);
}
@@ -67,7 +118,6 @@ class Slot extends React.Component {
component: Component,
childFactory,
defaultComponent: DefaultComponent,
queryData,
fill,
} = this.props;
const { plugins } = this.context;
@@ -78,8 +128,7 @@ class Slot extends React.Component {
const props = plugins.getSlotComponentProps(
DefaultComponent,
reduxState,
this.getSlotProps(this.props),
queryData
this.getPassthrough()
);
children = <DefaultComponent {...props} />;
}
@@ -125,8 +174,12 @@ Slot.propTypes = {
component: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
// props coming from graphql must be passed through this property.
// @Deprecated
queryData: PropTypes.object,
// props that are passed to all Slot Components
passthrough: PropTypes.object,
/**
* You may need to apply reactive updates to a child as it is exiting.
* This is generally done by using `cloneElement` however in the case of an exiting
+44 -24
View File
@@ -67,39 +67,64 @@ function addMetaDataToSlotComponents(plugins) {
});
}
/**
* getSlotComponentProps calculate the props we would pass to the slot component.
* query datas are only passed to the component if it is defined in `component.fragments`.
*/
function getSlotComponentProps(component, reduxState, props, queryData) {
const pluginConfig = get(reduxState, 'config.plugin_config') || emptyConfig;
return {
...props,
config: pluginConfig,
...(component.fragments
? pick(queryData, Object.keys(component.fragments))
: withWarnings(component, queryData)),
};
}
/**
* splitProps detects props coming from the query and
* returns `queryData` and `rest`.
*/
function splitProps(props) {
const rest = { ...props };
const queryData = {};
if (props.passthrough) {
Object.keys(props).forEach(k => {
if (props[k].__unsafeFromQuery) {
queryData[k] = props[k];
delete rest[k];
}
});
}
return { queryData, rest };
}
class PluginsService {
constructor(plugins) {
this.plugins = plugins;
addMetaDataToSlotComponents(plugins);
}
isSlotEmpty(slot, reduxState, props = {}, queryData = {}) {
return (
this.getSlotElements(slot, reduxState, props, queryData).length === 0
);
isSlotEmpty(slot, reduxState, props = {}) {
return this.getSlotElements(slot, reduxState, props).length === 0;
}
/**
* getSlotComponentProps calculate the props we would pass to the slot component.
* query datas are only passed to the component if it is defined in `component.fragments`.
* Returns props that would pass to the given slot component.
*/
getSlotComponentProps(component, reduxState, props, queryData) {
const pluginConfig = get(reduxState, 'config.plugin_config') || emptyConfig;
return {
...props,
config: pluginConfig,
...(component.fragments
? pick(queryData, Object.keys(component.fragments))
: withWarnings(component, queryData)),
};
getSlotComponentProps(component, reduxState, props) {
const { queryData, rest } = splitProps(props);
return getSlotComponentProps(component, reduxState, rest, queryData);
}
/**
* Returns React Elements for given slot.
*/
getSlotElements(slot, reduxState, props = {}, queryData = {}, options = {}) {
getSlotElements(slot, reduxState, props = {}, options = {}) {
const pluginConfig = get(reduxState, 'config.plugin_config') || emptyConfig;
const { size = 0 } = options;
const { queryData, rest } = splitProps(props);
const isDisabled = component => {
if (
@@ -112,10 +137,10 @@ class PluginsService {
// Check if component is excluded.
if (component.isExcluded) {
let resolvedProps = this.getSlotComponentProps(
let resolvedProps = getSlotComponentProps(
component,
reduxState,
props,
rest,
queryData
);
if (component.mapStateToProps) {
@@ -154,12 +179,7 @@ class PluginsService {
.map(({ component, key }) =>
React.createElement(component, {
key,
...this.getSlotComponentProps(
component,
reduxState,
props,
queryData
),
...getSlotComponentProps(component, reduxState, rest, queryData),
})
);
}