Skip to content

[refactor] Add element type for Activity #32499

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,172 @@ describe('Store component filters', () => {
});

// @reactVersion >= 16.0
it('should filter Suspense', async () => {
const Suspense = React.Suspense;
await actAsync(async () =>
render(
<React.Fragment>
<Suspense>
<div>Visible</div>
</Suspense>
<Suspense>
<div>Hidden</div>
</Suspense>
</React.Fragment>,
),
);

expect(store).toMatchInlineSnapshot(`
[root]
▾ <Suspense>
<div>
▾ <Suspense>
<div>
`);

await actAsync(
async () =>
(store.componentFilters = [
utils.createElementTypeFilter(Types.ElementTypeActivity),
]),
);

expect(store).toMatchInlineSnapshot(`
[root]
▾ <Suspense>
<div>
▾ <Suspense>
<div>
`);

await actAsync(
async () =>
(store.componentFilters = [
utils.createElementTypeFilter(Types.ElementTypeActivity, false),
]),
);

expect(store).toMatchInlineSnapshot(`
[root]
▾ <Suspense>
<div>
▾ <Suspense>
<div>
`);
});

it('should filter Activity', async () => {
const Activity = React.unstable_Activity;

if (Activity != null) {
await actAsync(async () =>
render(
<React.Fragment>
<Activity mode="visible">
<div>Visible</div>
</Activity>
<Activity mode="hidden">
<div>Hidden</div>
</Activity>
</React.Fragment>,
),
);

expect(store).toMatchInlineSnapshot(`
[root]
▾ <Activity>
<div>
▾ <Activity>
<div>
`);

await actAsync(
async () =>
(store.componentFilters = [
utils.createElementTypeFilter(Types.ElementTypeActivity),
]),
);

expect(store).toMatchInlineSnapshot(`
[root]
<div>
<div>
`);

await actAsync(
async () =>
(store.componentFilters = [
utils.createElementTypeFilter(Types.ElementTypeActivity, false),
]),
);

expect(store).toMatchInlineSnapshot(`
[root]
▾ <Activity>
<div>
▾ <Activity>
<div>
`);
}
});

it('should filter ViewTransition', async () => {
const ViewTransition = React.unstable_ViewTransition;

if (ViewTransition != null) {
await actAsync(async () =>
render(
<React.Fragment>
<ViewTransition>
<div>Visible</div>
</ViewTransition>
<ViewTransition>
<div>Hidden</div>
</ViewTransition>
</React.Fragment>,
),
);

expect(store).toMatchInlineSnapshot(`
[root]
▾ <ViewTransition>
<div>
▾ <ViewTransition>
<div>
`);

await actAsync(
async () =>
(store.componentFilters = [
utils.createElementTypeFilter(Types.ElementTypeActivity),
]),
);

expect(store).toMatchInlineSnapshot(`
[root]
▾ <ViewTransition>
<div>
▾ <ViewTransition>
<div>
`);

await actAsync(
async () =>
(store.componentFilters = [
utils.createElementTypeFilter(Types.ElementTypeActivity, false),
]),
);

expect(store).toMatchInlineSnapshot(`
[root]
▾ <ViewTransition>
<div>
▾ <ViewTransition>
<div>
`);
}
});

it('should ignore invalid ElementTypeRoot filter', async () => {
const Component = () => <div>Hi</div>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export function describeFiber(
ForwardRef,
ClassComponent,
ViewTransitionComponent,
ActivityComponent,
} = workTagMap;

switch (workInProgress.tag) {
Expand All @@ -60,6 +61,8 @@ export function describeFiber(
return describeBuiltInComponentFrame('SuspenseList');
case ViewTransitionComponent:
return describeBuiltInComponentFrame('ViewTransition');
case ActivityComponent:
return describeBuiltInComponentFrame('Activity');
case FunctionComponent:
case IndeterminateComponent:
case SimpleMemoComponent:
Expand Down Expand Up @@ -154,6 +157,7 @@ export function getOwnerStackByFiberInDev(
SuspenseComponent,
SuspenseListComponent,
ViewTransitionComponent,
ActivityComponent,
} = workTagMap;
try {
let info = '';
Expand Down Expand Up @@ -184,6 +188,9 @@ export function getOwnerStackByFiberInDev(
case ViewTransitionComponent:
info += describeBuiltInComponentFrame('ViewTransition');
break;
case ActivityComponent:
info += describeBuiltInComponentFrame('Activity');
break;
}

let owner: void | null | Fiber | ReactComponentInfo = workInProgress;
Expand Down
12 changes: 12 additions & 0 deletions packages/react-devtools-shared/src/backend/fiber/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
ElementTypeSuspenseList,
ElementTypeTracingMarker,
ElementTypeViewTransition,
ElementTypeActivity,
ElementTypeVirtual,
StrictMode,
} from 'react-devtools-shared/src/frontend/types';
Expand Down Expand Up @@ -385,6 +386,7 @@ export function getInternalReactConstants(version: string): {
YieldComponent: -1, // Removed
Throw: 29,
ViewTransitionComponent: 30, // Experimental
ActivityComponent: 31,
};
} else if (gte(version, '17.0.0-alpha')) {
ReactTypeOfWork = {
Expand Down Expand Up @@ -421,6 +423,7 @@ export function getInternalReactConstants(version: string): {
YieldComponent: -1, // Removed
Throw: -1, // Doesn't exist yet
ViewTransitionComponent: -1, // Doesn't exist yet
ActivityComponent: -1, // Doesn't exist yet
};
} else if (gte(version, '16.6.0-beta.0')) {
ReactTypeOfWork = {
Expand Down Expand Up @@ -457,6 +460,7 @@ export function getInternalReactConstants(version: string): {
YieldComponent: -1, // Removed
Throw: -1, // Doesn't exist yet
ViewTransitionComponent: -1, // Doesn't exist yet
ActivityComponent: -1, // Doesn't exist yet
};
} else if (gte(version, '16.4.3-alpha')) {
ReactTypeOfWork = {
Expand Down Expand Up @@ -493,6 +497,7 @@ export function getInternalReactConstants(version: string): {
YieldComponent: -1, // Removed
Throw: -1, // Doesn't exist yet
ViewTransitionComponent: -1, // Doesn't exist yet
ActivityComponent: -1, // Doesn't exist yet
};
} else {
ReactTypeOfWork = {
Expand Down Expand Up @@ -529,6 +534,7 @@ export function getInternalReactConstants(version: string): {
YieldComponent: 9,
Throw: -1, // Doesn't exist yet
ViewTransitionComponent: -1, // Doesn't exist yet
ActivityComponent: -1, // Doesn't exist yet
};
}
// **********************************************************
Expand Down Expand Up @@ -572,6 +578,7 @@ export function getInternalReactConstants(version: string): {
TracingMarkerComponent,
Throw,
ViewTransitionComponent,
ActivityComponent,
} = ReactTypeOfWork;

function resolveFiberType(type: any): $FlowFixMe {
Expand Down Expand Up @@ -622,6 +629,8 @@ export function getInternalReactConstants(version: string): {
}

switch (tag) {
case ActivityComponent:
return 'Activity';
case CacheComponent:
return 'Cache';
case ClassComponent:
Expand Down Expand Up @@ -892,6 +901,7 @@ export function attach(
StrictModeBits,
} = getInternalReactConstants(version);
const {
ActivityComponent,
CacheComponent,
ClassComponent,
ContextConsumer,
Expand Down Expand Up @@ -1565,6 +1575,8 @@ export function attach(
const {type, tag} = fiber;

switch (tag) {
case ActivityComponent:
return ElementTypeActivity;
case ClassComponent:
case IncompleteClassComponent:
return ElementTypeClass;
Expand Down
1 change: 1 addition & 0 deletions packages/react-devtools-shared/src/backend/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export type WorkTagMap = {
YieldComponent: WorkTag,
Throw: WorkTag,
ViewTransitionComponent: WorkTag,
ActivityComponent: WorkTag,
};

export type HostInstance = Object;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,8 @@ export default function ComponentsSettings({
((parseInt(currentTarget.value, 10): any): ElementType),
)
}>
{/* TODO: currently only experimental, only list this if it's available */}
{/*<option value={ElementTypeActivity}>activity</option>*/}
<option value={ElementTypeClass}>class</option>
<option value={ElementTypeContext}>context</option>
<option value={ElementTypeFunction}>function</option>
Expand All @@ -485,6 +487,10 @@ export default function ComponentsSettings({
<option value={ElementTypeOtherOrUnknown}>other</option>
<option value={ElementTypeProfiler}>profiler</option>
<option value={ElementTypeSuspense}>suspense</option>
{/* TODO: currently only experimental, only list this if it's available */}
{/*<option value={ElementTypeViewTransition}>*/}
{/* view transition*/}
{/*</option>*/}
</select>
)}
{(componentFilter.type === ComponentFilterLocation ||
Expand Down
4 changes: 3 additions & 1 deletion packages/react-devtools-shared/src/frontend/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const ElementTypeSuspenseList = 13;
export const ElementTypeTracingMarker = 14;
export const ElementTypeVirtual = 15;
export const ElementTypeViewTransition = 16;
export const ElementTypeActivity = 17;

// Different types of elements displayed in the Elements tree.
// These types may be used to visually distinguish types,
Expand All @@ -68,7 +69,8 @@ export type ElementType =
| 13
| 14
| 15
| 16;
| 16
| 17;

// WARNING
// The values below are referenced by ComponentFilters (which are saved via localStorage).
Expand Down
15 changes: 15 additions & 0 deletions packages/react-reconciler/src/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import {
TracingMarkerComponent,
Throw,
ViewTransitionComponent,
ActivityComponent,
} from './ReactWorkTags';
import {OffscreenVisible} from './ReactFiberActivityComponent';
import {getComponentNameFromOwner} from 'react-reconciler/src/getComponentNameFromFiber';
Expand Down Expand Up @@ -107,6 +108,7 @@ import {
REACT_TRACING_MARKER_TYPE,
REACT_ELEMENT_TYPE,
REACT_VIEW_TRANSITION_TYPE,
REACT_ACTIVITY_TYPE,
} from 'shared/ReactSymbols';
import {TransitionTracingMarker} from './ReactFiberTracingMarkerComponent';
import {
Expand Down Expand Up @@ -588,6 +590,8 @@ export function createFiberFromTypeAndProps(
}
} else {
getTag: switch (type) {
case REACT_ACTIVITY_TYPE:
return createFiberFromActivity(pendingProps, mode, lanes, key);
case REACT_FRAGMENT_TYPE:
return createFiberFromFragment(pendingProps.children, mode, lanes, key);
case REACT_STRICT_MODE_TYPE:
Expand Down Expand Up @@ -865,6 +869,17 @@ export function createFiberFromOffscreen(
fiber.stateNode = primaryChildInstance;
return fiber;
}
export function createFiberFromActivity(
pendingProps: OffscreenProps,
mode: TypeOfMode,
lanes: Lanes,
key: null | string,
): Fiber {
const fiber = createFiber(ActivityComponent, pendingProps, key, mode);
fiber.elementType = REACT_ACTIVITY_TYPE;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not actually right. This was wrong for Offscreen too.

In the case of const LazyActivity = React.lazy(async () => ({ default: React.Activity })); <LazyActivity /> the elementType should be the Lazy wrapper object. Otherwise reconciliation won't work correctly.

In fact, it looks like we get this wrong for all these built-ins so wrapping them would yield the wrong reconciliation.

So it's an existing bug that we need to fix separately.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this wrong for REACT_VIEW_TRANSITION_TYPE too?

What's the fix?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, we throw for this case already with an error that says:

Caution

Element type is invalid. Received a promise that resolves to: ViewTransition. Lazy element type must resolve to a class or function.

This is broken for portal, so I fixed it and tested all the built-ins here: #32640

fiber.lanes = lanes;
return fiber;
}

export function createFiberFromViewTransition(
pendingProps: ViewTransitionProps,
Expand Down
Loading
Loading