Skip to content

Commit

Permalink
Components: Deprecate withContext HOC and remove its usage (#8189)
Browse files Browse the repository at this point in the history
* Components: Deprecate withContext HOC

* Element: Add support for Context API in serializer

* Element: Make serializer work with nested context consumers and providers

* Element: Improve detection for Context API components

* Components: Update deprecation version for wp.component.withContext

* Blocks: Simplify BlockContent context HOC implementation

* Blocks: Addressing feedback from review

* Element: Add test for serialization with nested providers

* Test: Fix test description in serializer test suite

* Tests: Mock deprecated when testing if it was called
  • Loading branch information
gziolo authored Aug 14, 2018
1 parent 8085f2e commit ca39dc5
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 67 deletions.
1 change: 1 addition & 0 deletions docs/reference/deprecated.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ Gutenberg's deprecation policy is intended to support backwards-compatibility fo

## 3.8.0

- `wp.components.withContext` has been removed. Please use `wp.element.createContext` instead. See: https://reactjs.org/docs/context.html.
- `wp.coreBlocks.registerCoreBlocks` has been removed. Please use `wp.blockLibrary.registerCoreBlocks` instead.

## 3.7.0
Expand Down
58 changes: 37 additions & 21 deletions packages/blocks/src/block-content-provider/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
/**
* WordPress dependencies
*/
import { Component, RawHTML } from '@wordpress/element';
import { createHigherOrderComponent } from '@wordpress/compose';
import { createContext, RawHTML } from '@wordpress/element';

/**
* Internal dependencies
*/
import { serialize } from '../api';

const { Consumer, Provider } = createContext( () => {} );

/**
* An internal block component used in block content serialization to inject
* nested block content within the `save` implementation of the ancestor
Expand All @@ -22,29 +25,42 @@ import { serialize } from '../api';
* { blockSaveElement }
* </BlockContentProvider>
* ```
*
* @return {WPElement} Element with BlockContent injected via context.
*/
class BlockContentProvider extends Component {
getChildContext() {
const { innerBlocks } = this.props;

return {
BlockContent() {
// Value is an array of blocks, so defer to block serializer
const html = serialize( innerBlocks );

// Use special-cased raw HTML tag to avoid default escaping
return <RawHTML>{ html }</RawHTML>;
},
};
}
const BlockContentProvider = ( { children, innerBlocks } ) => {
const BlockContent = () => {
// Value is an array of blocks, so defer to block serializer
const html = serialize( innerBlocks );

render() {
return this.props.children;
}
}
// Use special-cased raw HTML tag to avoid default escaping
return <RawHTML>{ html }</RawHTML>;
};

BlockContentProvider.childContextTypes = {
BlockContent: () => {},
return (
<Provider value={ BlockContent }>
{ children }
</Provider>
);
};

/**
* A Higher Order Component used to inject BlockContent using context to the
* wrapped component.
*
* @return {Component} Enhanced component with injected BlockContent as prop.
*/
export const withBlockContentContext = createHigherOrderComponent( ( OriginalComponent ) => {
return ( props ) => (
<Consumer>
{ ( context ) => (
<OriginalComponent
{ ...props }
BlockContent={ context }
/>
) }
</Consumer>
);
}, 'withBlockContentContext' );

export default BlockContentProvider;
1 change: 1 addition & 0 deletions packages/blocks/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
// and then stored as objects in state, from which it is then rendered for editing.
import './store';
export * from './api';
export { withBlockContentContext } from './block-content-provider';
8 changes: 8 additions & 0 deletions packages/components/src/higher-order/with-context/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@ import { noop } from 'lodash';
*/
import { Component } from '@wordpress/element';
import { createHigherOrderComponent } from '@wordpress/compose';
import deprecated from '@wordpress/deprecated';

export default ( contextName ) => ( mapSettingsToProps ) => createHigherOrderComponent(
( OriginalComponent ) => {
deprecated( 'wp.components.withContext', {
version: '3.8',
alternative: 'wp.element.createContext',
plugin: 'Gutenberg',
hint: 'https://reactjs.org/docs/context.html',
} );

class WrappedComponent extends Component {
render() {
const extraProps = mapSettingsToProps ?
Expand Down
12 changes: 8 additions & 4 deletions packages/components/src/higher-order/with-context/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ import renderer from 'react-test-renderer';
import PropTypes from 'prop-types';

/**
* Internal dependencies
* WordPress dependencies
*/
import withContext from '../';
import { Component } from '@wordpress/element';
import deprecated from '@wordpress/deprecated';

/**
* WordPress dependencies
* Internal dependencies
*/
import { Component } from '@wordpress/element';
import withContext from '../';

jest.mock( '@wordpress/deprecated', () => jest.fn() );

class PassContext extends Component {
getChildContext() {
Expand All @@ -39,6 +42,7 @@ describe( 'withContext', () => {
);

expect( wrapper.root.findByType( 'div' ).children[ 0 ] ).toBe( 'ok' );
expect( deprecated ).toHaveBeenCalled();
} );

it( 'should allow specifying a context getter mapping', () => {
Expand Down
11 changes: 4 additions & 7 deletions packages/editor/src/components/inner-blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { withContext } from '@wordpress/components';
import { withViewportMatch } from '@wordpress/viewport';
import { Component } from '@wordpress/element';
import { withSelect, withDispatch } from '@wordpress/data';
import { synchronizeBlocksWithTemplate } from '@wordpress/blocks';
import { synchronizeBlocksWithTemplate, withBlockContentContext } from '@wordpress/blocks';
import isShallowEqual from '@wordpress/is-shallow-equal';
import { compose } from '@wordpress/compose';

Expand Down Expand Up @@ -149,10 +148,8 @@ InnerBlocks = compose( [
} ),
] )( InnerBlocks );

InnerBlocks.Content = ( { BlockContent } ) => {
return <BlockContent />;
};

InnerBlocks.Content = withContext( 'BlockContent' )()( InnerBlocks.Content );
InnerBlocks.Content = withBlockContentContext(
( { BlockContent } ) => <BlockContent />
);

export default InnerBlocks;
104 changes: 72 additions & 32 deletions packages/element/src/serialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,34 @@ import {
/**
* Internal dependencies
*/
import { Fragment, RawHTML } from './';
import {
Fragment,
StrictMode,
} from './react';
import RawHTML from './raw-html';

/**
* Boolean reflecting whether the current environment supports Symbol.
*
* @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
*
* @type {boolean}
*/
const HAS_SYMBOL = typeof Symbol === 'function' && Symbol.for;

/**
* Internal React symbol representing Provider type.
*
* @type {Symbol}
*/
const REACT_PROVIDER_TYPE = HAS_SYMBOL ? Symbol.for( 'react.provider' ) : 0xeacd;

/**
* Internal React symbol representing context (Consumer) type.
*
* @type {Symbol}
*/
const REACT_CONTEXT_TYPE = HAS_SYMBOL ? Symbol.for( 'react.context' ) : 0xeace;

/**
* Valid attribute types.
Expand Down Expand Up @@ -430,18 +457,19 @@ function getNormalStylePropertyValue( property, value ) {
/**
* Serializes a React element to string.
*
* @param {WPElement} element Element to serialize.
* @param {?Object} context Context object.
* @param {WPElement} element Element to serialize.
* @param {?Object} context Context object.
* @param {?Object} legacyContext Legacy context object.
*
* @return {string} Serialized element.
*/
export function renderElement( element, context = {} ) {
export function renderElement( element, context, legacyContext = {} ) {
if ( null === element || undefined === element || false === element ) {
return '';
}

if ( Array.isArray( element ) ) {
return renderChildren( element, context );
return renderChildren( element, context, legacyContext );
}

switch ( typeof element ) {
Expand All @@ -452,11 +480,12 @@ export function renderElement( element, context = {} ) {
return element.toString();
}

const { type: tagName, props } = element;
const { type, props } = element;

switch ( tagName ) {
switch ( type ) {
case StrictMode:
case Fragment:
return renderChildren( props.children, context );
return renderChildren( props.children, context, legacyContext );

case RawHTML:
const { children, ...wrapperProps } = props;
Expand All @@ -467,20 +496,28 @@ export function renderElement( element, context = {} ) {
...wrapperProps,
dangerouslySetInnerHTML: { __html: children },
},
context
context,
legacyContext
);
}

switch ( typeof tagName ) {
switch ( typeof type ) {
case 'string':
return renderNativeComponent( tagName, props, context );
return renderNativeComponent( type, props, context, legacyContext );

case 'function':
if ( tagName.prototype && typeof tagName.prototype.render === 'function' ) {
return renderComponent( tagName, props, context );
if ( type.prototype && typeof type.prototype.render === 'function' ) {
return renderComponent( type, props, context, legacyContext );
}

return renderElement( tagName( props, context ), context );
return renderElement( type( props, legacyContext ), context, legacyContext );
}
switch ( type && type.$$typeof ) {
case REACT_PROVIDER_TYPE:
return renderChildren( props.children, props.value, legacyContext );

case REACT_CONTEXT_TYPE:
return renderElement( props.children( context || type._currentValue ), context, legacyContext );
}

return '';
Expand All @@ -489,27 +526,28 @@ export function renderElement( element, context = {} ) {
/**
* Serializes a native component type to string.
*
* @param {?string} type Native component type to serialize, or null if
* rendering as fragment of children content.
* @param {Object} props Props object.
* @param {?Object} context Context object.
* @param {?string} type Native component type to serialize, or null if
* rendering as fragment of children content.
* @param {Object} props Props object.
* @param {?Object} context Context object.
* @param {?Object} legacyContext Legacy context object.
*
* @return {string} Serialized element.
*/
export function renderNativeComponent( type, props, context = {} ) {
export function renderNativeComponent( type, props, context, legacyContext = {} ) {
let content = '';
if ( type === 'textarea' && props.hasOwnProperty( 'value' ) ) {
// Textarea children can be assigned as value prop. If it is, render in
// place of children. Ensure to omit so it is not assigned as attribute
// as well.
content = renderChildren( props.value, context );
content = renderChildren( props.value, context, legacyContext );
props = omit( props, 'value' );
} else if ( props.dangerouslySetInnerHTML &&
typeof props.dangerouslySetInnerHTML.__html === 'string' ) {
// Dangerous content is left unescaped.
content = props.dangerouslySetInnerHTML.__html;
} else if ( typeof props.children !== 'undefined' ) {
content = renderChildren( props.children, context );
content = renderChildren( props.children, context, legacyContext );
}

if ( ! type ) {
Expand All @@ -528,41 +566,43 @@ export function renderNativeComponent( type, props, context = {} ) {
/**
* Serializes a non-native component type to string.
*
* @param {Function} Component Component type to serialize.
* @param {Object} props Props object.
* @param {?Object} context Context object.
* @param {Function} Component Component type to serialize.
* @param {Object} props Props object.
* @param {?Object} context Context object.
* @param {?Object} legacyContext Legacy context object.
*
* @return {string} Serialized element
*/
export function renderComponent( Component, props, context = {} ) {
const instance = new Component( props, context );
export function renderComponent( Component, props, context, legacyContext = {} ) {
const instance = new Component( props, legacyContext );

if ( typeof instance.getChildContext === 'function' ) {
Object.assign( context, instance.getChildContext() );
Object.assign( legacyContext, instance.getChildContext() );
}

const html = renderElement( instance.render(), context );
const html = renderElement( instance.render(), context, legacyContext );

return html;
}

/**
* Serializes an array of children to string.
*
* @param {Array} children Children to serialize.
* @param {?Object} context Context object.
* @param {Array} children Children to serialize.
* @param {?Object} context Context object.
* @param {?Object} legacyContext Legacy context object.
*
* @return {string} Serialized children.
*/
function renderChildren( children, context = {} ) {
function renderChildren( children, context, legacyContext = {} ) {
let result = '';

children = castArray( children );

for ( let i = 0; i < children.length; i++ ) {
const child = children[ i ];

result += renderElement( child, context );
result += renderElement( child, context, legacyContext );
}

return result;
Expand Down
Loading

0 comments on commit ca39dc5

Please sign in to comment.