-
Notifications
You must be signed in to change notification settings - Fork 126
/
RenderInnerContainer.tsx
72 lines (61 loc) · 3.04 KB
/
RenderInnerContainer.tsx
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
67
68
69
70
71
72
import React from 'react';
import ReactDOM from 'react-dom';
import propTypes from 'prop-types';
import { ReactNodePropTypes } from '../../lib/utils';
import { Nullable } from '../../typings/utility-types';
import { safePropTypesInstanceOf } from '../../lib/SSRSafe';
import { PortalProps, RenderContainerProps } from './RenderContainerTypes';
interface RenderInnerContainerProps extends RenderContainerProps {
domContainer: Nullable<HTMLElement>;
rootId: string;
}
// Заглушка нужна для корректной гидрации порталов после SSR,
// которую реакт сам пока не поддерживает.
// @see https://github.com/facebook/react/issues/13097
// А также для вставки актуального render-container-id на клиенте.
//
// Дело в том, что во время гидрации, структура HTML на сервере
// и на клиенте должна совпадать, иначе возможны артефакты.
// Алгоритм там примерно такой. Клиент во время гидрации идет
// по этим двум деревьям и сравнивает узлы. Элементы разных типов
// он подменяет на свои. А те, что совпадают, он оставляет как есть
// вместе со всеми атрибутами, навесив только обработчики событий.
//
// Поэтому, для портала, который рендерится только на клиенте,
// нужно использовать серверную заглушку, чтобы при гидрации
// он не испортил какой-то другой элемент. Null не подходит,
// т.к. на сервере он тоже не рендерится.
// А элемент с render-container-id нужно отрендерить с нуля.
const SSRPlaceholder = () => <script data-id="ssr-placeholder" />;
export const Portal = ({ container, rt_rootID, children }: PortalProps) => {
// container exists only in browser
return (
<React.Fragment>
{container ? ReactDOM.createPortal(children, container) : <SSRPlaceholder />}
{container ? <noscript data-render-container-id={rt_rootID} /> : <SSRPlaceholder />}
</React.Fragment>
);
};
export class RenderInnerContainer extends React.Component<RenderInnerContainerProps> {
public static __KONTUR_REACT_UI__ = 'RenderInnerContainer';
public render() {
const { anchor, children, domContainer, rootId } = this.props;
let inner = anchor;
if (children) {
inner = (
<React.Fragment>
{anchor}
<Portal key="portal-ref" rt_rootID={rootId} container={domContainer}>
{children}
</Portal>
</React.Fragment>
);
}
return inner;
}
}
Portal.propTypes = {
container: safePropTypesInstanceOf(() => HTMLElement),
rt_rootID: propTypes.string.isRequired,
children: ReactNodePropTypes.isRequired,
};