From 1bfab39fa27e11d9dfc3fdc8bd0589a4165fe10a Mon Sep 17 00:00:00 2001 From: Stanislav Miklik Date: Fri, 6 Apr 2018 10:30:47 +0200 Subject: [PATCH] initial replacement with new context API - WIP --- __tests__/MenuTrigger-test.js | 4 +-- package.json | 5 ++-- src/Menu.js | 27 +++++++++--------- src/MenuOption.js | 16 +++++------ src/MenuOptions.js | 14 ++++------ src/MenuProvider.js | 52 +++++++++++++++++------------------ src/MenuTrigger.js | 10 +++---- src/with-context.js | 40 +++++++++++++++++++++++++++ 8 files changed, 102 insertions(+), 66 deletions(-) create mode 100644 src/with-context.js diff --git a/__tests__/MenuTrigger-test.js b/__tests__/MenuTrigger-test.js index 19b85f0..86b7036 100644 --- a/__tests__/MenuTrigger-test.js +++ b/__tests__/MenuTrigger-test.js @@ -57,7 +57,7 @@ describe('MenuTrigger', () => { ); const menuActions = { openMenu: createSpy() }; - instance.context = { menuActions }; + instance.props.ctx = { menuActions }; nthChild(output, 1).props.onPress(); expect(menuActions.openMenu).toHaveBeenCalledWith('menu1'); expect(menuActions.openMenu.calls.count()).toEqual(1); @@ -68,7 +68,7 @@ describe('MenuTrigger', () => { ); const menuActions = { openMenu: createSpy() }; - instance.context = { menuActions }; + instance.props.ctx = { menuActions }; nthChild(output, 1).props.onPress(); expect(menuActions.openMenu).not.toHaveBeenCalled(); }); diff --git a/package.json b/package.json index 03962f1..ba48500 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-popup-menu", - "version": "0.12.3", + "version": "0.13.0-rc", "description": "extensible popup/context menu for react native", "main": "src/index.js", "directories": { @@ -61,5 +61,6 @@ "react-dom": "^15.5.4", "react-test-renderer": "^15.6.1", "sinon": "^2.2.0" - } + }, + "dependencies": {} } diff --git a/src/Menu.js b/src/Menu.js index 938a1c0..096cd2c 100644 --- a/src/Menu.js +++ b/src/Menu.js @@ -5,17 +5,19 @@ import { MenuOptions, MenuTrigger } from './index'; import ContextMenu from './renderers/ContextMenu'; import { makeName } from './helpers'; import { debug } from './logger'; +import { withCtx } from './MenuProvider'; const isRegularComponent = c => c.type !== MenuOptions && c.type !== MenuTrigger; const isTrigger = c => c.type === MenuTrigger; const isMenuOptions = c => c.type === MenuOptions; -export default class Menu extends Component { +class Menu extends Component { - constructor(props, ctx) { - super(props, ctx); + constructor(props) { + super(props); this._name = this.props.name || makeName(); this._forceClose = false; + const { ctx } = props; if(!(ctx && ctx.menuActions)) { throw new Error("Menu component must be ancestor of MenuProvider"); } @@ -26,24 +28,24 @@ export default class Menu extends Component { return; } debug('subscribing menu', this._name); - this.context.menuRegistry.subscribe(this); - this.context.menuActions._notify(); + this.props.ctx.menuRegistry.subscribe(this); + this.props.ctx.menuActions._notify(); } componentDidUpdate() { // force update if menu is opened as its content might have changed const force = this._isOpen(); debug('component did update', this._name, force); - this.context.menuActions._notify(force); + this.props.ctx.menuActions._notify(force); } componentWillUnmount() { debug('unsubscribing menu', this._name); if (this._isOpen()) { this._forceClose = true; - this.context.menuActions._notify(); + this.props.ctx.menuActions._notify(); } - this.context.menuRegistry.unsubscribe(this); + this.props.ctx.menuRegistry.unsubscribe(this); } componentWillReceiveProps(nextProps) { @@ -53,11 +55,11 @@ export default class Menu extends Component { } open() { - return this.context.menuActions.openMenu(this._name); + return this.props.ctx.menuActions.openMenu(this._name); } close() { - return this.context.menuActions.closeMenu(); + return this.props.ctx.menuActions.closeMenu(); } getName() { @@ -156,7 +158,4 @@ Menu.defaultProps = { onBackdropPress: () => {}, }; -Menu.contextTypes = { - menuRegistry: PropTypes.object, - menuActions: PropTypes.object, -}; +export default withCtx(Menu); diff --git a/src/MenuOption.js b/src/MenuOption.js index 1d25a51..dcff65d 100644 --- a/src/MenuOption.js +++ b/src/MenuOption.js @@ -3,8 +3,10 @@ import PropTypes from 'prop-types'; import { View, StyleSheet, Text } from 'react-native'; import { debug } from './logger'; import { makeTouchable } from './helpers'; +import { withCtx } from './MenuProvider'; -export default class MenuOption extends Component { + +class MenuOption extends Component { _onSelect() { const { value } = this.props; @@ -12,17 +14,17 @@ export default class MenuOption extends Component { const shouldClose = onSelect(value) !== false; debug('select option', value, shouldClose); if (shouldClose) { - this.context.menuActions.closeMenu(); + this.props.ctx.menuActions.closeMenu(); } } _getMenusOnSelect() { - const menu = this.context.menuActions._getOpenedMenu(); + const menu = this.props.ctx.menuActions._getOpenedMenu(); return menu.instance.props.onSelect; } _getCustomStyles() { - const { optionsCustomStyles } = this.context.menuActions._getOpenedMenu(); + const { optionsCustomStyles } = this.props.ctx.menuActions._getOpenedMenu(); return { ...optionsCustomStyles, ...this.props.customStyles, @@ -81,10 +83,6 @@ MenuOption.defaultProps = { customStyles: {}, }; -MenuOption.contextTypes = { - menuActions: PropTypes.object, -}; - const defaultStyles = StyleSheet.create({ option: { padding: 5, @@ -94,3 +92,5 @@ const defaultStyles = StyleSheet.create({ color: '#ccc', }, }); + +export default withCtx(MenuOption); diff --git a/src/MenuOptions.js b/src/MenuOptions.js index 0beba59..cac1ed2 100644 --- a/src/MenuOptions.js +++ b/src/MenuOptions.js @@ -1,14 +1,15 @@ import React from 'react'; import PropTypes from 'prop-types'; import { View } from 'react-native'; +import { withCtx } from './MenuProvider'; -class MenuOptions extends React.Component { +class _MenuOptions extends React.Component { updateCustomStyles(_props) { const { customStyles } = _props - const menu = this.context.menuActions._getOpenedMenu() + const menu = this.props.ctx.menuActions._getOpenedMenu() const menuName = menu.instance.getName() - this.context.menuRegistry.setOptionsCustomStyles(menuName, customStyles) + this.props.ctx.menuRegistry.setOptionsCustomStyles(menuName, customStyles) } componentWillReceiveProps(nextProps) { @@ -29,6 +30,8 @@ class MenuOptions extends React.Component { } } +const MenuOptions = withCtx(_MenuOptions) + MenuOptions.propTypes = { customStyles: PropTypes.object, renderOptionsContainer: PropTypes.func, @@ -43,9 +46,4 @@ MenuOptions.defaultProps = { customStyles: {}, }; -MenuOptions.contextTypes = { - menuRegistry: PropTypes.object, - menuActions: PropTypes.object, -}; - export default MenuOptions; diff --git a/src/MenuProvider.js b/src/MenuProvider.js index 3a5d000..106b60b 100644 --- a/src/MenuProvider.js +++ b/src/MenuProvider.js @@ -1,6 +1,8 @@ -import React, { Component } from 'react'; +import React, { Component, createContext } from 'react'; import PropTypes from 'prop-types'; import { View, BackHandler } from 'react-native'; + +import { withContext } from './with-context'; import makeMenuRegistry from './menuRegistry'; import MenuPlaceholder from './MenuPlaceholder'; import { measure } from './helpers'; @@ -14,15 +16,18 @@ const layoutsEqual = (a, b) => ( const isFunctional = Component => !Component.prototype.render; +if (!createContext) { + console.warn('New React context API not available - are you using RN 0.55+ ?') +} +const PopupMenuContext = createContext({}) +export const withCtx = withContext(PopupMenuContext, "ctx"); + export default class MenuProvider extends Component { constructor(props) { super(props); this._menuRegistry = makeMenuRegistry(); this._isMenuClosing = false; - } - - getChildContext() { const menuActions = { openMenu: name => this.openMenu(name), closeMenu: () => this.closeMenu(), @@ -31,8 +36,7 @@ export default class MenuProvider extends Component { _getOpenedMenu: () => this._getOpenedMenu(), _notify: force => this._notify(force), }; - const menuRegistry = this._menuRegistry; - return { menuRegistry, menuActions }; + this.menuCtx = { menuRegistry: this._menuRegistry, menuActions } } _handleBackButton = () => { @@ -196,21 +200,23 @@ export default class MenuProvider extends Component { const { style, customStyles } = this.props; debug('render menu', this.isMenuOpen(), this._ownLayout); return ( - - - { this.props.children } + + + + { this.props.children } + + - - + ); } @@ -235,7 +241,6 @@ export default class MenuProvider extends Component { if (menu) { menu.instance.props.onBackdropPress(); } - this.closeMenu(); } @@ -311,8 +316,3 @@ MenuProvider.defaultProps = { customStyles: {}, backHandler: false, }; - -MenuProvider.childContextTypes = { - menuRegistry: PropTypes.object, - menuActions: PropTypes.object, -}; diff --git a/src/MenuTrigger.js b/src/MenuTrigger.js index d559f53..f2057e8 100644 --- a/src/MenuTrigger.js +++ b/src/MenuTrigger.js @@ -3,13 +3,14 @@ import PropTypes from 'prop-types'; import { View, Text } from 'react-native'; import { debug } from './logger.js'; import { makeTouchable } from './helpers'; +import { withCtx } from './MenuProvider'; -export default class MenuTrigger extends Component { +class MenuTrigger extends Component { _onPress() { debug('trigger onPress'); this.props.onPress && this.props.onPress(); - this.context.menuActions.openMenu(this.props.menuName); + this.props.ctx.menuActions.openMenu(this.props.menuName); } render() { @@ -45,7 +46,4 @@ MenuTrigger.defaultProps = { customStyles: {}, }; -MenuTrigger.contextTypes = { - menuActions: PropTypes.object, -}; - +export default withCtx(MenuTrigger) diff --git a/src/with-context.js b/src/with-context.js new file mode 100644 index 0000000..863ce18 --- /dev/null +++ b/src/with-context.js @@ -0,0 +1,40 @@ +import React from "react"; + +export function withContext(Context, propName = "context") { + return function wrap(Component) { + class EnhanceContext extends React.Component { + render() { + const { forwardedRef, ...rest } = this.props; + + return ( + + {value => { + const custom = { + [propName]: value, + ref: forwardedRef + }; + return ; + }} + + ); + } + } + + const name = Component.displayName || Component.name || "Component"; + const consumerName = + Context.Consumer.displayName || + Context.Consumer.name || + "Context.Consumer"; + + function enhanceForwardRef(props, ref) { + return ; + } + + enhanceForwardRef.displayName = `enhanceContext-${consumerName}(${name})`; + + enhanceForwardRef.defaultProps = Component.defaultProps; + enhanceForwardRef.propTypes = Component.propTypes; + + return React.forwardRef(enhanceForwardRef); + }; +}