Skip to content

Commit e4a7bc4

Browse files
committed
fix(ReactDOM): automatically pass ReactRef to react-draggable
Fixes React 17 compatibility and react-grid-layout/react-grid-layout#1458
1 parent 7d3e14d commit e4a7bc4

File tree

5 files changed

+49
-26
lines changed

5 files changed

+49
-26
lines changed

__tests__/Resizable.test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ describe('render Resizable', () => {
2323
transformScale: 1,
2424
width: 50,
2525
};
26+
const handleFn = (axis, ref) => <span className={`custom-handle-${axis}`} ref={ref} />;
2627
const userChildren = <span className={'children'} />;
2728
const resizableBoxChildren = <div style={{width: '50px', height: '50px'}}>{userChildren}</div>;
2829

@@ -45,6 +46,21 @@ describe('render Resizable', () => {
4546
expect(cursorE).toHaveLength(1);
4647
});
4748

49+
test('with handle function', () => {
50+
const handleFn = (axis, ref) => {
51+
expect(axis).toMatch(/(se|e)/);
52+
expect(ref).toMatchObject({current: null}); // ReactRef
53+
return <span className={`custom-handle-${axis}`} ref={ref} />;
54+
};
55+
const element = shallow(<Resizable {...props} handle={handleFn}>{resizableBoxChildren}</Resizable>);
56+
expect(element.find('.test-classname').find('.children'));
57+
expect(element.find(DraggableCore)).toHaveLength(2);
58+
const cursorSe = element.find('.custom-handle-se');
59+
const cursorE = element.find('.custom-handle-e');
60+
expect(cursorSe).toHaveLength(1);
61+
expect(cursorE).toHaveLength(1);
62+
});
63+
4864
describe('and pass handle props', () => {
4965
test('as component', () => {
5066
const customProps = {

__tests__/ResizableBox.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ describe('render ResizableBox', () => {
1010
const props = {
1111
axis: 'x',
1212
draggableOpts: {},
13-
handle: jest.fn(resizeHandle => <span className={`test-class-${resizeHandle}`} />),
13+
handle: (jest.fn((resizeHandle, ref) => <span className={`test-class-${resizeHandle}`} ref={ref} />): Function),
1414
handleSize: [20, 20],
1515
height: 50,
1616
lockAspectRatio: false,

examples/ExampleLayout.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default class ExampleLayout extends React.Component<{}, {width: number, h
1919
};
2020

2121
// On top layout
22-
onResize = (event, {element, size, handle}) => {
22+
onFirstBoxResize = (event, {element, size, handle}) => {
2323
this.setState({width: size.width, height: size.height});
2424
};
2525

@@ -56,8 +56,8 @@ export default class ExampleLayout extends React.Component<{}, {width: number, h
5656

5757
<h3>Statically Positioned Layout</h3>
5858
<div className="layoutRoot">
59-
<Resizable className="box" height={this.state.height} width={this.state.width} onResize={this.onResize} resizeHandles={['sw', 'se', 'nw', 'ne', 'w', 'e', 'n', 's']}>
60-
<div className="box" style={{width: this.state.width + 'px', height: this.state.height + 'px'}}>
59+
<Resizable className="box" height={this.state.height} width={this.state.width} onResize={this.onFirstBoxResize} resizeHandles={['sw', 'se', 'nw', 'ne', 'w', 'e', 'n', 's']}>
60+
<div style={{width: this.state.width + 'px', height: this.state.height + 'px'}}>
6161
<span className="text">{"Raw use of <Resizable> element. 200x200, all Resize Handles."}</span>
6262
<button onClick={this.onResetClick} style={{'marginTop': '10px'}}>Reset this element's width/height</button>
6363
</div>
@@ -77,7 +77,7 @@ export default class ExampleLayout extends React.Component<{}, {width: number, h
7777
className="custom-box box"
7878
width={200}
7979
height={200}
80-
handle={(h) => <span className={`custom-handle custom-handle-${h}`} />}
80+
handle={(h, ref) => <span className={`custom-handle custom-handle-${h}`} />}
8181
handleSize={[8, 8]}
8282
resizeHandles={['sw', 'se', 'nw', 'ne', 'w', 'e', 'n', 's']}>
8383
<span className="text">{"<ResizableBox> with custom handles in all locations."}</span>

lib/Resizable.js

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
// @flow
2-
import React from 'react';
2+
import * as React from 'react';
33
import type {Node as ReactNode} from 'react';
44
import {DraggableCore} from 'react-draggable';
55
import {cloneElement} from './utils';
66
import {resizableProps} from "./propTypes";
7-
import type {ResizeHandleAxis, DefaultProps, Props, ReactRef, ResizableState, DragCallbackData} from './propTypes';
7+
import type {ResizeHandleAxis, DefaultProps, Props, ReactRef, DragCallbackData} from './propTypes';
88

9-
export default class Resizable extends React.Component<Props, ResizableState> {
9+
// The base <Resizable> component.
10+
// This component does not have state and relies on the parent to set its props based on callback data.
11+
export default class Resizable extends React.Component<Props, void> {
1012
static propTypes = resizableProps;
1113

1214
static defaultProps: DefaultProps = {
@@ -19,8 +21,7 @@ export default class Resizable extends React.Component<Props, ResizableState> {
1921
transformScale: 1
2022
};
2123

22-
state: ResizableState = undefined;
23-
24+
handleRefs: {[key: ResizeHandleAxis]: ReactRef<HTMLElement>} = {};
2425
lastHandleRect: ?ClientRect = null;
2526
slack: ?[number, number] = null;
2627

@@ -151,15 +152,16 @@ export default class Resizable extends React.Component<Props, ResizableState> {
151152
};
152153
}
153154

154-
renderResizeHandle(resizeHandleAxis: ResizeHandleAxis): ReactNode {
155+
// Render a resize handle given an axis & DOM ref.
156+
renderResizeHandle(handleAxis: ResizeHandleAxis, ref: ReactRef<HTMLElement>): ReactNode {
155157
const {handle} = this.props;
156158
if (handle) {
157159
if (typeof handle === 'function') {
158-
return handle(resizeHandleAxis);
160+
return handle(handleAxis, ref);
159161
}
160-
return handle;
162+
return React.cloneElement(handle, {ref});
161163
}
162-
return <span className={`react-resizable-handle react-resizable-handle-${resizeHandleAxis}`} />;
164+
return <span className={`react-resizable-handle react-resizable-handle-${handleAxis}`} ref={ref} />;
163165
}
164166

165167
render(): ReactNode {
@@ -178,17 +180,22 @@ export default class Resizable extends React.Component<Props, ResizableState> {
178180
className: `${className ? `${className} ` : ''}react-resizable`,
179181
children: [
180182
...children.props.children,
181-
...resizeHandles.map((handleAxis) => (
182-
<DraggableCore
183-
{...draggableOpts}
184-
key={`resizableHandle-${handleAxis}`}
185-
onStop={this.resizeHandler('onResizeStop', handleAxis)}
186-
onStart={this.resizeHandler('onResizeStart', handleAxis)}
187-
onDrag={this.resizeHandler('onResize', handleAxis)}
188-
>
189-
{this.renderResizeHandle(handleAxis)}
190-
</DraggableCore>
191-
))
183+
...resizeHandles.map((handleAxis) => {
184+
// Create a ref to the handle so that `<DraggableCore>` doesn't have to use ReactDOM.findDOMNode().
185+
const ref = (this.handleRefs[handleAxis]) ?? (this.handleRefs[handleAxis] = React.createRef());
186+
return (
187+
<DraggableCore
188+
{...draggableOpts}
189+
nodeRef={ref}
190+
key={`resizableHandle-${handleAxis}`}
191+
onStop={this.resizeHandler('onResizeStop', handleAxis)}
192+
onStart={this.resizeHandler('onResizeStart', handleAxis)}
193+
onDrag={this.resizeHandler('onResize', handleAxis)}
194+
>
195+
{this.renderResizeHandle(handleAxis, ref)}
196+
</DraggableCore>
197+
);
198+
})
192199
]
193200
});
194201
}

lib/propTypes.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export type Props = {
4343
className?: ?string,
4444
draggableOpts?: ?ElementConfig<typeof DraggableCore>,
4545
height: number,
46-
handle?: ReactElement<any> | (resizeHandleAxis: ResizeHandleAxis) => ReactElement<any>,
46+
handle?: ReactElement<any> | (resizeHandleAxis: ResizeHandleAxis, ref: ReactRef<HTMLElement>) => ReactElement<any>,
4747
onResizeStop?: ?(e: SyntheticEvent<>, data: ResizeCallbackData) => any,
4848
onResizeStart?: ?(e: SyntheticEvent<>, data: ResizeCallbackData) => any,
4949
onResize?: ?(e: SyntheticEvent<>, data: ResizeCallbackData) => any,

0 commit comments

Comments
 (0)