initial replacement with new context API - WIP

This commit is contained in:
Stanislav Miklik 2018-04-06 10:30:47 +02:00
parent 37d0a0f8e1
commit 1bfab39fa2
8 changed files with 102 additions and 66 deletions

View File

@ -57,7 +57,7 @@ describe('MenuTrigger', () => {
<MenuTrigger menuName='menu1' />
);
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', () => {
<MenuTrigger menuName='menu1' disabled={true} />
);
const menuActions = { openMenu: createSpy() };
instance.context = { menuActions };
instance.props.ctx = { menuActions };
nthChild(output, 1).props.onPress();
expect(menuActions.openMenu).not.toHaveBeenCalled();
});

View File

@ -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": {}
}

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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 (
<View style={{flex:1}} onLayout={this._onLayout}>
<View style={[
{flex:1},
customStyles.menuContextWrapper,
customStyles.menuProviderWrapper,
style,
]}>
{ this.props.children }
<PopupMenuContext.Provider value={this.menuCtx}>
<View style={{flex:1}} onLayout={this._onLayout}>
<View style={[
{flex:1},
customStyles.menuContextWrapper,
customStyles.menuProviderWrapper,
style,
]}>
{ this.props.children }
</View>
<MenuPlaceholder
ctx={this}
backdropStyles={customStyles.backdrop}
ref={this._onPlaceholderRef}
/>
</View>
<MenuPlaceholder
ctx={this}
backdropStyles={customStyles.backdrop}
ref={this._onPlaceholderRef}
/>
</View>
</PopupMenuContext.Provider>
);
}
@ -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,
};

View File

@ -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)

40
src/with-context.js Normal file
View File

@ -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 (
<Context.Consumer>
{value => {
const custom = {
[propName]: value,
ref: forwardedRef
};
return <Component {...custom} {...rest} />;
}}
</Context.Consumer>
);
}
}
const name = Component.displayName || Component.name || "Component";
const consumerName =
Context.Consumer.displayName ||
Context.Consumer.name ||
"Context.Consumer";
function enhanceForwardRef(props, ref) {
return <EnhanceContext {...props} forwardedRef={ref} />;
}
enhanceForwardRef.displayName = `enhanceContext-${consumerName}(${name})`;
enhanceForwardRef.defaultProps = Component.defaultProps;
enhanceForwardRef.propTypes = Component.propTypes;
return React.forwardRef(enhanceForwardRef);
};
}