Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(unmount): fix setState after unmount #424

Merged
merged 2 commits into from
Apr 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions karma.conf.js
Expand Up @@ -28,6 +28,7 @@ module.exports = function(config) {
mode: 'production',
// Remove source maps: *speeeeeed*
devtool: 'none',
cache: true,
module: {
// Suppress power-assert warning
exprContextCritical: false,
Expand Down
4 changes: 2 additions & 2 deletions lib/Draggable.js
Expand Up @@ -295,8 +295,8 @@ class Draggable extends React.Component<DraggableProps, DraggableState> {
if (!this.state.dragging) return false;

// Short-circuit if user's callback killed it.
const shouldStop = this.props.onStop(e, createDraggableData(this, coreData));
if (shouldStop === false) return false;
const shouldContinue = this.props.onStop(e, createDraggableData(this, coreData));
if (shouldContinue === false) return false;

log('Draggable: onDragStop: %j', coreData);

Expand Down
18 changes: 13 additions & 5 deletions lib/DraggableCore.js
Expand Up @@ -222,7 +222,14 @@ export default class DraggableCore extends React.Component<DraggableCoreProps, D
touchIdentifier: null
};

mounted = false;

componentDidMount() {
this.mounted = true;
}

componentWillUnmount() {
this.mounted = false;
// Remove any leftover event handlers. Remove both touch and mouse handlers in case
// some browser quirk caused a touch event to fire during a mouse move, or vice versa.
const thisNode = ReactDOM.findDOMNode(this);
Expand Down Expand Up @@ -277,7 +284,7 @@ export default class DraggableCore extends React.Component<DraggableCoreProps, D
// Call event handler. If it returns explicit false, cancel.
log('calling', this.props.onStart);
const shouldUpdate = this.props.onStart(e, coreEvent);
if (shouldUpdate === false) return;
if (shouldUpdate === false || this.mounted === false) return;

// Add a style to the body to disable user-select. This prevents text from
// being selected all over the page.
Expand Down Expand Up @@ -324,7 +331,7 @@ export default class DraggableCore extends React.Component<DraggableCoreProps, D

// Call event handler. If it returns explicit false, trigger end.
const shouldUpdate = this.props.onDrag(e, coreEvent);
if (shouldUpdate === false) {
if (shouldUpdate === false || this.mounted === false) {
try {
// $FlowIgnore
this.handleDragStop(new MouseEvent('mouseup'));
Expand Down Expand Up @@ -353,6 +360,10 @@ export default class DraggableCore extends React.Component<DraggableCoreProps, D
const {x, y} = position;
const coreEvent = createCoreData(this, x, y);

// Call event handler
const shouldContinue = this.props.onStop(e, coreEvent);
if (shouldContinue === false || this.mounted === false) return false;

const thisNode = ReactDOM.findDOMNode(this);
if (thisNode) {
// Remove user-select hack
Expand All @@ -368,9 +379,6 @@ export default class DraggableCore extends React.Component<DraggableCoreProps, D
lastY: NaN
});

// Call event handler
this.props.onStop(e, coreEvent);

if (thisNode) {
// Remove event handlers
log('DraggableCore: Removing handlers');
Expand Down
32 changes: 32 additions & 0 deletions specs/draggable.spec.jsx
Expand Up @@ -721,6 +721,38 @@ describe('react-draggable', function () {
// (element, fromX, fromY, toX, toY)
simulateMovementFromTo(drag, 0, 0, 100, 100);
});

it('should not throw an error if unmounted during a callback', function () {
function App(props) {
const [firstVisible, setFirstVisible] = React.useState(true);
// Callback with ref to draggable
const dragRef = React.useRef(null);
props.draggableRefCb(dragRef);
return (
<div className="App">
<button onClick={() => setFirstVisible(true)}>Show draggables</button>

{firstVisible && (
<Draggable onStop={() => setFirstVisible(false)} ref={dragRef}>
<h2>1. Drag me!</h2>
</Draggable>
)}
</div>
);
}
let dragRef;
const appContainer = TestUtils.renderIntoDocument(
<App draggableRefCb={(_ref) => {dragRef = _ref;}}/>
);

// (element, fromX, fromY, toX, toY)
simulateMovementFromTo(dragRef.current, 0, 0, 100, 100);

// ok, was a setstate warning thrown?
// Assert unmounted
assert(dragRef.current === null);
});

});

describe('DraggableCore callbacks', function () {
Expand Down