Skip to content

Commit

Permalink
use async rendering but defer it to microtask
Browse files Browse the repository at this point in the history
  • Loading branch information
vursen committed Nov 19, 2024
1 parent 91499bd commit 768626e
Show file tree
Hide file tree
Showing 9 changed files with 78 additions and 50 deletions.
12 changes: 8 additions & 4 deletions packages/react-components-pro/src/GridProEditColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,16 @@ function GridProEditColumn<TItem = GridDefaultItem>(
ref: ForwardedRef<GridProEditColumnElement<TItem>>,
): ReactElement | null {
const [editModePortals, editModeRenderer] = useModelRenderer(editColumnReactRenderer(props.editModeRenderer), {
renderSync: true,
renderMode: 'sync',
});
const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header, {
renderMode: 'microtask',
});
const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer, {
renderMode: 'microtask',
});
const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header);
const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer);
const [bodyPortals, bodyRenderer] = useModelRenderer(editColumnReactRenderer(props.renderer ?? children), {
renderSync: true,
renderMode: 'sync',
});

return (
Expand Down
25 changes: 8 additions & 17 deletions packages/react-components/src/Grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import {
forwardRef,
type ReactElement,
type RefAttributes,
useEffect,
useLayoutEffect,
useRef,
useState,
} from 'react';
import {
Grid as _Grid,
Expand All @@ -30,28 +28,21 @@ function Grid<TItem = GridDefaultItem>(
props: GridProps<TItem>,
ref: ForwardedRef<GridElement<TItem>>,
): ReactElement | null {
const [portals, rowDetailsRenderer] = useModelRenderer(props.rowDetailsRenderer);
const [portals, rowDetailsRenderer] = useModelRenderer(props.rowDetailsRenderer, {
renderMode: 'microtask'
});

const innerRef = useRef<GridElement & { _recalculateColumnWidths?(...args: any[]): void }>(null);
const innerRef = useRef<GridElement>(null);
const finalRef = useMergedRefs(innerRef, ref);

const [pendingRecalculateColumnWidthsCall, setPendingRecalculateColumnWidthsCall] = useState<any[] | null>(null);

useLayoutEffect(() => {
innerRef.current!._recalculateColumnWidths = function (...args) {
setPendingRecalculateColumnWidthsCall(args);
innerRef.current!.recalculateColumnWidths = function (...args) {
queueMicrotask(() => {
Object.getPrototypeOf(this).recalculateColumnWidths.call(this, ...args);
});
};
}, []);

useLayoutEffect(() => {
if (pendingRecalculateColumnWidthsCall) {
const gridElement = innerRef.current!;
const gridProto = Object.getPrototypeOf(gridElement);
gridProto._recalculateColumnWidths.call(gridElement, ...pendingRecalculateColumnWidthsCall);
setPendingRecalculateColumnWidthsCall(null);
}
}, [pendingRecalculateColumnWidthsCall]);

return (
<_Grid<TItem> {...props} ref={finalRef} rowDetailsRenderer={rowDetailsRenderer}>
{props.children}
Expand Down
12 changes: 9 additions & 3 deletions packages/react-components/src/GridColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,15 @@ function GridColumn<TItem = GridDefaultItem>(
{ children, footer, header, ...props }: GridColumnProps<TItem>,
ref: ForwardedRef<GridColumnElement<TItem>>,
): ReactElement | null {
const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header);
const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer);
const [bodyPortals, bodyRenderer] = useModelRenderer(props.renderer ?? children);
const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header, {
renderMode: 'microtask',
});
const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer, {
renderMode: 'microtask',
});
const [bodyPortals, bodyRenderer] = useModelRenderer(props.renderer ?? children, {
renderMode: 'microtask',
});

return (
<_GridColumn<TItem>
Expand Down
8 changes: 6 additions & 2 deletions packages/react-components/src/GridColumnGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,12 @@ function GridColumnGroup(
{ children, footer, header, ...props }: GridColumnGroupProps,
ref: ForwardedRef<GridColumnGroupElement>,
): ReactElement | null {
const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header);
const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer);
const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header, {
renderMode: 'microtask',
});
const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer, {
renderMode: 'microtask',
});

return (
<_GridColumnGroup {...props} footerRenderer={footerRenderer} headerRenderer={headerRenderer} ref={ref}>
Expand Down
8 changes: 6 additions & 2 deletions packages/react-components/src/GridFilterColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ function GridFilterColumn<TItem = GridDefaultItem>(
{ footer, ...props }: GridFilterColumnProps<TItem>,
ref: ForwardedRef<GridFilterColumnElement<TItem>>,
): ReactElement | null {
const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer);
const [bodyPortals, bodyRenderer] = useModelRenderer(props.renderer ?? props.children);
const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer, {
renderMode: 'microtask',
});
const [bodyPortals, bodyRenderer] = useModelRenderer(props.renderer ?? props.children, {
renderMode: 'microtask',
});

return (
<_GridFilterColumn<TItem> {...props} footerRenderer={footerRenderer} ref={ref} renderer={bodyRenderer}>
Expand Down
12 changes: 9 additions & 3 deletions packages/react-components/src/GridSelectionColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,15 @@ function GridSelectionColumn<TItem = GridDefaultItem>(
{ footer, header, ...props }: GridSelectionColumnProps<TItem>,
ref: ForwardedRef<GridSelectionColumnElement<TItem>>,
): ReactElement | null {
const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header);
const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer);
const [bodyPortals, bodyRenderer] = useModelRenderer(props.renderer ?? props.children);
const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header, {
renderMode: 'microtask',
});
const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer, {
renderMode: 'microtask',
});
const [bodyPortals, bodyRenderer] = useModelRenderer(props.renderer ?? props.children, {
renderMode: 'microtask',
});

return (
<_GridSelectionColumn<TItem>
Expand Down
8 changes: 6 additions & 2 deletions packages/react-components/src/GridSortColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ function GridSortColumn<TItem = GridDefaultItem>(
{ footer, ...props }: GridSortColumnProps<TItem>,
ref: ForwardedRef<GridSortColumnElement<TItem>>,
): ReactElement | null {
const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer);
const [bodyPortals, bodyRenderer] = useModelRenderer(props.renderer ?? props.children);
const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer, {
renderMode: 'microtask',
});
const [bodyPortals, bodyRenderer] = useModelRenderer(props.renderer ?? props.children, {
renderMode: 'microtask',
});

return (
<_GridSortColumn<TItem> {...props} footerRenderer={footerRenderer} ref={ref} renderer={bodyRenderer}>
Expand Down
8 changes: 6 additions & 2 deletions packages/react-components/src/GridTreeColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,12 @@ function GridTreeColumn<TItem = GridDefaultItem>(
{ footer, header, ...props }: GridTreeColumnProps<TItem>,
ref: ForwardedRef<GridTreeColumnElement<TItem>>,
): ReactElement | null {
const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header);
const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer);
const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header, {
renderMode: 'microtask',
});
const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer, {
renderMode: 'microtask',
});

return (
<_GridTreeColumn<TItem> {...props} headerRenderer={headerRenderer} footerRenderer={footerRenderer} ref={ref}>
Expand Down
35 changes: 20 additions & 15 deletions packages/react-components/src/renderers/useRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,25 @@ function rendererReducer<W extends WebComponentRenderer>(
}

export type RendererConfig = {
renderSync?: boolean;
renderMode?: 'default' | 'sync' | 'microtask';
};

const renderQueue: Array<(...args: any[]) => any> = [];

function flushMicrotask(callback: (...args: any[]) => any) {
renderQueue.push(callback);

if (renderQueue.length === 1) {
queueMicrotask(() => {
flushSync(() => {
while (renderQueue.length) {
renderQueue.shift()!();
}
});
});
}
}

export function useRenderer<P extends {}, W extends WebComponentRenderer>(
node: ReactNode,
convert?: (props: Slice<Parameters<W>, 1>) => PropsWithChildren<P>,
Expand All @@ -46,21 +62,10 @@ export function useRenderer<P extends {}, W extends WebComponentRenderer>(
const [map, update] = useReducer<typeof rendererReducer<W>>(rendererReducer, initialState);
const renderer = useCallback(
((...args: Parameters<W>) => {
if (config?.renderSync) {
// The web components may request multiple synchronous renderer calls that
// would result in flushSync logging a warning (and actually executing the
// overlapping flushSync in microtask timing). Suppress the warning and allow
// the resulting asynchronicity.
const console = globalThis.console as any;
const error = console.error;
console.error = (message: string) => {
if (message.includes('flushSync')) {
return;
}
error(message);
};
if (config?.renderMode === 'microtask') {
flushMicrotask(() => update(args));
} else if (config?.renderMode === 'sync') {
flushSync(() => update(args));
console.error = error;
} else {
update(args);
}
Expand Down

0 comments on commit 768626e

Please sign in to comment.