-
Notifications
You must be signed in to change notification settings - Fork 461
Expand file tree
/
Copy pathuseCustomElementPortal.tsx
More file actions
66 lines (55 loc) · 1.95 KB
/
Copy pathuseCustomElementPortal.tsx
File metadata and controls
66 lines (55 loc) · 1.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import type React from 'react';
import { useRef, useState } from 'react';
import { createPortal } from 'react-dom';
export type UseCustomElementPortalParams = {
component: React.ReactNode;
id: string | number;
};
export type UseCustomElementPortalReturn = {
portal: React.ComponentType;
mount: (node: Element) => void;
unmount: () => void;
id: string | number;
};
type PortalKey = string | number;
// This function takes a component as prop, and returns functions that mount and unmount
// the given component into a given node
export const useCustomElementPortal = (elements: UseCustomElementPortalParams[]) => {
const [nodeMap, setNodeMap] = useState<Map<PortalKey, Element | null>>(new Map());
const nodeMapRef = useRef(nodeMap);
const elementsRef = useRef<Map<PortalKey, React.ReactNode>>(new Map());
const portalsRef = useRef<Map<PortalKey, UseCustomElementPortalReturn>>(new Map());
nodeMapRef.current = nodeMap;
elementsRef.current = new Map(elements.map(el => [el.id, el.component]));
const elementIds = new Set(elements.map(el => el.id));
portalsRef.current.forEach((_, id) => {
if (!elementIds.has(id)) {
portalsRef.current.delete(id);
}
});
return elements.map(el => {
const id = el.id;
const existingPortal = portalsRef.current.get(id);
if (existingPortal) {
return existingPortal;
}
const portal: React.ComponentType = () => {
const node = nodeMapRef.current.get(id);
const component = elementsRef.current.get(id);
return node ? createPortal(component, node) : null;
};
const customElementPortal = {
id: el.id,
mount: (node: Element) => setNodeMap(prev => new Map(prev).set(id, node)),
unmount: () =>
setNodeMap(prev => {
const newMap = new Map(prev);
newMap.set(id, null);
return newMap;
}),
portal,
};
portalsRef.current.set(id, customElementPortal);
return customElementPortal;
});
};