Skip to content

Commit 8db17f5

Browse files
committed
Fixed ref passing and added ref tests for BodyPortal
1 parent 7f4367c commit 8db17f5

File tree

2 files changed

+67
-8
lines changed

2 files changed

+67
-8
lines changed

src/components/BodyPortal.spec.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import React from 'react';
12
import { render } from '@testing-library/react';
23
import { BodyPortal } from './BodyPortal';
34
import { BodyPortalSlotsContext } from './BodyPortalSlotsContext';
@@ -176,6 +177,61 @@ describe('BodyPortal', () => {
176177
Modal
177178
</div>
178179
</body>
180+
`);
181+
});
182+
183+
it('accepts an optional ref parameter that will be set', () => {
184+
const TestPortal = ({ children }: React.PropsWithChildren<{}>) => {
185+
const ref = React.useRef<HTMLElement | null>(null);
186+
expect(ref.current).toBeNull();
187+
188+
React.useEffect(() => {
189+
expect(ref.current).toBeInstanceOf(HTMLElement);
190+
}, []);
191+
192+
return <BodyPortal ref={ref} slot='footer' tagName='footer'>{children}</BodyPortal>;
193+
};
194+
render(<><TestPortal>Footer stuff</TestPortal><h1>Title</h1></>, { container: root });
195+
expect(document.body).toMatchInlineSnapshot(`
196+
<body>
197+
<main
198+
id="root"
199+
>
200+
<h1>
201+
Title
202+
</h1>
203+
</main>
204+
<footer
205+
data-portal-slot="footer"
206+
>
207+
Footer stuff
208+
</footer>
209+
</body>
210+
`);
211+
});
212+
213+
it('accepts a ref callback', () => {
214+
const setRef = jest.fn().mockImplementation((element) => {
215+
expect(element).toBeInstanceOf(HTMLElement);
216+
});
217+
render(<><BodyPortal ref={setRef} slot='footer' tagName='footer'>Footer stuff</BodyPortal>
218+
<h1>Title</h1></>, { container: root });
219+
expect(setRef).toHaveBeenCalled();
220+
expect(document.body).toMatchInlineSnapshot(`
221+
<body>
222+
<main
223+
id="root"
224+
>
225+
<h1>
226+
Title
227+
</h1>
228+
</main>
229+
<footer
230+
data-portal-slot="footer"
231+
>
232+
Footer stuff
233+
</footer>
234+
</body>
179235
`);
180236
});
181237
});

src/components/BodyPortal.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,24 @@ const getInsertBeforeTarget = (bodyPortalSlots: string[], slot?: string) => {
2121
return null;
2222
}
2323

24-
export const BodyPortal = ({
25-
children, className, ref, role, slot, tagName
26-
}: React.PropsWithChildren<{
24+
export const BodyPortal = React.forwardRef<HTMLElement, React.PropsWithChildren<{
2725
className?: string;
28-
ref?: React.MutableRefObject<HTMLElement | null>;
2926
role?: string;
3027
slot?: string;
31-
tagName?: string
32-
}>) => {
28+
tagName?: string;
29+
}>>(({ children, className, role, slot, tagName }, ref) => {
3330
const tag = tagName?.toUpperCase() ?? 'DIV';
3431
const internalRef = React.useRef<HTMLElement>(document.createElement(tag));
3532
if (internalRef.current.tagName !== tag) {
3633
internalRef.current = document.createElement(tag);
3734
}
38-
if (ref) { ref.current = internalRef.current; }
35+
if (ref) {
36+
if (typeof ref === 'function') {
37+
ref(internalRef.current);
38+
} else {
39+
ref.current = internalRef.current;
40+
}
41+
}
3942

4043
const bodyPortalOrderedRefs = React.useContext(BodyPortalSlotsContext);
4144

@@ -64,4 +67,4 @@ export const BodyPortal = ({
6467
}, [bodyPortalOrderedRefs, className, role, slot, tag]);
6568

6669
return createPortal(children, internalRef.current);
67-
};
70+
});

0 commit comments

Comments
 (0)