Skip to content

Commit d8e5b2d

Browse files
committed
Add ref cleanup function
1 parent c54e354 commit d8e5b2d

File tree

6 files changed

+126
-18
lines changed

6 files changed

+126
-18
lines changed

packages/react-dom/src/__tests__/refs-test.js

+66
Original file line numberDiff line numberDiff line change
@@ -534,3 +534,69 @@ describe('strings refs across renderers', () => {
534534
expect(inst.refs.child2).toBe(div2.firstChild);
535535
});
536536
});
537+
538+
describe('refs return clean up function', () => {
539+
it('does not break', () => {
540+
const container = document.createElement('div');
541+
let cleanUp = jest.fn();
542+
let setup = jest.fn();
543+
544+
ReactDOM.render(
545+
<div
546+
ref={_ref => {
547+
setup(_ref);
548+
return cleanUp;
549+
}}
550+
/>,
551+
container,
552+
);
553+
554+
ReactDOM.render(
555+
<div
556+
ref={_ref => {
557+
setup(_ref);
558+
}}
559+
/>,
560+
container,
561+
);
562+
563+
expect(setup).toHaveBeenCalledTimes(2);
564+
expect(cleanUp).toHaveBeenCalledTimes(1);
565+
expect(cleanUp.mock.calls[0][0]).toBe(undefined);
566+
567+
ReactDOM.render(<div ref={_ref => {}} />, container);
568+
569+
expect(cleanUp).toHaveBeenCalledTimes(1);
570+
expect(setup).toHaveBeenCalledTimes(3);
571+
expect(setup.mock.calls[2][0]).toBe(null);
572+
573+
cleanUp = jest.fn();
574+
setup = jest.fn();
575+
576+
ReactDOM.render(
577+
<div
578+
ref={_ref => {
579+
setup(_ref);
580+
return cleanUp;
581+
}}
582+
/>,
583+
container,
584+
);
585+
586+
expect(setup).toHaveBeenCalledTimes(1);
587+
expect(cleanUp).toHaveBeenCalledTimes(0);
588+
589+
ReactDOM.render(
590+
<div
591+
ref={_ref => {
592+
setup(_ref);
593+
return cleanUp;
594+
}}
595+
/>,
596+
container,
597+
);
598+
599+
expect(setup).toHaveBeenCalledTimes(2);
600+
expect(cleanUp).toHaveBeenCalledTimes(1);
601+
});
602+
});

packages/react-reconciler/src/ReactFiber.new.js

+3
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ function FiberNode(
148148
this.index = 0;
149149

150150
this.ref = null;
151+
this.refCleanup = null;
151152

152153
this.pendingProps = pendingProps;
153154
this.memoizedProps = null;
@@ -336,6 +337,7 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
336337
workInProgress.sibling = current.sibling;
337338
workInProgress.index = current.index;
338339
workInProgress.ref = current.ref;
340+
workInProgress.refCleanup = null;
339341

340342
if (enableProfilerTimer) {
341343
workInProgress.selfBaseDuration = current.selfBaseDuration;
@@ -880,6 +882,7 @@ export function assignFiberPropertiesInDEV(
880882
target.sibling = source.sibling;
881883
target.index = source.index;
882884
target.ref = source.ref;
885+
target.refCleanup = source.refCleanup;
883886
target.pendingProps = source.pendingProps;
884887
target.memoizedProps = source.memoizedProps;
885888
target.updateQueue = source.updateQueue;

packages/react-reconciler/src/ReactFiber.old.js

+3
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ function FiberNode(
148148
this.index = 0;
149149

150150
this.ref = null;
151+
this.refCleanup = null;
151152

152153
this.pendingProps = pendingProps;
153154
this.memoizedProps = null;
@@ -336,6 +337,7 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
336337
workInProgress.sibling = current.sibling;
337338
workInProgress.index = current.index;
338339
workInProgress.ref = current.ref;
340+
workInProgress.refCleanup = null;
339341

340342
if (enableProfilerTimer) {
341343
workInProgress.selfBaseDuration = current.selfBaseDuration;
@@ -880,6 +882,7 @@ export function assignFiberPropertiesInDEV(
880882
target.sibling = source.sibling;
881883
target.index = source.index;
882884
target.ref = source.ref;
885+
target.refCleanup = source.refCleanup;
883886
target.pendingProps = source.pendingProps;
884887
target.memoizedProps = source.memoizedProps;
885888
target.updateQueue = source.updateQueue;

packages/react-reconciler/src/ReactFiberCommitWork.new.js

+26-9
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,29 @@ function safelyAttachRef(current: Fiber, nearestMountedAncestor: Fiber | null) {
286286

287287
function safelyDetachRef(current: Fiber, nearestMountedAncestor: Fiber | null) {
288288
const ref = current.ref;
289-
if (ref !== null) {
289+
const refCleanup = current.refCleanup;
290+
291+
if (refCleanup !== null) {
292+
if (typeof ref === 'function') {
293+
try {
294+
if (shouldProfile(current)) {
295+
try {
296+
startLayoutEffectTimer();
297+
refCleanup();
298+
} finally {
299+
recordLayoutEffectDuration(current);
300+
}
301+
} else {
302+
refCleanup();
303+
}
304+
} catch (error) {
305+
captureCommitPhaseError(current, nearestMountedAncestor, error);
306+
}
307+
} else {
308+
// $FlowFixMe unable to narrow type to RefObject
309+
ref.refCleanup = null;
310+
}
311+
} else if (ref !== null) {
290312
if (typeof ref === 'function') {
291313
let retVal;
292314
try {
@@ -1583,14 +1605,9 @@ function commitAttachRef(finishedWork: Fiber) {
15831605
} else {
15841606
retVal = ref(instanceToUse);
15851607
}
1586-
if (__DEV__) {
1587-
if (typeof retVal === 'function') {
1588-
console.error(
1589-
'Unexpected return value from a callback ref in %s. ' +
1590-
'A callback ref should not return a function.',
1591-
getComponentNameFromFiber(finishedWork),
1592-
);
1593-
}
1608+
1609+
if (typeof retVal === 'function') {
1610+
finishedWork.refCleanup = retVal;
15941611
}
15951612
} else {
15961613
if (__DEV__) {

packages/react-reconciler/src/ReactFiberCommitWork.old.js

+26-9
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,29 @@ function safelyAttachRef(current: Fiber, nearestMountedAncestor: Fiber | null) {
286286

287287
function safelyDetachRef(current: Fiber, nearestMountedAncestor: Fiber | null) {
288288
const ref = current.ref;
289-
if (ref !== null) {
289+
const refCleanup = current.refCleanup;
290+
291+
if (refCleanup !== null) {
292+
if (typeof ref === 'function') {
293+
try {
294+
if (shouldProfile(current)) {
295+
try {
296+
startLayoutEffectTimer();
297+
refCleanup();
298+
} finally {
299+
recordLayoutEffectDuration(current);
300+
}
301+
} else {
302+
refCleanup();
303+
}
304+
} catch (error) {
305+
captureCommitPhaseError(current, nearestMountedAncestor, error);
306+
}
307+
} else {
308+
// $FlowFixMe unable to narrow type to RefObject
309+
ref.refCleanup = null;
310+
}
311+
} else if (ref !== null) {
290312
if (typeof ref === 'function') {
291313
let retVal;
292314
try {
@@ -1583,14 +1605,9 @@ function commitAttachRef(finishedWork: Fiber) {
15831605
} else {
15841606
retVal = ref(instanceToUse);
15851607
}
1586-
if (__DEV__) {
1587-
if (typeof retVal === 'function') {
1588-
console.error(
1589-
'Unexpected return value from a callback ref in %s. ' +
1590-
'A callback ref should not return a function.',
1591-
getComponentNameFromFiber(finishedWork),
1592-
);
1593-
}
1608+
1609+
if (typeof retVal === 'function') {
1610+
finishedWork.refCleanup = retVal;
15941611
}
15951612
} else {
15961613
if (__DEV__) {

packages/react-reconciler/src/ReactInternalTypes.js

+2
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ export type Fiber = {
131131
| (((handle: mixed) => void) & {_stringRef: ?string, ...})
132132
| RefObject,
133133

134+
refCleanup: null | (() => void),
135+
134136
// Input is the data coming into process this fiber. Arguments. Props.
135137
pendingProps: any, // This type will be more specific once we overload the tag.
136138
memoizedProps: any, // The props used to create the output.

0 commit comments

Comments
 (0)