Skip to content

Commit

Permalink
Backpatch: add StrictMode support react-bootstrap#5687
Browse files Browse the repository at this point in the history
  • Loading branch information
adamayres committed Jan 27, 2023
1 parent 5f191b0 commit 7e04468
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 89 deletions.
4 changes: 2 additions & 2 deletions src/AccordionCollapse.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';

import { Transition } from 'react-transition-group';
import Collapse, { CollapseProps } from './Collapse';
import AccordionContext from './AccordionContext';
import SelectableContext from './SelectableContext';
Expand All @@ -26,7 +26,7 @@ const propTypes = {
children: PropTypes.element.isRequired,
};

const AccordionCollapse: AccordionCollapse = React.forwardRef<typeof Collapse>(
const AccordionCollapse: AccordionCollapse = React.forwardRef<Transition<any>, AccordionCollapseProps>(
({ children, eventKey, ...props }: AccordionCollapseProps, ref) => {
const contextEventKey = useContext(AccordionContext);

Expand Down
16 changes: 10 additions & 6 deletions src/Carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import useEventCallback from '@restart/hooks/useEventCallback';
import useUpdateEffect from '@restart/hooks/useUpdateEffect';
import useCommittedRef from '@restart/hooks/useCommittedRef';
import useTimeout from '@restart/hooks/useTimeout';
import classNames from 'classnames';
import Transition from 'react-transition-group/Transition';
import { TransitionStatus } from 'react-transition-group/Transition';
import PropTypes from 'prop-types';
import React, {
useCallback,
Expand All @@ -25,6 +24,7 @@ import {
BsPrefixPropsWithChildren,
BsPrefixRefForwardingComponent,
} from './helpers';
import TransitionWrapper from './TransitionWrapper';

export interface CarouselRef {
element: HTMLElement;
Expand Down Expand Up @@ -580,15 +580,19 @@ function CarouselFunc(uncontrolledProps: CarouselProps, ref) {
const isActive = index === renderedActiveIndex;

return slide ? (
<Transition
<TransitionWrapper
in={isActive}
onEnter={isActive ? handleEnter : undefined}
onEntered={isActive ? handleEntered : undefined}
addEndListener={transitionEndListener}
>
{(status) =>
{(
status: TransitionStatus,
innerProps: Record<string, unknown>,
) =>
React.cloneElement(child, {
className: classNames(
...innerProps,
className: classNames(
child.props.className,
isActive && status !== 'entered' && orderClassName,
(status === 'entered' || status === 'exiting') && 'active',
Expand All @@ -597,7 +601,7 @@ function CarouselFunc(uncontrolledProps: CarouselProps, ref) {
),
})
}
</Transition>
</TransitionWrapper>
) : (
React.cloneElement(child, {
className: classNames(
Expand Down
15 changes: 9 additions & 6 deletions src/Collapse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import css from 'dom-helpers/css';
import PropTypes from 'prop-types';
import React, { useMemo } from 'react';
import Transition, {
TransitionStatus,
ENTERED,
ENTERING,
EXITED,
Expand All @@ -12,6 +13,7 @@ import { TransitionCallbacks } from './helpers';
import createChainedFunction from './createChainedFunction';
import triggerBrowserReflow from './triggerBrowserReflow';
import { useClassNameMapper } from './ThemeProvider';
import TransitionWrapper from './TransitionWrapper';

type Dimension = 'height' | 'width';

Expand Down Expand Up @@ -150,7 +152,7 @@ const defaultProps = {
getDimensionValue: getDefaultDimensionValue,
};

const Collapse = React.forwardRef(
const Collapse = React.forwardRef<Transition<any>, CollapseProps>(
(
{
onEnter,
Expand All @@ -163,7 +165,7 @@ const Collapse = React.forwardRef(
dimension = 'height',
getDimensionValue = getDefaultDimensionValue,
...props
}: CollapseProps,
},
ref,
) => {
const classNames = useClassNameMapper();
Expand Down Expand Up @@ -221,7 +223,7 @@ const Collapse = React.forwardRef(
);

return (
<Transition
<TransitionWrapper
// @ts-ignore
ref={ref}
addEndListener={transitionEndListener}
Expand All @@ -232,19 +234,20 @@ const Collapse = React.forwardRef(
onEntered={handleEntered}
onExit={handleExit}
onExiting={handleExiting}
childRef={(children as any).ref}
>
{(state, innerProps) => {
{(state: TransitionStatus, innerProps: Record<string, unknown>) => {
return React.cloneElement(children as any, {
...innerProps,
className: classNames(
className,
(children as any).props.className,
children.props.className,
collapseStyles[state],
computedDimension === 'width' && 'width',
),
});
}}
</Transition>
</TransitionWrapper>
);
},
);
Expand Down
12 changes: 7 additions & 5 deletions src/Fade.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import PropTypes from 'prop-types';
import React, { useCallback } from 'react';
import Transition, {
TransitionStatus,
ENTERED,
ENTERING,
} from 'react-transition-group/Transition';
import transitionEndListener from './transitionEndListener';
import { TransitionCallbacks } from './helpers';
import triggerBrowserReflow from './triggerBrowserReflow';
import { useClassNameMapper } from './ThemeProvider';
import TransitionWrapper from './TransitionWrapper';

export interface FadeProps extends TransitionCallbacks {
className?: string;
Expand Down Expand Up @@ -92,21 +94,21 @@ const Fade = React.forwardRef<Transition<any>, FadeProps>(
const handleEnter = useCallback(
(node) => {
triggerBrowserReflow(node);
if (props.onEnter) props.onEnter(node);
},
props.onEnter?.(node); },
[props],
);

const classNames = useClassNameMapper();

return (
<Transition
<TransitionWrapper
ref={ref}
addEndListener={transitionEndListener}
{...props}
onEnter={handleEnter}
childRef={(children as any).ref}
>
{(status, innerProps) =>
{(status: TransitionStatus, innerProps: Record<string, unknown>) =>
React.cloneElement(children, {
...innerProps,
className: classNames(
Expand All @@ -117,7 +119,7 @@ const Fade = React.forwardRef<Transition<any>, FadeProps>(
),
})
}
</Transition>
</TransitionWrapper>
);
},
);
Expand Down
34 changes: 15 additions & 19 deletions src/OverlayTrigger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import useTimeout from '@restart/hooks/useTimeout';
import safeFindDOMNode from 'react-overlays/safeFindDOMNode';
import warning from 'warning';
import { useUncontrolledProp } from 'uncontrollable';
import useMergedRefs from '@restart/hooks/useMergedRefs';
import Overlay, { OverlayChildren, OverlayProps } from './Overlay';

export type OverlayTriggerType = 'hover' | 'click' | 'focus';
Expand Down Expand Up @@ -36,12 +37,6 @@ export interface OverlayTriggerProps
onHide?: never;
}

class RefHolder extends React.Component {
render() {
return this.props.children;
}
}

function normalizeDelay(delay?: OverlayDelay) {
return delay && typeof delay === 'object'
? delay
Expand Down Expand Up @@ -187,6 +182,10 @@ function OverlayTrigger({
...props
}: OverlayTriggerProps) {
const triggerNodeRef = useRef(null);
const mergedRef = useMergedRefs<unknown>(
triggerNodeRef,
(children as any).ref,
);
const timeout = useTimeout();
const hoverStateRef = useRef<string>('');

Expand All @@ -199,10 +198,9 @@ function OverlayTrigger({
? React.Children.only(children).props
: ({} as any);

const getTarget = useCallback(
() => safeFindDOMNode(triggerNodeRef.current),
[],
);
const attachRef = (r: React.ComponentClass | Element | null | undefined) => {
mergedRef(safeFindDOMNode(r));
};

const handleShow = useCallback(() => {
timeout.clear();
Expand Down Expand Up @@ -271,7 +269,9 @@ function OverlayTrigger({
);

const triggers: string[] = trigger == null ? [] : [].concat(trigger as any);
const triggerProps: any = {};
const triggerProps: any = {
ref: attachRef,
};

if (triggers.indexOf('click') !== -1) {
triggerProps.onClick = handleClick;
Expand All @@ -293,21 +293,17 @@ function OverlayTrigger({

return (
<>
{typeof children === 'function' ? (
children({ ...triggerProps, ref: triggerNodeRef })
) : (
<RefHolder ref={triggerNodeRef}>
{cloneElement(children as any, triggerProps)}
</RefHolder>
)}
{typeof children === 'function'
? children(triggerProps)
: cloneElement(children, triggerProps)}
<Overlay
{...props}
show={show}
onHide={handleHide}
flip={flip}
placement={placement}
popperConfig={popperConfig}
target={getTarget as any}
target={triggerNodeRef.current}
>
{overlay}
</Overlay>
Expand Down
93 changes: 93 additions & 0 deletions src/TransitionWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React, { useCallback, useRef } from 'react';
import Transition, {
TransitionProps,
TransitionStatus,
} from 'react-transition-group/Transition';
import safeFindDOMNode from 'react-overlays/safeFindDOMNode';
import useMergedRefs from '@restart/hooks/useMergedRefs';

export type TransitionWrapperProps = TransitionProps & {
childRef?: React.Ref<unknown>;
children:
| React.ReactElement
| ((
status: TransitionStatus,
props: Record<string, unknown>,
) => React.ReactNode);
};

// Normalizes Transition callbacks when nodeRef is used.
const TransitionWrapper = React.forwardRef<
Transition<any>,
TransitionWrapperProps
>(
(
{
onEnter,
onEntering,
onEntered,
onExit,
onExiting,
onExited,
addEndListener,
children,
childRef,
...props
},
ref,
) => {
const nodeRef = useRef<HTMLElement>(null);
const mergedRef = useMergedRefs(nodeRef, childRef);

const attachRef = (
r: React.ComponentClass | Element | null | undefined,
) => {
mergedRef(safeFindDOMNode(r));
};

const normalize = (
callback?: (node: HTMLElement, param: unknown) => void,
) => (param: unknown) => {
if (callback && nodeRef.current) {
callback(nodeRef.current, param);
}
};

const handleEnter = useCallback(normalize(onEnter), [onEnter]);
const handleEntering = useCallback(normalize(onEntering), [onEntering]);
const handleEntered = useCallback(normalize(onEntered), [onEntered]);
const handleExit = useCallback(normalize(onExit), [onExit]);
const handleExiting = useCallback(normalize(onExiting), [onExiting]);
const handleExited = useCallback(normalize(onExited), [onExited]);
const handleAddEndListener = useCallback(normalize(addEndListener), [
addEndListener,
]);

return (
<Transition
ref={ref}
{...props}
onEnter={handleEnter}
onEntered={handleEntered}
onEntering={handleEntering}
onExit={handleExit}
onExited={handleExited}
onExiting={handleExiting}
addEndListener={handleAddEndListener}
nodeRef={nodeRef}
>
{typeof children === 'function'
? (status: TransitionStatus, innerProps: Record<string, unknown>) =>
children(status, {
...innerProps,
ref: attachRef,
})
: React.cloneElement(children as React.ReactElement, {
ref: attachRef,
})}
</Transition>
);
},
);

export default TransitionWrapper;
16 changes: 0 additions & 16 deletions test/ButtonSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,20 +139,4 @@ describe('<Button>', () => {
</Button>,
).assertSingle(`.my-btn.my-btn-danger`);
});

it('Should allow a custom classNameMap', () => {
mount(
<Button
variant="danger"
classNameMap={{
btn: 'someMappedClassNameForBtn',
'btn-danger': 'someMappedClassNameForBtnDanger',
}}
>
Title
</Button>,
).assertSingle(
`button.someMappedClassNameForBtn.someMappedClassNameForBtnDanger`,
);
});
});

0 comments on commit 7e04468

Please sign in to comment.