Skip to content

Commit b3f7f91

Browse files
authored
Merge pull request #3213 from timdoherty/issue-3200-react-lazy-load
Issue #3200 React lazy load
2 parents 313bafb + 5005564 commit b3f7f91

File tree

3 files changed

+72
-13
lines changed

3 files changed

+72
-13
lines changed

react/lib/grid-stack-render-provider.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@ import {
44
useLayoutEffect,
55
useMemo,
66
useRef,
7+
useReducer,
78
} from "react";
89
import { useGridStackContext } from "./grid-stack-context";
910
import { GridStack, GridStackOptions, GridStackWidget } from "gridstack";
1011
import { GridStackRenderContext } from "./grid-stack-render-context";
1112
import isEqual from "react-fast-compare";
1213

1314
// WeakMap to store widget containers for each grid instance
14-
export const gridWidgetContainersMap = new WeakMap<GridStack, Map<string, HTMLElement>>();
15+
export const gridWidgetContainersMap = new WeakMap<
16+
GridStack,
17+
Map<string, HTMLElement>
18+
>();
1519

1620
export function GridStackRenderProvider({ children }: PropsWithChildren) {
1721
const {
@@ -22,6 +26,7 @@ export function GridStackRenderProvider({ children }: PropsWithChildren) {
2226
const widgetContainersRef = useRef<Map<string, HTMLElement>>(new Map());
2327
const containerRef = useRef<HTMLDivElement>(null);
2428
const optionsRef = useRef<GridStackOptions>(initialOptions);
29+
const [renderTick, forceRerender] = useReducer((value) => value + 1, 0);
2530

2631
const renderCBFn = useCallback(
2732
(element: HTMLElement, widget: GridStackWidget & { grid?: GridStack }) => {
@@ -33,9 +38,15 @@ export function GridStackRenderProvider({ children }: PropsWithChildren) {
3338
gridWidgetContainersMap.set(widget.grid, containers);
3439
}
3540
containers.set(widget.id, element);
36-
41+
3742
// Also update the local ref for backward compatibility
3843
widgetContainersRef.current.set(widget.id, element);
44+
45+
if (widget.lazyLoad || optionsRef.current.lazyLoad) {
46+
// We need to force a re-render, since React has
47+
// already eagerly rendered all widgets in the grid
48+
forceRerender();
49+
}
3950
}
4051
},
4152
[]
@@ -100,7 +111,7 @@ export function GridStackRenderProvider({ children }: PropsWithChildren) {
100111
}),
101112
// ! gridStack is required to reinitialize the grid when the options change
102113
// eslint-disable-next-line react-hooks/exhaustive-deps
103-
[gridStack]
114+
[gridStack, renderTick]
104115
)}
105116
>
106117
<div ref={containerRef}>{gridStack ? children : null}</div>

react/lib/grid-stack-render.tsx

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useRef, memo } from "react";
12
import { createPortal } from "react-dom";
23
import { useGridStackContext } from "./grid-stack-context";
34
import { useGridStackRenderContext } from "./grid-stack-render-context";
@@ -10,12 +11,24 @@ export interface ComponentDataType<T = object> {
1011
props: T;
1112
}
1213

14+
type ParsedComponentData = ComponentDataType & {
15+
error: unknown;
16+
metaHash: string;
17+
};
18+
1319
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1420
export type ComponentMap = Record<string, ComponentType<any>>;
1521

1622
function parseWeightMetaToComponentData(
17-
meta: GridStackWidget
23+
meta: GridStackWidget,
24+
cache: Map<string, ParsedComponentData>
1825
): ComponentDataType & { error: unknown } {
26+
const cacheKey = meta.id ?? "";
27+
const metaHash = meta.content ?? "";
28+
const cached = cache.get(cacheKey);
29+
// Ensure componentData is immutable between renders
30+
if (cached && cached.metaHash === metaHash) return cached;
31+
1932
let error = null;
2033
let name = "";
2134
let props = {};
@@ -31,37 +44,71 @@ function parseWeightMetaToComponentData(
3144
} catch (e) {
3245
error = e;
3346
}
34-
return {
47+
const parsed: ParsedComponentData = {
3548
name,
3649
props,
3750
error,
51+
metaHash,
3852
};
53+
cache.set(cacheKey, parsed);
54+
return parsed;
3955
}
4056

57+
type WidgetMemoProps = {
58+
id: string;
59+
componentData: ComponentDataType;
60+
widgetContainer: HTMLElement;
61+
WidgetComponent: ComponentType<unknown>;
62+
};
63+
64+
const WidgetMemo = memo(
65+
({
66+
id,
67+
componentData,
68+
widgetContainer,
69+
WidgetComponent,
70+
}: WidgetMemoProps) => {
71+
return (
72+
<GridStackWidgetContext.Provider value={{ widget: { id } }}>
73+
{createPortal(
74+
<WidgetComponent {...componentData.props} />,
75+
widgetContainer
76+
)}
77+
</GridStackWidgetContext.Provider>
78+
);
79+
}
80+
);
81+
WidgetMemo.displayName = "WidgetMemo";
82+
4183
export function GridStackRender(props: { componentMap: ComponentMap }) {
4284
const { _rawWidgetMetaMap } = useGridStackContext();
4385
const { getWidgetContainer } = useGridStackRenderContext();
86+
const parsedCache = useRef<Map<string, ParsedComponentData>>(new Map());
4487

4588
return (
4689
<>
4790
{Array.from(_rawWidgetMetaMap.value.entries()).map(([id, meta]) => {
48-
const componentData = parseWeightMetaToComponentData(meta);
91+
const componentData = parseWeightMetaToComponentData(
92+
meta,
93+
parsedCache.current
94+
);
4995

5096
const WidgetComponent = props.componentMap[componentData.name];
5197

5298
const widgetContainer = getWidgetContainer(id);
5399

54100
if (!widgetContainer) {
55-
throw new Error(`Widget container not found for id: ${id}`);
101+
return null;
56102
}
57103

58104
return (
59-
<GridStackWidgetContext.Provider key={id} value={{ widget: { id } }}>
60-
{createPortal(
61-
<WidgetComponent {...componentData.props} />,
62-
widgetContainer
63-
)}
64-
</GridStackWidgetContext.Provider>
105+
<WidgetMemo
106+
id={id}
107+
key={id}
108+
componentData={componentData}
109+
widgetContainer={widgetContainer}
110+
WidgetComponent={WidgetComponent}
111+
/>
65112
);
66113
})}
67114
</>

react/src/demo/demo.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ const gridOptions: GridStackOptions = {
130130
y: 2,
131131
},
132132
],
133+
lazyLoad: true
133134
};
134135

135136
export function GridStackDemo() {

0 commit comments

Comments
 (0)