From 3e7e2a915824a39fcd13897717733b0a1ecd0158 Mon Sep 17 00:00:00 2001 From: Pablo Palacios Date: Wed, 11 Nov 2020 21:10:38 +0100 Subject: [PATCH] refactor: Replace obsolete react context (#663) --- .../src/FluxibleComponent.js | 23 +++-------- .../src/FluxibleContext.js | 39 +++++++++++++++++++ .../src/connectToStores.js | 11 ++---- .../src/createElementWithContext.js | 17 ++++---- packages/fluxible-addons-react/src/index.js | 1 + .../src/provideContext.js | 31 ++++----------- .../tests/unit/lib/connectToStores.js | 6 +-- .../tests/unit/lib/provideContext.js | 15 +++---- .../src/lib/createNavLinkComponent.js | 13 ++----- .../fluxible-router/src/lib/handleHistory.js | 9 ++--- .../fluxible-router/src/lib/handleRoute.js | 6 +-- .../tests/mocks/MockAppComponent.js | 27 ++++++------- .../tests/unit/lib/NavLink-test.js | 17 +++----- .../tests/unit/lib/handleHistory-test.js | 6 +-- 14 files changed, 99 insertions(+), 122 deletions(-) create mode 100644 packages/fluxible-addons-react/src/FluxibleContext.js diff --git a/packages/fluxible-addons-react/src/FluxibleComponent.js b/packages/fluxible-addons-react/src/FluxibleComponent.js index 3017d809..482a45b4 100644 --- a/packages/fluxible-addons-react/src/FluxibleComponent.js +++ b/packages/fluxible-addons-react/src/FluxibleComponent.js @@ -2,21 +2,15 @@ * Copyright 2015, Yahoo Inc. * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. */ -import { Component, cloneElement } from 'react'; -import { func, object, node } from 'prop-types'; +import { Component, cloneElement, createElement } from 'react'; +import { object, node } from 'prop-types'; +import { FluxibleProvider } from './FluxibleContext'; class FluxibleComponent extends Component { - getChildContext() { - return { - getStore: this.props.context.getStore, - executeAction: this.props.context.executeAction - }; - } - render() { - return cloneElement(this.props.children, { - context: this.props.context - }); + const { children, context } = this.props; + const childrenWithContext = cloneElement(children, { context }); + return createElement(FluxibleProvider, { context }, childrenWithContext); } } @@ -25,9 +19,4 @@ FluxibleComponent.propTypes = { context: object.isRequired }; -FluxibleComponent.childContextTypes = { - executeAction: func.isRequired, - getStore: func.isRequired -}; - export default FluxibleComponent; diff --git a/packages/fluxible-addons-react/src/FluxibleContext.js b/packages/fluxible-addons-react/src/FluxibleContext.js new file mode 100644 index 00000000..b06d3ea7 --- /dev/null +++ b/packages/fluxible-addons-react/src/FluxibleContext.js @@ -0,0 +1,39 @@ +import { Component, createContext, createElement } from 'react'; +import { arrayOf, node, object, string } from 'prop-types'; + +export const FluxibleContext = createContext({ + executeAction: () => {}, + getStore: () => {}, +}); + +export class FluxibleProvider extends Component { + constructor(props) { + super(props); + + const state = { + executeAction: this.props.context.executeAction, + getStore: this.props.context.getStore, + }; + + this.props.plugins.forEach(plugin => { + state[plugin] = this.props.context[plugin]; + }); + + this.state = state; + } + + render() { + const props = { value: this.state }; + return createElement(FluxibleContext.Provider, props, this.props.children); + } +} + +FluxibleProvider.propTypes = { + children: node.isRequired, + context: object.isRequired, + plugins: arrayOf(string) +}; + +FluxibleProvider.defaultProps = { + plugins: [] +}; diff --git a/packages/fluxible-addons-react/src/connectToStores.js b/packages/fluxible-addons-react/src/connectToStores.js index 2995522f..f68a7a03 100644 --- a/packages/fluxible-addons-react/src/connectToStores.js +++ b/packages/fluxible-addons-react/src/connectToStores.js @@ -3,8 +3,8 @@ * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. */ import { Component as ReactComponent, createRef, createElement } from 'react'; -import { func } from 'prop-types'; import hoistNonReactStatics from 'hoist-non-react-statics'; +import { FluxibleContext } from './FluxibleContext'; /** * Registers change listeners and retrieves state from stores using the `getStateFromStores` @@ -25,11 +25,9 @@ import hoistNonReactStatics from 'hoist-non-react-statics'; * @param {array} stores List of stores to listen for changes * @param {function} getStateFromStores function that receives all stores and should return * the full state object. Receives `stores` hash and component `props` as arguments - * @param {Object} [customContextTypes] additional `contextTypes` that could be accessed from your `getStateFromStores` - * function * @returns {React.Component} or {Function} if using decorator pattern */ -function connectToStores(Component, stores, getStateFromStores, customContextTypes) { +function connectToStores(Component, stores, getStateFromStores) { class StoreConnector extends ReactComponent { constructor(props, context) { super(props, context); @@ -75,10 +73,7 @@ function connectToStores(Component, stores, getStateFromStores, customContextTyp StoreConnector.displayName = `storeConnector(${Component.displayName || Component.name || 'Component'})`; - StoreConnector.contextTypes = { - getStore: func.isRequired, - ...customContextTypes - }, + StoreConnector.contextType = FluxibleContext; StoreConnector.WrappedComponent = Component; diff --git a/packages/fluxible-addons-react/src/createElementWithContext.js b/packages/fluxible-addons-react/src/createElementWithContext.js index 56ecefbe..49e6bdf6 100644 --- a/packages/fluxible-addons-react/src/createElementWithContext.js +++ b/packages/fluxible-addons-react/src/createElementWithContext.js @@ -17,18 +17,15 @@ function createElementWithContext(fluxibleContext, props) { if (!Component) { throw new Error('A top-level component was not passed to the Fluxible constructor.'); } + + const context = fluxibleContext.getComponentContext(); + if (Component.displayName && Component.displayName.includes('contextProvider')) { - return createElement( - Component, - {context: fluxibleContext.getComponentContext(), ...props}, - ); + return createElement(Component, { context, ...props}); } - const componentInstance = createElement(Component, props); - return createElement( - FluxibleComponent, - {context: fluxibleContext.getComponentContext()}, - componentInstance - ); + + const children = createElement(Component, props); + return createElement(FluxibleComponent, { context }, children); } export default createElementWithContext; diff --git a/packages/fluxible-addons-react/src/index.js b/packages/fluxible-addons-react/src/index.js index 33732224..8be5d60c 100644 --- a/packages/fluxible-addons-react/src/index.js +++ b/packages/fluxible-addons-react/src/index.js @@ -7,3 +7,4 @@ export { default as batchedUpdatePlugin } from './batchedUpdatePlugin'; export { default as connectToStores } from './connectToStores'; export { default as createElementWithContext } from './createElementWithContext'; export { default as provideContext } from './provideContext'; +export { FluxibleContext } from './FluxibleContext'; diff --git a/packages/fluxible-addons-react/src/provideContext.js b/packages/fluxible-addons-react/src/provideContext.js index 06b4748f..4e08c8be 100644 --- a/packages/fluxible-addons-react/src/provideContext.js +++ b/packages/fluxible-addons-react/src/provideContext.js @@ -5,6 +5,7 @@ import { Component as ReactComponent, createRef, createElement } from 'react'; import { func, object } from 'prop-types'; import hoistNonReactStatics from 'hoist-non-react-statics'; +import { FluxibleProvider } from './FluxibleContext'; /** * Provides context prop to all children as React context @@ -16,43 +17,27 @@ import hoistNonReactStatics from 'hoist-non-react-statics'; * * @method provideContext * @param {React.Component} [Component] component to wrap - * @param {object} customContextTypes Custom contextTypes to add - * @returns {React.Component} or {Function} if using decorator pattern + * @param {array} [plugins] list of plugins names to inject into the context + * @returns {React.Component} */ -function provideContext(Component, customContextTypes) { +function provideContext(Component, plugins) { class ContextProvider extends ReactComponent { constructor(props) { super(props); this.wrappedElementRef = createRef(); } - getChildContext() { - const childContext = { - executeAction: this.props.context.executeAction, - getStore: this.props.context.getStore - }; - if (customContextTypes) { - Object.keys(customContextTypes).forEach(key => { - childContext[key] = this.props.context[key]; - }); - } - return childContext; - } - render() { const props = (Component.prototype && Component.prototype.isReactComponent) ? {ref: this.wrappedElementRef} : null; - return createElement(Component, {...this.props, ...props}); + + const { context } = this.props; + const children = createElement(Component, {...this.props, ...props}); + return createElement(FluxibleProvider, { context, plugins }, children); } } - ContextProvider.childContextTypes = { - executeAction: func.isRequired, - getStore: func.isRequired, - ...customContextTypes - }; - ContextProvider.propTypes = { context: object.isRequired }; diff --git a/packages/fluxible-addons-react/tests/unit/lib/connectToStores.js b/packages/fluxible-addons-react/tests/unit/lib/connectToStores.js index 3a25ffca..361e8f03 100644 --- a/packages/fluxible-addons-react/tests/unit/lib/connectToStores.js +++ b/packages/fluxible-addons-react/tests/unit/lib/connectToStores.js @@ -8,7 +8,7 @@ import PropTypes from 'prop-types'; import { JSDOM } from 'jsdom'; import createMockComponentContext from 'fluxible/utils/createMockComponentContext'; -import { connectToStores, provideContext } from '../../../'; +import { connectToStores, provideContext, FluxibleContext } from '../../../'; import FooStore from '../../fixtures/stores/FooStore'; import BarStore from '../../fixtures/stores/BarStore'; @@ -35,9 +35,7 @@ describe('fluxible-addons-react', () => { it('should get the state from the stores', (done) => { class Component extends React.Component { - static contextTypes = { - executeAction: PropTypes.func.isRequired, - } + static contextType = FluxibleContext constructor() { super(); diff --git a/packages/fluxible-addons-react/tests/unit/lib/provideContext.js b/packages/fluxible-addons-react/tests/unit/lib/provideContext.js index 3b9375d5..682ca793 100644 --- a/packages/fluxible-addons-react/tests/unit/lib/provideContext.js +++ b/packages/fluxible-addons-react/tests/unit/lib/provideContext.js @@ -7,7 +7,7 @@ import { renderToString } from 'react-dom/server'; import PropTypes from 'prop-types'; import { JSDOM } from 'jsdom'; -import { provideContext } from '../../../'; +import { provideContext, FluxibleContext } from '../../../'; describe('fluxible-addons-react', () => { describe('provideContext', () => { @@ -53,6 +53,7 @@ describe('fluxible-addons-react', () => { }); it('should provide the context with custom types to children', () => { + const plugins = ['foo']; const context = { foo: 'bar', executeAction: function() {}, @@ -60,23 +61,17 @@ describe('fluxible-addons-react', () => { }; class Component extends React.Component { - static contextTypes = { - foo: PropTypes.string.isRequired, - executeAction: PropTypes.func.isRequired, - getStore: PropTypes.func.isRequired - } + static contextType = FluxibleContext; render() { - expect(this.context.foo).to.equal(context.foo); expect(this.context.executeAction).to.equal(context.executeAction); expect(this.context.getStore).to.equal(context.getStore); + expect(this.context.foo).to.equal(context.foo); return null; } } - const WrappedComponent = provideContext(Component, { - foo: PropTypes.string - }); + const WrappedComponent = provideContext(Component, plugins); renderToString(); }); diff --git a/packages/fluxible-router/src/lib/createNavLinkComponent.js b/packages/fluxible-router/src/lib/createNavLinkComponent.js index 8a5443a2..3310a682 100644 --- a/packages/fluxible-router/src/lib/createNavLinkComponent.js +++ b/packages/fluxible-router/src/lib/createNavLinkComponent.js @@ -7,6 +7,7 @@ 'use strict'; var React = require('react'); var PropTypes = require('prop-types'); +var { FluxibleContext } = require('fluxible-addons-react'); var RouteStore = require('./RouteStore'); var debug = require('debug')('NavLink'); var navigateAction = require('./navigateAction'); @@ -255,8 +256,7 @@ class NavLink extends React.Component { try { onBeforeUnloadText = window.onbeforeunload(); } catch(error) { - var logWarn = (this.context.logger && this.context.logger.warn) || console.warn; - logWarn('Warning: Call of window.onbeforeunload failed', error); + console.warn('Warning: Call of window.onbeforeunload failed', error); } } var confirmResult = onBeforeUnloadText ? window.confirm(onBeforeUnloadText) : true; @@ -291,8 +291,7 @@ class NavLink extends React.Component { throw new Error('NavLink created with empty or missing href \'' + props.href + '\'or unresolvable routeName \'' + props.routeName); } else { - var logError = (this.context.logger && this.context.logger.error) || console.error; - logError('Error: Render NavLink with empty or missing href', props); + console.error('Error: Render NavLink with empty or missing href', props); } } @@ -361,11 +360,7 @@ class NavLink extends React.Component { NavLink._isMounted = false; NavLink.autobind = false; NavLink.displayName = 'NavLink'; -NavLink.contextTypes = { - executeAction: PropTypes.func.isRequired, - getStore: PropTypes.func.isRequired, - logger: PropTypes.object -} +NavLink.contextType = FluxibleContext; NavLink.propTypes = { href: PropTypes.string, stopPropagation: PropTypes.bool, diff --git a/packages/fluxible-router/src/lib/handleHistory.js b/packages/fluxible-router/src/lib/handleHistory.js index 86177365..04610fbe 100644 --- a/packages/fluxible-router/src/lib/handleHistory.js +++ b/packages/fluxible-router/src/lib/handleHistory.js @@ -7,6 +7,7 @@ var React = require('react'); var PropTypes = require('prop-types'); var debug = require('debug')('FluxibleRouter:handleHistory'); +var { FluxibleContext } = require('fluxible-addons-react'); var handleRoute = require('../lib/handleRoute'); var navigateAction = require('../lib/navigateAction'); var History = require('./History'); @@ -59,10 +60,7 @@ function createComponent(Component, opts) { inherits(HistoryHandler, React.Component); HistoryHandler.displayName = 'HistoryHandler'; - HistoryHandler.contextTypes = { - executeAction: PropTypes.func.isRequired, - logger: PropTypes.object - }; + HistoryHandler.contextType = FluxibleContext; HistoryHandler.propTypes = { currentRoute: PropTypes.object, currentNavigate: PropTypes.object @@ -168,8 +166,7 @@ function createComponent(Component, opts) { try { onBeforeUnloadText = window.onbeforeunload(); } catch(error) { - var logWarn = (this.context.logger && this.context.logger.warn) || console.warn; - logWarn('Warning: Call of window.onbeforeunload failed', error); + console.warn('Warning: Call of window.onbeforeunload failed', error); } } var confirmResult = onBeforeUnloadText ? window.confirm(onBeforeUnloadText) : true; diff --git a/packages/fluxible-router/src/lib/handleRoute.js b/packages/fluxible-router/src/lib/handleRoute.js index 13902e94..0a0ff383 100644 --- a/packages/fluxible-router/src/lib/handleRoute.js +++ b/packages/fluxible-router/src/lib/handleRoute.js @@ -7,7 +7,7 @@ 'use strict'; var React = require('react'); var PropTypes = require('prop-types'); -var connectToStores = require('fluxible-addons-react').connectToStores; +var { connectToStores, FluxibleContext } = require('fluxible-addons-react'); var hoistNonReactStatics = require('hoist-non-react-statics'); var inherits = require('inherits'); @@ -19,9 +19,7 @@ function createComponent(Component) { inherits(RouteHandler, React.Component); RouteHandler.displayName = 'RouteHandler'; - RouteHandler.contextTypes = { - getStore: PropTypes.func.isRequired - }; + RouteHandler.contextType = FluxibleContext; RouteHandler.propTypes = { currentRoute: PropTypes.object, currentNavigate: PropTypes.object, diff --git a/packages/fluxible-router/tests/mocks/MockAppComponent.js b/packages/fluxible-router/tests/mocks/MockAppComponent.js index 151e15cc..07d9dfbc 100644 --- a/packages/fluxible-router/tests/mocks/MockAppComponent.js +++ b/packages/fluxible-router/tests/mocks/MockAppComponent.js @@ -5,19 +5,12 @@ 'use strict'; var React = require('react'); var PropTypes = require('prop-types'); -var provideContext = require('fluxible-addons-react').provideContext; +var { provideContext, FluxibleContext } = require('fluxible-addons-react'); var handleHistory = require('../../dist/lib/handleHistory'); var createReactClass = require('create-react-class'); -var MockAppComponent = createReactClass({ - contextTypes: { - getStore: PropTypes.func.isRequired - }, - propTypes: { - children: PropTypes.object, - currentRoute: PropTypes.object - }, - render: function () { +class MockAppComponent extends React.Component { + render() { if (!this.props.children) { return null; } @@ -25,16 +18,20 @@ var MockAppComponent = createReactClass({ currentRoute: this.props.currentRoute }); } -}); +} -var customContextTypes = { - logger: PropTypes.object +MockAppComponent.contextType = FluxibleContext; + +MockAppComponent.propTypes = { + children: PropTypes.object, + currentRoute: PropTypes.object }; + module.exports = provideContext(handleHistory(MockAppComponent, { checkRouteOnPageLoad: false, enableScroll: true -}), customContextTypes); +})); module.exports.createWrappedMockAppComponent = function createWrappedMockAppComponent(opts) { - return provideContext(handleHistory(MockAppComponent, opts), customContextTypes); + return provideContext(handleHistory(MockAppComponent, opts)); }; diff --git a/packages/fluxible-router/tests/unit/lib/NavLink-test.js b/packages/fluxible-router/tests/unit/lib/NavLink-test.js index 2157e75b..c4d4c0b1 100644 --- a/packages/fluxible-router/tests/unit/lib/NavLink-test.js +++ b/packages/fluxible-router/tests/unit/lib/NavLink-test.js @@ -494,10 +494,8 @@ describe('NavLink', function () { it('should ignore any error which happens when calling onbeforeunload', function (done) { var loggerWarning; - mockContext.logger = { - warn: function () { - loggerWarning = arguments; - } + global.console.warn = (...args) => { + loggerWarning = args; }; global.window.onbeforeunload = function () { throw new Error('Test error'); @@ -679,17 +677,14 @@ describe('NavLink NODE_ENV === development', function () { describe('NavLink NODE_ENV === production', function () { var mockContext; var loggerError; - var logger = { - error: function () { - loggerError = arguments; - } - }; beforeEach(function (done) { - loggerError = null; + global.console.error = (...args) => { + loggerError = args; + }; + setup({nodeEnv: 'production'}, function (err, context) { mockContext = context; - mockContext.logger = logger; done(err); }); }); diff --git a/packages/fluxible-router/tests/unit/lib/handleHistory-test.js b/packages/fluxible-router/tests/unit/lib/handleHistory-test.js index b73aa4d6..cfd2fd48 100644 --- a/packages/fluxible-router/tests/unit/lib/handleHistory-test.js +++ b/packages/fluxible-router/tests/unit/lib/handleHistory-test.js @@ -404,11 +404,7 @@ describe('handleHistory', function () { it('should ignore any error which happens when calling onbeforeunload', function (done) { var loggerWarning; - mockContext.logger = { - warn: function () { - loggerWarning = arguments; - } - }; + global.console.warn = (...args) => { loggerWarning = args; }; global.window.confirm = function () { return false; }; global.window.onbeforeunload = function () { throw new Error('Test error');