Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Components: Deprecate withContext HOC and remove its usage #8189

Merged
merged 10 commits into from
Aug 14, 2018
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
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ describe( 'withContext', () => {
);

expect( wrapper.root.findByType( 'div' ).children[ 0 ] ).toBe( 'ok' );
expect( console ).toHaveWarned();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Question came up from @danielbachhuber about testing with deprecated warnings, and potential for conflicts with its internal memoization. Made me think over the weekend that the fact we test against console logging is a bit of an abstraction piercing; while it seems obvious deprecated would log to console by its description, we can't know for certain, and seems we should test that deprecated was merely called, regardless of its internal implementation.

I'd like to think this would be as simple as? ...

const deprecated = jest.mock( '@wordpress/deprecated' );

// ...

expect( deprecated ).toHaveBeenCalled();

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should need:

import deprecated from '@wordpress/deprecated';

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

expect( deprecated ).toHaveBeenCalled();

I can give it a try.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored in cf41540.

} );

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