Skip to content

Commit

Permalink
fix(TabContainer): Resolve lifecycle deprecation (#4370)
Browse files Browse the repository at this point in the history
* fix(TabContainer): Resolve lifecycle deprecation

* Disable lint rule for props used by hook.
  • Loading branch information
nortonwong authored and taion committed Sep 4, 2019
1 parent c0594ba commit 8103448
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 135 deletions.
260 changes: 128 additions & 132 deletions src/TabContainer.js
Original file line number Diff line number Diff line change
@@ -1,138 +1,134 @@
import React from 'react';
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import { uncontrollable } from 'uncontrollable';
import { useUncontrolled } from 'uncontrollable';

import TabContext from './TabContext';
import SelectableContext from './SelectableContext';

class TabContainer extends React.Component {
static propTypes = {
/**
* HTML id attribute, required if no `generateChildId` prop
* is specified.
*
* @type {string}
*/
id(props, ...args) {
let error = null;

if (!props.generateChildId) {
error = PropTypes.string(props, ...args);

if (!error && !props.id) {
error = new Error(
'In order to properly initialize Tabs in a way that is accessible ' +
'to assistive technologies (such as screen readers) an `id` or a ' +
'`generateChildId` prop to TabContainer is required',
);
}
/* eslint-disable react/no-unused-prop-types */
const propTypes = {
/**
* HTML id attribute, required if no `generateChildId` prop
* is specified.
*
* @type {string}
*/
id(props, ...args) {
let error = null;

if (!props.generateChildId) {
error = PropTypes.string(props, ...args);

if (!error && !props.id) {
error = new Error(
'In order to properly initialize Tabs in a way that is accessible ' +
'to assistive technologies (such as screen readers) an `id` or a ' +
'`generateChildId` prop to TabContainer is required',
);
}

return error;
},

/**
* Sets a default animation strategy for all children `<TabPane>`s. Use
* `false` to disable, `true` to enable the default `<Fade>` animation or
* a react-transition-group v2 `<Transition/>` component.
*
* @type {{Transition | false}}
* @default {Fade}
*/
transition: PropTypes.oneOfType([
PropTypes.oneOf([false]),
PropTypes.elementType,
]),
/**
* Wait until the first "enter" transition to mount tabs (add them to the DOM)
*/
mountOnEnter: PropTypes.bool,

/**
* Unmount tabs (remove it from the DOM) when they are no longer visible
*/
unmountOnExit: PropTypes.bool,

/**
* A function that takes an `eventKey` and `type` and returns a unique id for
* child tab `<NavItem>`s and `<TabPane>`s. The function _must_ be a pure
* function, meaning it should always return the _same_ id for the same set
* of inputs. The default value requires that an `id` to be set for the
* `<TabContainer>`.
*
* The `type` argument will either be `"tab"` or `"pane"`.
*
* @defaultValue (eventKey, type) => `${this.props.id}-${type}-${eventKey}`
*/
generateChildId: PropTypes.func,

/**
* A callback fired when a tab is selected.
*
* @controllable activeKey
*/
onSelect: PropTypes.func,

/**
* The `eventKey` of the currently active tab.
*
* @controllable onSelect
*/
activeKey: PropTypes.any,
};

constructor(props) {
super(props);

this.state = {
tabContext: {
onSelect: this.props.onSelect,
activeKey: this.props.activeKey,
transition: this.props.transition,
mountOnEnter: this.props.mountOnEnter,
unmountOnExit: this.props.unmountOnExit,
getControlledId: this.getControlledId,
getControllerId: this.getControllerId,
},
};
}

static getDerivedStateFromProps(
{ activeKey, mountOnEnter, unmountOnExit, transition },
prevState,
) {
return {
tabContext: {
...prevState.tabContext,
activeKey,
mountOnEnter,
unmountOnExit,
transition,
},
};
}

getKey(key, type) {
const { generateChildId, id } = this.props;
if (generateChildId) return generateChildId(key, type);
return id ? `${id}-${type}-${key}` : null;
}

getControlledId = key => this.getKey(key, 'tabpane');

getControllerId = key => this.getKey(key, 'tab');

render() {
const { children, onSelect } = this.props;

return (
<TabContext.Provider value={this.state.tabContext}>
<SelectableContext.Provider value={onSelect}>
{children}
</SelectableContext.Provider>
</TabContext.Provider>
);
}
}

export default uncontrollable(TabContainer, { activeKey: 'onSelect' });
}

return error;
},

/**
* Sets a default animation strategy for all children `<TabPane>`s. Use
* `false` to disable, `true` to enable the default `<Fade>` animation or
* a react-transition-group v2 `<Transition/>` component.
*
* @type {{Transition | false}}
* @default {Fade}
*/
transition: PropTypes.oneOfType([
PropTypes.oneOf([false]),
PropTypes.elementType,
]),
/**
* Wait until the first "enter" transition to mount tabs (add them to the DOM)
*/
mountOnEnter: PropTypes.bool,

/**
* Unmount tabs (remove it from the DOM) when they are no longer visible
*/
unmountOnExit: PropTypes.bool,

/**
* A function that takes an `eventKey` and `type` and returns a unique id for
* child tab `<NavItem>`s and `<TabPane>`s. The function _must_ be a pure
* function, meaning it should always return the _same_ id for the same set
* of inputs. The default value requires that an `id` to be set for the
* `<TabContainer>`.
*
* The `type` argument will either be `"tab"` or `"pane"`.
*
* @defaultValue (eventKey, type) => `${props.id}-${type}-${eventKey}`
*/
generateChildId: PropTypes.func,

/**
* A callback fired when a tab is selected.
*
* @controllable activeKey
*/
onSelect: PropTypes.func,

/**
* The `eventKey` of the currently active tab.
*
* @controllable onSelect
*/
activeKey: PropTypes.any,
};

const TabContainer = props => {
const {
id,
generateChildId: generateCustomChildId,
onSelect,
activeKey,
transition,
mountOnEnter,
unmountOnExit,
children,
} = useUncontrolled(props, { activeKey: 'onSelect' });

const generateChildId = useMemo(
() =>
generateCustomChildId ||
((key, type) => (id ? `${id}-${type}-${key}` : null)),
[id, generateCustomChildId],
);

const tabContext = useMemo(
() => ({
onSelect,
activeKey,
transition,
mountOnEnter,
unmountOnExit,
getControlledId: key => generateChildId(key, 'tabpane'),
getControllerId: key => generateChildId(key, 'tab'),
}),
[
onSelect,
activeKey,
transition,
mountOnEnter,
unmountOnExit,
generateChildId,
],
);

return (
<TabContext.Provider value={tabContext}>
<SelectableContext.Provider value={onSelect}>
{children}
</SelectableContext.Provider>
</TabContext.Provider>
);
};

TabContainer.propTypes = propTypes;

export default TabContainer;
4 changes: 1 addition & 3 deletions src/Tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ import { useUncontrolled } from 'uncontrollable';
import Nav from './Nav';
import NavLink from './NavLink';
import NavItem from './NavItem';
import UncontrolledTabContainer from './TabContainer';
import TabContainer from './TabContainer';
import TabContent from './TabContent';
import TabPane from './TabPane';

import { forEach, map } from './utils/ElementChildren';

const TabContainer = UncontrolledTabContainer.ControlledComponent;

const propTypes = {
/**
* Mark the Tab with a matching `eventKey` as active.
Expand Down

0 comments on commit 8103448

Please sign in to comment.