Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 964ca88

Browse files
committed
Support ref cleanup function for imperative handle refs
1 parent cf5ab8b commit 964ca88

File tree

2 files changed

+103
-17
lines changed

2 files changed

+103
-17
lines changed

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

Lines changed: 96 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,16 @@
99

1010
'use strict';
1111

12-
let React = require('react');
13-
let ReactDOMClient = require('react-dom/client');
14-
let act = require('internal-test-utils').act;
12+
const React = require('react');
13+
const ReactDOMClient = require('react-dom/client');
14+
const act = require('internal-test-utils').act;
1515

1616
// This is testing if string refs are deleted from `instance.refs`
1717
// Once support for string refs is removed, this test can be removed.
1818
// Detaching is already tested in refs-detruction-test.js
1919
describe('reactiverefs', () => {
2020
let container;
2121

22-
beforeEach(() => {
23-
jest.resetModules();
24-
React = require('react');
25-
ReactDOMClient = require('react-dom/client');
26-
act = require('internal-test-utils').act;
27-
});
28-
2922
afterEach(() => {
3023
if (container) {
3124
document.body.removeChild(container);
@@ -199,11 +192,6 @@ describe('reactiverefs', () => {
199192
describe('ref swapping', () => {
200193
let RefHopsAround;
201194
beforeEach(() => {
202-
jest.resetModules();
203-
React = require('react');
204-
ReactDOMClient = require('react-dom/client');
205-
act = require('internal-test-utils').act;
206-
207195
RefHopsAround = class extends React.Component {
208196
container = null;
209197
state = {count: 0};
@@ -804,3 +792,96 @@ describe('refs return clean up function', () => {
804792
expect(nullHandler).toHaveBeenCalledTimes(0);
805793
});
806794
});
795+
796+
describe('useImerativeHandle refs', () => {
797+
const ImperativeHandleComponent = React.forwardRef(({name}, ref) => {
798+
React.useImperativeHandle(
799+
ref,
800+
() => ({
801+
greet() {
802+
return `Hello ${name}`;
803+
},
804+
}),
805+
[name],
806+
);
807+
return null;
808+
});
809+
810+
it('should work with object style refs', async () => {
811+
const container = document.createElement('div');
812+
const root = ReactDOMClient.createRoot(container);
813+
const ref = React.createRef();
814+
815+
await act(async () => {
816+
root.render(<ImperativeHandleComponent name="Alice" ref={ref} />);
817+
});
818+
expect(ref.current.greet()).toBe('Hello Alice');
819+
await act(() => {
820+
root.render(null);
821+
});
822+
expect(ref.current).toBe(null);
823+
});
824+
825+
it('should work with callback style refs', async () => {
826+
const container = document.createElement('div');
827+
const root = ReactDOMClient.createRoot(container);
828+
let current = null;
829+
830+
await act(async () => {
831+
root.render(
832+
<ImperativeHandleComponent
833+
name="Alice"
834+
ref={r => {
835+
current = r;
836+
}}
837+
/>,
838+
);
839+
});
840+
expect(current.greet()).toBe('Hello Alice');
841+
await act(() => {
842+
root.render(null);
843+
});
844+
expect(current).toBe(null);
845+
});
846+
847+
it('should work with callback style refs with cleanup function', async () => {
848+
const container = document.createElement('div');
849+
const root = ReactDOMClient.createRoot(container);
850+
851+
let cleanupCalls = 0;
852+
let createCalls = 0;
853+
let current = null;
854+
855+
const ref = r => {
856+
current = r;
857+
createCalls++;
858+
return () => {
859+
current = null;
860+
cleanupCalls++;
861+
};
862+
};
863+
864+
await act(async () => {
865+
root.render(<ImperativeHandleComponent name="Alice" ref={ref} />);
866+
});
867+
expect(current.greet()).toBe('Hello Alice');
868+
expect(createCalls).toBe(1);
869+
expect(cleanupCalls).toBe(0);
870+
871+
// update a dep should recreate the ref
872+
await act(async () => {
873+
root.render(<ImperativeHandleComponent name="Bob" ref={ref} />);
874+
});
875+
expect(current.greet()).toBe('Hello Bob');
876+
expect(createCalls).toBe(2);
877+
expect(cleanupCalls).toBe(1);
878+
879+
// unmounting should call cleanup
880+
await act(() => {
881+
root.render(null);
882+
});
883+
expect(current).toBe(null);
884+
expect(createCalls).toBe(2);
885+
expect(cleanupCalls).toBe(2);
886+
});
887+
});

packages/react-reconciler/src/ReactFiberHooks.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2564,9 +2564,14 @@ function imperativeHandleEffect<T>(
25642564
if (typeof ref === 'function') {
25652565
const refCallback = ref;
25662566
const inst = create();
2567-
refCallback(inst);
2567+
const refCleanup = refCallback(inst);
25682568
return () => {
2569-
refCallback(null);
2569+
if (typeof refCleanup === 'function') {
2570+
// $FlowFixMe[incompatible-use] we need to assume no parameters
2571+
refCleanup();
2572+
} else {
2573+
refCallback(null);
2574+
}
25702575
};
25712576
} else if (ref !== null && ref !== undefined) {
25722577
const refObject = ref;

0 commit comments

Comments
 (0)