Skip to content

Commit 3cb0a57

Browse files
committed
useImperativeHandle returns null if node is null
We shouldn't create an object if the underlying node is null, as this handle is meant to emulate the node but can't proxy methods if none exist.
1 parent bcb6534 commit 3cb0a57

File tree

2 files changed

+73
-56
lines changed

2 files changed

+73
-56
lines changed

packages/react-strict-dom/src/native/modules/useStrictDOMElement.js

Lines changed: 59 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -43,62 +43,65 @@ export function useStrictDOMElement<T>(
4343
const { scale: viewportScale } = useViewportScale();
4444

4545
React.useImperativeHandle(ref, () => {
46-
return {
47-
get __nativeTag() {
48-
const node = nativeRef.current as Node;
49-
return node?.__nativeTag;
50-
},
51-
addEventListener: proxy(nativeRef, 'addEventListener'),
52-
animate: proxy(nativeRef, 'animate'),
53-
blur: proxy(nativeRef, 'blur'),
54-
click: proxy(nativeRef, 'click'),
55-
get complete() {
56-
if (tagName === 'img') {
57-
const node = nativeRef.current as Node;
58-
if (node?.complete == null) {
59-
// Assume images are never pre-loaded in React Native
60-
return false;
61-
} else {
62-
return node.complete;
63-
}
64-
}
65-
},
66-
contains: proxy(nativeRef, 'contains'),
67-
dispatchEvent: proxy(nativeRef, 'dispatchEvent'),
68-
focus: proxy(nativeRef, 'focus'),
69-
getAttribute: proxy(nativeRef, 'getAttribute'),
70-
getBoundingClientRect() {
71-
const node = nativeRef.current as Node;
72-
const getBoundingClientRect =
73-
node?.getBoundingClientRect ?? node?.unstable_getBoundingClientRect;
74-
if (getBoundingClientRect) {
75-
const rect = getBoundingClientRect.call(node);
76-
if (viewportScale !== 1) {
77-
return new DOMRect(
78-
rect.x / viewportScale,
79-
rect.y / viewportScale,
80-
rect.width / viewportScale,
81-
rect.height / viewportScale
82-
);
83-
}
84-
return rect;
85-
}
86-
return errorUnimplemented('getBoundingClientRect');
87-
},
88-
getRootNode: proxy(nativeRef, 'getRootNode'),
89-
hasPointerCapture: proxy(nativeRef, 'hasPointerCapture'),
90-
nodeName: tagName.toUpperCase(),
91-
releasePointerCapture: proxy(nativeRef, 'releasePointerCapture'),
92-
removeEventListener: proxy(nativeRef, 'removeEventListener'),
93-
scroll: proxy(nativeRef, 'scroll'),
94-
scrollBy: proxy(nativeRef, 'scrollBy'),
95-
scrollIntoView: proxy(nativeRef, 'scrollIntoView'),
96-
scrollTo: proxy(nativeRef, 'scrollTo'),
97-
select: proxy(nativeRef, 'select'),
98-
setSelectionRange: proxy(nativeRef, 'setSelectionRange'),
99-
setPointerCapture: proxy(nativeRef, 'setPointerCapture'),
100-
showPicker: proxy(nativeRef, 'showPicker')
101-
};
46+
return nativeRef.current == null
47+
? null
48+
: {
49+
get __nativeTag() {
50+
const node = nativeRef.current as Node;
51+
return node?.__nativeTag;
52+
},
53+
addEventListener: proxy(nativeRef, 'addEventListener'),
54+
animate: proxy(nativeRef, 'animate'),
55+
blur: proxy(nativeRef, 'blur'),
56+
click: proxy(nativeRef, 'click'),
57+
get complete() {
58+
if (tagName === 'img') {
59+
const node = nativeRef.current as Node;
60+
if (node?.complete == null) {
61+
// Assume images are never pre-loaded in React Native
62+
return false;
63+
} else {
64+
return node.complete;
65+
}
66+
}
67+
},
68+
contains: proxy(nativeRef, 'contains'),
69+
dispatchEvent: proxy(nativeRef, 'dispatchEvent'),
70+
focus: proxy(nativeRef, 'focus'),
71+
getAttribute: proxy(nativeRef, 'getAttribute'),
72+
getBoundingClientRect() {
73+
const node = nativeRef.current as Node;
74+
const getBoundingClientRect =
75+
node?.getBoundingClientRect ??
76+
node?.unstable_getBoundingClientRect;
77+
if (getBoundingClientRect) {
78+
const rect = getBoundingClientRect.call(node);
79+
if (viewportScale !== 1) {
80+
return new DOMRect(
81+
rect.x / viewportScale,
82+
rect.y / viewportScale,
83+
rect.width / viewportScale,
84+
rect.height / viewportScale
85+
);
86+
}
87+
return rect;
88+
}
89+
return errorUnimplemented('getBoundingClientRect');
90+
},
91+
getRootNode: proxy(nativeRef, 'getRootNode'),
92+
hasPointerCapture: proxy(nativeRef, 'hasPointerCapture'),
93+
nodeName: tagName.toUpperCase(),
94+
releasePointerCapture: proxy(nativeRef, 'releasePointerCapture'),
95+
removeEventListener: proxy(nativeRef, 'removeEventListener'),
96+
scroll: proxy(nativeRef, 'scroll'),
97+
scrollBy: proxy(nativeRef, 'scrollBy'),
98+
scrollIntoView: proxy(nativeRef, 'scrollIntoView'),
99+
scrollTo: proxy(nativeRef, 'scrollTo'),
100+
select: proxy(nativeRef, 'select'),
101+
setSelectionRange: proxy(nativeRef, 'setSelectionRange'),
102+
setPointerCapture: proxy(nativeRef, 'setPointerCapture'),
103+
showPicker: proxy(nativeRef, 'showPicker')
104+
};
102105
}, [tagName, viewportScale]);
103106

104107
return nativeRef;

packages/react-strict-dom/tests/html-test.native.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1563,6 +1563,20 @@ describe('<html.*>', () => {
15631563
});
15641564
*/
15651565

1566+
// We shouldn't create a handle if there is no underlying node
1567+
test('node is null', () => {
1568+
act(() => {
1569+
create(
1570+
<html.input
1571+
ref={(node) => {
1572+
expect(() => node.getBoundingClientRect()).toThrow();
1573+
}}
1574+
/>,
1575+
{ createNodeMock: () => null }
1576+
);
1577+
});
1578+
});
1579+
15661580
[
15671581
'animate',
15681582
'click',

0 commit comments

Comments
 (0)