From 17693bf3332d4b683b2707a981b75a54375b9ff1 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Thu, 16 Jul 2026 10:29:32 -0400 Subject: [PATCH 1/2] Replace legacy context API with modern one --- src/ControlLabel.js | 9 ++-- src/FormControl.js | 9 ++-- src/FormControlFeedback.js | 10 ++-- src/FormGroup.js | 27 ++++------- src/FormGroupContext.js | 6 +++ src/Modal.js | 68 +++++++++++---------------- src/ModalContext.js | 6 +++ src/ModalHeader.js | 11 ++--- src/Nav.js | 44 ++++++++--------- src/Navbar.js | 96 ++++++++++++++++---------------------- src/NavbarBrand.js | 12 ++--- src/NavbarCollapse.js | 13 ++---- src/NavbarContext.js | 6 +++ src/NavbarHeader.js | 12 ++--- src/NavbarToggle.js | 13 ++---- src/Panel.js | 83 +++++++++++++------------------- src/PanelBody.js | 11 ++--- src/PanelCollapse.js | 14 ++---- src/PanelContext.js | 6 +++ src/PanelFooter.js | 13 ++---- src/PanelGroup.js | 70 ++++++++++++--------------- src/PanelGroupContext.js | 6 +++ src/PanelHeading.js | 13 ++---- src/PanelTitle.js | 13 ++---- src/PanelToggle.js | 16 ++----- src/TabContainer.js | 38 ++++++--------- src/TabContainerContext.js | 6 +++ src/TabContent.js | 82 ++++++++++++-------------------- src/TabContentContext.js | 6 +++ src/TabPane.js | 66 ++++++++++---------------- test/OverlayTriggerSpec.js | 4 -- test/index.js | 5 -- 32 files changed, 318 insertions(+), 476 deletions(-) create mode 100644 src/FormGroupContext.js create mode 100644 src/ModalContext.js create mode 100644 src/NavbarContext.js create mode 100644 src/PanelContext.js create mode 100644 src/PanelGroupContext.js create mode 100644 src/TabContainerContext.js create mode 100644 src/TabContentContext.js diff --git a/src/ControlLabel.js b/src/ControlLabel.js index 497e51d130..b833daa8dc 100644 --- a/src/ControlLabel.js +++ b/src/ControlLabel.js @@ -3,6 +3,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import warning from 'warning'; +import FormGroupContext from './FormGroupContext'; import { bsClass, getClassSet, splitBsProps } from './utils/bootstrapUtils'; const propTypes = { @@ -17,13 +18,9 @@ const defaultProps = { srOnly: false }; -const contextTypes = { - $bs_formGroup: PropTypes.object -}; - class ControlLabel extends React.Component { render() { - const formGroup = this.context.$bs_formGroup; + const formGroup = this.context; const controlId = formGroup && formGroup.controlId; const { htmlFor = controlId, srOnly, className, ...props } = this.props; @@ -51,6 +48,6 @@ class ControlLabel extends React.Component { ControlLabel.propTypes = propTypes; ControlLabel.defaultProps = defaultProps; -ControlLabel.contextTypes = contextTypes; +ControlLabel.contextType = FormGroupContext; export default bsClass('control-label', ControlLabel); diff --git a/src/FormControl.js b/src/FormControl.js index 9b5aa7a47a..be7961cecf 100644 --- a/src/FormControl.js +++ b/src/FormControl.js @@ -6,6 +6,7 @@ import warning from 'warning'; import FormControlFeedback from './FormControlFeedback'; import FormControlStatic from './FormControlStatic'; +import FormGroupContext from './FormGroupContext'; import { prefix, bsClass, @@ -39,13 +40,9 @@ const defaultProps = { componentClass: 'input' }; -const contextTypes = { - $bs_formGroup: PropTypes.object -}; - class FormControl extends React.Component { render() { - const formGroup = this.context.$bs_formGroup; + const formGroup = this.context; const controlId = formGroup && formGroup.controlId; const { @@ -92,7 +89,7 @@ class FormControl extends React.Component { FormControl.propTypes = propTypes; FormControl.defaultProps = defaultProps; -FormControl.contextTypes = contextTypes; +FormControl.contextType = FormGroupContext; FormControl.Feedback = FormControlFeedback; FormControl.Static = FormControlStatic; diff --git a/src/FormControlFeedback.js b/src/FormControlFeedback.js index 0f95fc09c9..f2b7d6525d 100644 --- a/src/FormControlFeedback.js +++ b/src/FormControlFeedback.js @@ -1,7 +1,7 @@ import classNames from 'classnames'; import React from 'react'; -import PropTypes from 'prop-types'; +import FormGroupContext from './FormGroupContext'; import Glyphicon from './Glyphicon'; import { bsClass, getClassSet, splitBsProps } from './utils/bootstrapUtils'; @@ -9,10 +9,6 @@ const defaultProps = { bsRole: 'feedback' }; -const contextTypes = { - $bs_formGroup: PropTypes.object -}; - class FormControlFeedback extends React.Component { getGlyph(validationState) { switch (validationState) { @@ -50,7 +46,7 @@ class FormControlFeedback extends React.Component { if (!children) { return this.renderDefaultFeedback( - this.context.$bs_formGroup, + this.context, className, classes, elementProps @@ -66,6 +62,6 @@ class FormControlFeedback extends React.Component { } FormControlFeedback.defaultProps = defaultProps; -FormControlFeedback.contextTypes = contextTypes; +FormControlFeedback.contextType = FormGroupContext; export default bsClass('form-control-feedback', FormControlFeedback); diff --git a/src/FormGroup.js b/src/FormGroup.js index 6ba68e7389..6bfd3da2bc 100644 --- a/src/FormGroup.js +++ b/src/FormGroup.js @@ -2,6 +2,7 @@ import classNames from 'classnames'; import React from 'react'; import PropTypes from 'prop-types'; +import FormGroupContext from './FormGroupContext'; import { bsClass, bsSizes, @@ -19,22 +20,7 @@ const propTypes = { validationState: PropTypes.oneOf(['success', 'warning', 'error', null]) }; -const childContextTypes = { - $bs_formGroup: PropTypes.object.isRequired -}; - class FormGroup extends React.Component { - getChildContext() { - const { controlId, validationState } = this.props; - - return { - $bs_formGroup: { - controlId, - validationState - } - }; - } - hasFeedback(children) { return ValidComponentChildren.some( children, @@ -57,15 +43,18 @@ class FormGroup extends React.Component { } return ( -
- {children} -
+ +
+ {children} +
+
); } } FormGroup.propTypes = propTypes; -FormGroup.childContextTypes = childContextTypes; export default bsClass( 'form-group', diff --git a/src/FormGroupContext.js b/src/FormGroupContext.js new file mode 100644 index 0000000000..4919156ce4 --- /dev/null +++ b/src/FormGroupContext.js @@ -0,0 +1,6 @@ +import React from 'react'; + +const FormGroupContext = React.createContext(undefined); +FormGroupContext.displayName = 'FormGroupContext'; + +export default FormGroupContext; diff --git a/src/Modal.js b/src/Modal.js index f0b46d6929..c643d6dc25 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -12,6 +12,7 @@ import elementType from 'prop-types-extra/lib/elementType'; import Fade from './Fade'; import Body from './ModalBody'; +import ModalContext from './ModalContext'; import ModalDialog from './ModalDialog'; import Footer from './ModalFooter'; import Header from './ModalHeader'; @@ -128,12 +129,6 @@ const defaultProps = { dialogComponentClass: ModalDialog }; -const childContextTypes = { - $bs_modal: PropTypes.shape({ - onHide: PropTypes.func - }) -}; - /* eslint-disable no-use-before-define, react/no-multi-comp */ function DialogTransition(props) { return ; @@ -160,14 +155,6 @@ class Modal extends React.Component { }; } - getChildContext() { - return { - $bs_modal: { - onHide: this.props.onHide - } - }; - } - componentWillUnmount() { // Clean up the listener if we need to. this.handleExited(); @@ -257,39 +244,40 @@ class Modal extends React.Component { const inClassName = show && !animation && 'in'; return ( - - + - {children} - - + + {children} + + + ); } } Modal.propTypes = propTypes; Modal.defaultProps = defaultProps; -Modal.childContextTypes = childContextTypes; Modal.Body = Body; Modal.Header = Header; diff --git a/src/ModalContext.js b/src/ModalContext.js new file mode 100644 index 0000000000..d2a581ae9f --- /dev/null +++ b/src/ModalContext.js @@ -0,0 +1,6 @@ +import React from 'react'; + +const ModalContext = React.createContext(undefined); +ModalContext.displayName = 'ModalContext'; + +export default ModalContext; diff --git a/src/ModalHeader.js b/src/ModalHeader.js index 7828629944..d190280e4f 100644 --- a/src/ModalHeader.js +++ b/src/ModalHeader.js @@ -5,6 +5,7 @@ import React from 'react'; import { bsClass, getClassSet, splitBsProps } from './utils/bootstrapUtils'; import createChainedFunction from './utils/createChainedFunction'; import CloseButton from './CloseButton'; +import ModalContext from './ModalContext'; // TODO: `aria-label` should be `closeLabel`. @@ -34,12 +35,6 @@ const defaultProps = { closeButton: false }; -const contextTypes = { - $bs_modal: PropTypes.shape({ - onHide: PropTypes.func - }) -}; - class ModalHeader extends React.Component { render() { const { @@ -51,7 +46,7 @@ class ModalHeader extends React.Component { ...props } = this.props; - const modal = this.context.$bs_modal; + const modal = this.context; const [bsProps, elementProps] = splitBsProps(props); @@ -74,6 +69,6 @@ class ModalHeader extends React.Component { ModalHeader.propTypes = propTypes; ModalHeader.defaultProps = defaultProps; -ModalHeader.contextTypes = contextTypes; +ModalHeader.contextType = ModalContext; export default bsClass('modal-header', ModalHeader); diff --git a/src/Nav.js b/src/Nav.js index 562662a16e..7705894360 100644 --- a/src/Nav.js +++ b/src/Nav.js @@ -1,10 +1,12 @@ import classNames from 'classnames'; -import React, { cloneElement } from 'react'; +import React, { cloneElement, useContext } from 'react'; import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; import all from 'prop-types-extra/lib/all'; import warning from 'warning'; +import NavbarContext from './NavbarContext'; +import TabContainerContext from './TabContainerContext'; import { bsClass, bsStyles, @@ -95,20 +97,6 @@ const defaultProps = { stacked: false }; -const contextTypes = { - $bs_navbar: PropTypes.shape({ - bsClass: PropTypes.string, - onSelect: PropTypes.func - }), - - $bs_tabContainer: PropTypes.shape({ - activeKey: PropTypes.any, - onSelect: PropTypes.func.isRequired, - getTabId: PropTypes.func.isRequired, - getPaneId: PropTypes.func.isRequired - }) -}; - class Nav extends React.Component { componentDidUpdate() { if (!this._needsRefocus) { @@ -138,7 +126,7 @@ class Nav extends React.Component { } getActiveProps() { - const tabContainer = this.context.$bs_tabContainer; + const tabContainer = this.props.tabContainerContext; if (tabContainer) { warning( @@ -280,10 +268,12 @@ class Nav extends React.Component { pullLeft, className, children, + navbarContext, + tabContainerContext, ...props } = this.props; - const tabContainer = this.context.$bs_tabContainer; + const tabContainer = tabContainerContext; const role = propsRole || (tabContainer ? 'tablist' : null); const { activeKey, activeHref } = this.getActiveProps(); @@ -298,12 +288,12 @@ class Nav extends React.Component { [prefix(bsProps, 'justified')]: justified }; - const navbar = propsNavbar != null ? propsNavbar : this.context.$bs_navbar; + const navbar = propsNavbar != null ? propsNavbar : navbarContext; let pullLeftClassName; let pullRightClassName; if (navbar) { - const navbarProps = this.context.$bs_navbar || { bsClass: 'navbar' }; + const navbarProps = navbarContext || { bsClass: 'navbar' }; classes[prefix(navbarProps, 'nav')] = true; @@ -353,6 +343,18 @@ class Nav extends React.Component { Nav.propTypes = propTypes; Nav.defaultProps = defaultProps; -Nav.contextTypes = contextTypes; -export default bsClass('nav', bsStyles(['tabs', 'pills'], Nav)); +function NavWithContext(props) { + const navbarContext = useContext(NavbarContext); + const tabContainerContext = useContext(TabContainerContext); + + return ( +