Skip to content

Commit dce6bdc

Browse files
authored
fix(Ref): handle primitive elements with findDOMNode (#35779)
1 parent 5e7c32d commit dce6bdc

File tree

2 files changed

+108
-17
lines changed

2 files changed

+108
-17
lines changed

packages/fluentui/react-component-ref/src/Ref.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export interface RefProps {
1616
}
1717

1818
export interface RefState {
19-
kind: 'self' | 'forward' | 'find' | null;
19+
kind: 'self' | 'direct' | 'find' | null;
2020
}
2121

2222
// ========================================================
@@ -51,7 +51,7 @@ function isFiberRef(node: Element | Fiber | Text | null): boolean {
5151

5252
export class Ref extends React.Component<RefProps, RefState> {
5353
nodeForFind?: Node | null;
54-
nodeForForward?: Node | null;
54+
nodeForDirect?: Node | null;
5555

5656
state = { kind: null };
5757

@@ -62,20 +62,20 @@ export class Ref extends React.Component<RefProps, RefState> {
6262
return { kind: 'self' };
6363
}
6464

65-
if (ReactIs.isForwardRef(child)) {
66-
return { kind: 'forward' };
65+
if (typeof child.type === 'string' || ReactIs.isForwardRef(child)) {
66+
return { kind: 'direct' };
6767
}
6868

6969
return { kind: 'find' };
7070
}
7171

72-
handleForwardRefOverride = (node: HTMLElement) => {
72+
handleRefOverride = (node: HTMLElement) => {
7373
const { children, innerRef } = this.props;
7474

7575
handleRef((children as React.ReactElement & { ref: React.Ref<any> }).ref, node);
7676
handleRef(innerRef, node);
7777

78-
this.nodeForForward = node;
78+
this.nodeForDirect = node;
7979
};
8080

8181
handleSelfOverride = (node: HTMLElement) => {
@@ -101,9 +101,9 @@ export class Ref extends React.Component<RefProps, RefState> {
101101
}
102102

103103
componentDidUpdate(prevProps: RefProps) {
104-
if (this.state.kind === 'forward') {
104+
if (this.state.kind === 'direct') {
105105
if (prevProps.innerRef !== this.props.innerRef) {
106-
handleRef(this.props.innerRef, this.nodeForForward);
106+
handleRef(this.props.innerRef, this.nodeForDirect);
107107
}
108108
} else if (this.state.kind === 'find') {
109109
let currentNode = ReactDOM.findDOMNode(this);
@@ -142,9 +142,9 @@ export class Ref extends React.Component<RefProps, RefState> {
142142
return childWithProps;
143143
}
144144

145-
if (this.state.kind === 'forward') {
145+
if (this.state.kind === 'direct') {
146146
return React.cloneElement(childWithProps, {
147-
ref: this.handleForwardRefOverride,
147+
ref: this.handleRefOverride,
148148
});
149149
}
150150

packages/fluentui/react-component-ref/test/Ref-test.tsx

Lines changed: 98 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,18 @@ describe('Ref', () => {
6161
});
6262

6363
it('passes an updated node', () => {
64+
const ComponentA = () => <div />;
65+
const ComponentB = () => <button />;
66+
6467
const innerRef = jest.fn();
6568
const wrapper = mount(
6669
<Ref innerRef={innerRef}>
67-
<div />
70+
<ComponentA />
6871
</Ref>,
6972
);
7073

7174
expect(innerRef).toHaveBeenCalledWith(expect.objectContaining({ tagName: 'DIV' }));
72-
wrapper.setProps({ children: <button /> });
75+
wrapper.setProps({ children: <ComponentB /> });
7376

7477
expect(innerRef).toHaveBeenCalledTimes(2);
7578
expect(innerRef).toHaveBeenCalledWith(expect.objectContaining({ tagName: 'BUTTON' }));
@@ -110,36 +113,62 @@ describe('Ref', () => {
110113
});
111114

112115
it('resolves once on node/ref change', () => {
116+
const ComponentA = () => <div />;
117+
const ComponentB = () => <span />;
118+
113119
const initialRef = jest.fn();
114120
const updatedRef = jest.fn();
121+
115122
const wrapper = mount(
116123
<Ref innerRef={initialRef}>
117-
<div />
124+
<ComponentA />
118125
</Ref>,
119126
);
120127

121128
expect(initialRef).toHaveBeenCalledTimes(1);
122129
expect(updatedRef).not.toHaveBeenCalled();
123130

131+
// ---
132+
124133
jest.resetAllMocks();
125-
wrapper.setProps({ children: <span />, innerRef: updatedRef });
134+
135+
wrapper.setProps({ children: <ComponentB />, innerRef: updatedRef });
126136

127137
expect(initialRef).not.toHaveBeenCalled();
128138
expect(updatedRef).toHaveBeenCalledTimes(1);
129139
});
130140

131141
it('always returns "null" for react-test-renderer', () => {
142+
const Component = () => <div />;
132143
const innerRef = jest.fn();
133-
const ref = jest.fn();
134144

135145
create(
136146
<Ref innerRef={innerRef}>
137-
<div ref={ref} />
147+
<Component />
138148
</Ref>,
139149
);
140150

141151
expect(innerRef).toHaveBeenCalledWith(null);
142-
expect(ref).toHaveBeenCalledWith(null);
152+
});
153+
154+
it('handles unmount', () => {
155+
const Component = () => <div />;
156+
const innerRef = jest.fn();
157+
158+
const wrapper = mount(
159+
<Ref innerRef={innerRef}>
160+
<Component />
161+
</Ref>,
162+
);
163+
164+
expect(innerRef).toHaveBeenCalledWith(expect.objectContaining({ tagName: 'DIV' }));
165+
166+
// ---
167+
168+
jest.resetAllMocks();
169+
wrapper.unmount();
170+
171+
expect(innerRef).toHaveBeenCalledWith(null);
143172
});
144173
});
145174

@@ -201,6 +230,65 @@ describe('Ref', () => {
201230
});
202231
});
203232

233+
describe('kind="forward" (plain element)', () => {
234+
it('applies refs', () => {
235+
const innerRef = jest.fn();
236+
const elRef = jest.fn();
237+
238+
mount(
239+
<Ref innerRef={innerRef}>
240+
<button ref={elRef} />
241+
</Ref>,
242+
);
243+
244+
expect(innerRef).toHaveBeenCalledWith(expect.objectContaining({ tagName: 'BUTTON' }));
245+
expect(elRef).toHaveBeenCalledWith(expect.objectContaining({ tagName: 'BUTTON' }));
246+
});
247+
248+
it('passes an updated node', () => {
249+
const innerRef = jest.fn();
250+
251+
mount(
252+
<Ref innerRef={innerRef}>
253+
<button />
254+
</Ref>,
255+
);
256+
257+
expect(innerRef).toHaveBeenCalledWith(expect.objectContaining({ tagName: 'BUTTON' }));
258+
259+
// ---
260+
261+
jest.resetAllMocks();
262+
263+
mount(
264+
<Ref innerRef={innerRef}>
265+
<div />
266+
</Ref>,
267+
);
268+
269+
expect(innerRef).toHaveBeenCalledWith(expect.objectContaining({ tagName: 'DIV' }));
270+
});
271+
272+
it('handles unmount', () => {
273+
const innerRef = jest.fn();
274+
275+
const wrapper = mount(
276+
<Ref innerRef={innerRef}>
277+
<button />
278+
</Ref>,
279+
);
280+
281+
expect(innerRef).toHaveBeenCalledWith(expect.objectContaining({ tagName: 'BUTTON' }));
282+
283+
// ---
284+
285+
jest.resetAllMocks();
286+
wrapper.unmount();
287+
288+
expect(innerRef).toHaveBeenCalledWith(null);
289+
});
290+
});
291+
204292
describe('kind="self"', () => {
205293
it('works with "forwardRef" API', () => {
206294
const forwardedRef = React.createRef<HTMLButtonElement>();
@@ -239,7 +327,10 @@ describe('Ref', () => {
239327
expect(innerRef).toHaveBeenCalledWith(expect.objectContaining({ tagName: 'DIV' }));
240328
expect(outerRef).toHaveBeenCalledWith(expect.objectContaining({ tagName: 'DIV' }));
241329

330+
// ---
331+
242332
jest.resetAllMocks();
333+
243334
wrapper.setProps({
244335
children: (
245336
<Ref innerRef={innerRef}>

0 commit comments

Comments
 (0)