Skip to content

Commit ca39dc5

Browse files
authored
Components: Deprecate withContext HOC and remove its usage (#8189)
* 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
1 parent 8085f2e commit ca39dc5

File tree

8 files changed

+228
-67
lines changed

8 files changed

+228
-67
lines changed

docs/reference/deprecated.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ Gutenberg's deprecation policy is intended to support backwards-compatibility fo
22

33
## 3.8.0
44

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

78
## 3.7.0
Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
/**
22
* WordPress dependencies
33
*/
4-
import { Component, RawHTML } from '@wordpress/element';
4+
import { createHigherOrderComponent } from '@wordpress/compose';
5+
import { createContext, RawHTML } from '@wordpress/element';
56

67
/**
78
* Internal dependencies
89
*/
910
import { serialize } from '../api';
1011

12+
const { Consumer, Provider } = createContext( () => {} );
13+
1114
/**
1215
* An internal block component used in block content serialization to inject
1316
* nested block content within the `save` implementation of the ancestor
@@ -22,29 +25,42 @@ import { serialize } from '../api';
2225
* { blockSaveElement }
2326
* </BlockContentProvider>
2427
* ```
28+
*
29+
* @return {WPElement} Element with BlockContent injected via context.
2530
*/
26-
class BlockContentProvider extends Component {
27-
getChildContext() {
28-
const { innerBlocks } = this.props;
29-
30-
return {
31-
BlockContent() {
32-
// Value is an array of blocks, so defer to block serializer
33-
const html = serialize( innerBlocks );
34-
35-
// Use special-cased raw HTML tag to avoid default escaping
36-
return <RawHTML>{ html }</RawHTML>;
37-
},
38-
};
39-
}
31+
const BlockContentProvider = ( { children, innerBlocks } ) => {
32+
const BlockContent = () => {
33+
// Value is an array of blocks, so defer to block serializer
34+
const html = serialize( innerBlocks );
4035

41-
render() {
42-
return this.props.children;
43-
}
44-
}
36+
// Use special-cased raw HTML tag to avoid default escaping
37+
return <RawHTML>{ html }</RawHTML>;
38+
};
4539

46-
BlockContentProvider.childContextTypes = {
47-
BlockContent: () => {},
40+
return (
41+
<Provider value={ BlockContent }>
42+
{ children }
43+
</Provider>
44+
);
4845
};
4946

47+
/**
48+
* A Higher Order Component used to inject BlockContent using context to the
49+
* wrapped component.
50+
*
51+
* @return {Component} Enhanced component with injected BlockContent as prop.
52+
*/
53+
export const withBlockContentContext = createHigherOrderComponent( ( OriginalComponent ) => {
54+
return ( props ) => (
55+
<Consumer>
56+
{ ( context ) => (
57+
<OriginalComponent
58+
{ ...props }
59+
BlockContent={ context }
60+
/>
61+
) }
62+
</Consumer>
63+
);
64+
}, 'withBlockContentContext' );
65+
5066
export default BlockContentProvider;

packages/blocks/src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
// and then stored as objects in state, from which it is then rendered for editing.
1010
import './store';
1111
export * from './api';
12+
export { withBlockContentContext } from './block-content-provider';

packages/components/src/higher-order/with-context/index.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,17 @@ import { noop } from 'lodash';
88
*/
99
import { Component } from '@wordpress/element';
1010
import { createHigherOrderComponent } from '@wordpress/compose';
11+
import deprecated from '@wordpress/deprecated';
1112

1213
export default ( contextName ) => ( mapSettingsToProps ) => createHigherOrderComponent(
1314
( OriginalComponent ) => {
15+
deprecated( 'wp.components.withContext', {
16+
version: '3.8',
17+
alternative: 'wp.element.createContext',
18+
plugin: 'Gutenberg',
19+
hint: 'https://reactjs.org/docs/context.html',
20+
} );
21+
1422
class WrappedComponent extends Component {
1523
render() {
1624
const extraProps = mapSettingsToProps ?

packages/components/src/higher-order/with-context/test/index.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@ import renderer from 'react-test-renderer';
55
import PropTypes from 'prop-types';
66

77
/**
8-
* Internal dependencies
8+
* WordPress dependencies
99
*/
10-
import withContext from '../';
10+
import { Component } from '@wordpress/element';
11+
import deprecated from '@wordpress/deprecated';
1112

1213
/**
13-
* WordPress dependencies
14+
* Internal dependencies
1415
*/
15-
import { Component } from '@wordpress/element';
16+
import withContext from '../';
17+
18+
jest.mock( '@wordpress/deprecated', () => jest.fn() );
1619

1720
class PassContext extends Component {
1821
getChildContext() {
@@ -39,6 +42,7 @@ describe( 'withContext', () => {
3942
);
4043

4144
expect( wrapper.root.findByType( 'div' ).children[ 0 ] ).toBe( 'ok' );
45+
expect( deprecated ).toHaveBeenCalled();
4246
} );
4347

4448
it( 'should allow specifying a context getter mapping', () => {

packages/editor/src/components/inner-blocks/index.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,10 @@ import classnames from 'classnames';
77
/**
88
* WordPress dependencies
99
*/
10-
import { withContext } from '@wordpress/components';
1110
import { withViewportMatch } from '@wordpress/viewport';
1211
import { Component } from '@wordpress/element';
1312
import { withSelect, withDispatch } from '@wordpress/data';
14-
import { synchronizeBlocksWithTemplate } from '@wordpress/blocks';
13+
import { synchronizeBlocksWithTemplate, withBlockContentContext } from '@wordpress/blocks';
1514
import isShallowEqual from '@wordpress/is-shallow-equal';
1615
import { compose } from '@wordpress/compose';
1716

@@ -149,10 +148,8 @@ InnerBlocks = compose( [
149148
} ),
150149
] )( InnerBlocks );
151150

152-
InnerBlocks.Content = ( { BlockContent } ) => {
153-
return <BlockContent />;
154-
};
155-
156-
InnerBlocks.Content = withContext( 'BlockContent' )()( InnerBlocks.Content );
151+
InnerBlocks.Content = withBlockContentContext(
152+
( { BlockContent } ) => <BlockContent />
153+
);
157154

158155
export default InnerBlocks;

packages/element/src/serialize.js

Lines changed: 72 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,34 @@ import {
4141
/**
4242
* Internal dependencies
4343
*/
44-
import { Fragment, RawHTML } from './';
44+
import {
45+
Fragment,
46+
StrictMode,
47+
} from './react';
48+
import RawHTML from './raw-html';
49+
50+
/**
51+
* Boolean reflecting whether the current environment supports Symbol.
52+
*
53+
* @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
54+
*
55+
* @type {boolean}
56+
*/
57+
const HAS_SYMBOL = typeof Symbol === 'function' && Symbol.for;
58+
59+
/**
60+
* Internal React symbol representing Provider type.
61+
*
62+
* @type {Symbol}
63+
*/
64+
const REACT_PROVIDER_TYPE = HAS_SYMBOL ? Symbol.for( 'react.provider' ) : 0xeacd;
65+
66+
/**
67+
* Internal React symbol representing context (Consumer) type.
68+
*
69+
* @type {Symbol}
70+
*/
71+
const REACT_CONTEXT_TYPE = HAS_SYMBOL ? Symbol.for( 'react.context' ) : 0xeace;
4572

4673
/**
4774
* Valid attribute types.
@@ -430,18 +457,19 @@ function getNormalStylePropertyValue( property, value ) {
430457
/**
431458
* Serializes a React element to string.
432459
*
433-
* @param {WPElement} element Element to serialize.
434-
* @param {?Object} context Context object.
460+
* @param {WPElement} element Element to serialize.
461+
* @param {?Object} context Context object.
462+
* @param {?Object} legacyContext Legacy context object.
435463
*
436464
* @return {string} Serialized element.
437465
*/
438-
export function renderElement( element, context = {} ) {
466+
export function renderElement( element, context, legacyContext = {} ) {
439467
if ( null === element || undefined === element || false === element ) {
440468
return '';
441469
}
442470

443471
if ( Array.isArray( element ) ) {
444-
return renderChildren( element, context );
472+
return renderChildren( element, context, legacyContext );
445473
}
446474

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

455-
const { type: tagName, props } = element;
483+
const { type, props } = element;
456484

457-
switch ( tagName ) {
485+
switch ( type ) {
486+
case StrictMode:
458487
case Fragment:
459-
return renderChildren( props.children, context );
488+
return renderChildren( props.children, context, legacyContext );
460489

461490
case RawHTML:
462491
const { children, ...wrapperProps } = props;
@@ -467,20 +496,28 @@ export function renderElement( element, context = {} ) {
467496
...wrapperProps,
468497
dangerouslySetInnerHTML: { __html: children },
469498
},
470-
context
499+
context,
500+
legacyContext
471501
);
472502
}
473503

474-
switch ( typeof tagName ) {
504+
switch ( typeof type ) {
475505
case 'string':
476-
return renderNativeComponent( tagName, props, context );
506+
return renderNativeComponent( type, props, context, legacyContext );
477507

478508
case 'function':
479-
if ( tagName.prototype && typeof tagName.prototype.render === 'function' ) {
480-
return renderComponent( tagName, props, context );
509+
if ( type.prototype && typeof type.prototype.render === 'function' ) {
510+
return renderComponent( type, props, context, legacyContext );
481511
}
482512

483-
return renderElement( tagName( props, context ), context );
513+
return renderElement( type( props, legacyContext ), context, legacyContext );
514+
}
515+
switch ( type && type.$$typeof ) {
516+
case REACT_PROVIDER_TYPE:
517+
return renderChildren( props.children, props.value, legacyContext );
518+
519+
case REACT_CONTEXT_TYPE:
520+
return renderElement( props.children( context || type._currentValue ), context, legacyContext );
484521
}
485522

486523
return '';
@@ -489,27 +526,28 @@ export function renderElement( element, context = {} ) {
489526
/**
490527
* Serializes a native component type to string.
491528
*
492-
* @param {?string} type Native component type to serialize, or null if
493-
* rendering as fragment of children content.
494-
* @param {Object} props Props object.
495-
* @param {?Object} context Context object.
529+
* @param {?string} type Native component type to serialize, or null if
530+
* rendering as fragment of children content.
531+
* @param {Object} props Props object.
532+
* @param {?Object} context Context object.
533+
* @param {?Object} legacyContext Legacy context object.
496534
*
497535
* @return {string} Serialized element.
498536
*/
499-
export function renderNativeComponent( type, props, context = {} ) {
537+
export function renderNativeComponent( type, props, context, legacyContext = {} ) {
500538
let content = '';
501539
if ( type === 'textarea' && props.hasOwnProperty( 'value' ) ) {
502540
// Textarea children can be assigned as value prop. If it is, render in
503541
// place of children. Ensure to omit so it is not assigned as attribute
504542
// as well.
505-
content = renderChildren( props.value, context );
543+
content = renderChildren( props.value, context, legacyContext );
506544
props = omit( props, 'value' );
507545
} else if ( props.dangerouslySetInnerHTML &&
508546
typeof props.dangerouslySetInnerHTML.__html === 'string' ) {
509547
// Dangerous content is left unescaped.
510548
content = props.dangerouslySetInnerHTML.__html;
511549
} else if ( typeof props.children !== 'undefined' ) {
512-
content = renderChildren( props.children, context );
550+
content = renderChildren( props.children, context, legacyContext );
513551
}
514552

515553
if ( ! type ) {
@@ -528,41 +566,43 @@ export function renderNativeComponent( type, props, context = {} ) {
528566
/**
529567
* Serializes a non-native component type to string.
530568
*
531-
* @param {Function} Component Component type to serialize.
532-
* @param {Object} props Props object.
533-
* @param {?Object} context Context object.
569+
* @param {Function} Component Component type to serialize.
570+
* @param {Object} props Props object.
571+
* @param {?Object} context Context object.
572+
* @param {?Object} legacyContext Legacy context object.
534573
*
535574
* @return {string} Serialized element
536575
*/
537-
export function renderComponent( Component, props, context = {} ) {
538-
const instance = new Component( props, context );
576+
export function renderComponent( Component, props, context, legacyContext = {} ) {
577+
const instance = new Component( props, legacyContext );
539578

540579
if ( typeof instance.getChildContext === 'function' ) {
541-
Object.assign( context, instance.getChildContext() );
580+
Object.assign( legacyContext, instance.getChildContext() );
542581
}
543582

544-
const html = renderElement( instance.render(), context );
583+
const html = renderElement( instance.render(), context, legacyContext );
545584

546585
return html;
547586
}
548587

549588
/**
550589
* Serializes an array of children to string.
551590
*
552-
* @param {Array} children Children to serialize.
553-
* @param {?Object} context Context object.
591+
* @param {Array} children Children to serialize.
592+
* @param {?Object} context Context object.
593+
* @param {?Object} legacyContext Legacy context object.
554594
*
555595
* @return {string} Serialized children.
556596
*/
557-
function renderChildren( children, context = {} ) {
597+
function renderChildren( children, context, legacyContext = {} ) {
558598
let result = '';
559599

560600
children = castArray( children );
561601

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

565-
result += renderElement( child, context );
605+
result += renderElement( child, context, legacyContext );
566606
}
567607

568608
return result;

0 commit comments

Comments
 (0)