Skip to content
Prev Previous commit
Next Next commit
Add maySuspendCommitOnUpdate Config for optimizations
Since we no longer diff all props in the render phase we might still get
here when nothing has changed. So this adds a small optimization to avoid
resuspending on every update.
  • Loading branch information
sebmarkbage committed Apr 4, 2025
commit 93eae2ab9f27809d0b0f70928dd348efcdaef785
4 changes: 4 additions & 0 deletions packages/react-art/src/ReactFiberConfigART.js
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,10 @@ export function maySuspendCommit(type, props) {
return false;
}

export function maySuspendCommitOnUpdate(type, oldProps, newProps) {
return false;
}

export function preloadInstance(type, props) {
// Return true to indicate it's already loaded
return true;
Expand Down
16 changes: 14 additions & 2 deletions packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export type Props = {
size?: number,
multiple?: boolean,
src?: string,
srcSet?: string,
loading?: 'eager' | 'lazy',
onLoad?: (event: any) => void,
...
Expand Down Expand Up @@ -773,9 +774,9 @@ export function commitMount(
// only need to assign one. And Safari just never triggers a new load event which means this technique
// is already a noop regardless of which properties are assigned. We should revisit if browsers update
// this heuristic in the future.
if ((newProps: any).src) {
if (newProps.src) {
((domElement: any): HTMLImageElement).src = (newProps: any).src;
} else if ((newProps: any).srcSet) {
} else if (newProps.srcSet) {
((domElement: any): HTMLImageElement).srcset = (newProps: any).srcSet;
}
return;
Expand Down Expand Up @@ -4992,6 +4993,17 @@ export function maySuspendCommit(type: Type, props: Props): boolean {
);
}

export function maySuspendCommitOnUpdate(
type: Type,
oldProps: Props,
newProps: Props,
): boolean {
return (
maySuspendCommit(type, newProps) &&
(newProps.src !== oldProps.src || newProps.srcSet !== oldProps.srcSet)
);
}

export function mayResourceSuspendCommit(resource: Resource): boolean {
return (
resource.type === 'stylesheet' &&
Expand Down
8 changes: 8 additions & 0 deletions packages/react-native-renderer/src/ReactFiberConfigFabric.js
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,14 @@ export function maySuspendCommit(type: Type, props: Props): boolean {
return false;
}

export function maySuspendCommitOnUpdate(
type: Type,
oldProps: Props,
newProps: Props,
): boolean {
return false;
}

export function preloadInstance(type: Type, props: Props): boolean {
return true;
}
Expand Down
8 changes: 8 additions & 0 deletions packages/react-native-renderer/src/ReactFiberConfigNative.js
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,14 @@ export function maySuspendCommit(type: Type, props: Props): boolean {
return false;
}

export function maySuspendCommitOnUpdate(
type: Type,
oldProps: Props,
newProps: Props,
): boolean {
return false;
}

export function preloadInstance(type: Type, props: Props): boolean {
// Return false to indicate it's already loaded
return true;
Expand Down
12 changes: 12 additions & 0 deletions packages/react-noop-renderer/src/createReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,18 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
return type === 'suspensey-thing' && typeof props.src === 'string';
},

maySuspendCommitOnUpdate(
type: string,
oldProps: Props,
newProps: Props,
): boolean {
// Asks whether it's possible for this combination of type and props
// to ever need to suspend. This is different from asking whether it's
// currently ready because even if it's ready now, it might get purged
// from the cache later.
return type === 'suspensey-thing' && typeof newProps.src === 'string';
},

mayResourceSuspendCommit(resource: mixed): boolean {
throw new Error(
'Resources are not implemented for React Noop yet. This method should not be called',
Expand Down
18 changes: 14 additions & 4 deletions packages/react-reconciler/src/ReactFiberCompleteWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ import {
preparePortalMount,
prepareScopeUpdate,
maySuspendCommit,
maySuspendCommitOnUpdate,
mayResourceSuspendCommit,
preloadInstance,
preloadResource,
Expand Down Expand Up @@ -547,10 +548,16 @@ function updateHostComponent(
function preloadInstanceAndSuspendIfNeeded(
workInProgress: Fiber,
type: Type,
props: Props,
oldProps: null | Props,
newProps: Props,
renderLanes: Lanes,
) {
if (!maySuspendCommit(type, props)) {
const maySuspend =
oldProps === null
? maySuspendCommit(type, newProps)
: maySuspendCommitOnUpdate(type, oldProps, newProps);

if (!maySuspend) {
// If this flag was set previously, we can remove it. The flag
// represents whether this particular set of props might ever need to
// suspend. The safest thing to do is for maySuspendCommit to always
Expand All @@ -571,7 +578,7 @@ function preloadInstanceAndSuspendIfNeeded(
// preload the instance if necessary. Even if this is an urgent render there
// could be benefits to preloading early.
// @TODO we should probably do the preload in begin work
const isReady = preloadInstance(type, props);
const isReady = preloadInstance(type, newProps);
if (!isReady) {
if (shouldRemainOnPreviousScreen()) {
workInProgress.flags |= ShouldSuspendCommit;
Expand Down Expand Up @@ -1104,6 +1111,7 @@ function completeWork(
preloadInstanceAndSuspendIfNeeded(
workInProgress,
type,
null,
newProps,
renderLanes,
);
Expand Down Expand Up @@ -1137,10 +1145,10 @@ function completeWork(
return null;
}
} else {
const oldProps = current.memoizedProps;
// This is an Instance
// We may have props to update on the Hoistable instance.
if (supportsMutation) {
const oldProps = current.memoizedProps;
if (oldProps !== newProps) {
markUpdate(workInProgress);
}
Expand All @@ -1160,6 +1168,7 @@ function completeWork(
preloadInstanceAndSuspendIfNeeded(
workInProgress,
type,
oldProps,
newProps,
renderLanes,
);
Expand Down Expand Up @@ -1323,6 +1332,7 @@ function completeWork(
preloadInstanceAndSuspendIfNeeded(
workInProgress,
workInProgress.type,
current === null ? null : current.memoizedProps,
workInProgress.pendingProps,
renderLanes,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ describe('ReactFiberHostContext', () => {
maySuspendCommit(type, props) {
return false;
},
maySuspendCommitOnUpdate(type, oldProps, newProps) {
return false;
},
preloadInstance(type, props) {
return true;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export const shouldAttemptEagerTransition =
export const detachDeletedInstance = $$$config.detachDeletedInstance;
export const requestPostPaintCallback = $$$config.requestPostPaintCallback;
export const maySuspendCommit = $$$config.maySuspendCommit;
export const maySuspendCommitOnUpdate = $$$config.maySuspendCommitOnUpdate;
export const preloadInstance = $$$config.preloadInstance;
export const startSuspendingCommit = $$$config.startSuspendingCommit;
export const suspendInstance = $$$config.suspendInstance;
Expand Down
8 changes: 8 additions & 0 deletions packages/react-test-renderer/src/ReactFiberConfigTestHost.js
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,14 @@ export function maySuspendCommit(type: Type, props: Props): boolean {
return false;
}

export function maySuspendCommitOnUpdate(
type: Type,
oldProps: Props,
newProps: Props,
): boolean {
return false;
}

export function preloadInstance(type: Type, props: Props): boolean {
// Return true to indicate it's already loaded
return true;
Expand Down