Skip to content
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 @@ -3081,6 +3081,12 @@ function isReorderableExpression(
return true;
}
}
case 'TSInstantiationExpression': {
const innerExpr = (expr as NodePath<t.TSInstantiationExpression>).get(
'expression',
) as NodePath<t.Expression>;
return isReorderableExpression(builder, innerExpr, allowLocalIdentifiers);
}
case 'RegExpLiteral':
case 'StringLiteral':
case 'NumericLiteral':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1816,8 +1816,16 @@ function computeSignatureForInstruction(
}
case 'PropertyStore':
case 'ComputedStore': {
/**
* Add a hint about naming as "ref"/"-Ref", but only if we weren't able to infer any
* type for the object. In some cases the variable may be named like a ref, but is
* also used as a ref callback such that we infer the type as a function rather than
* a ref.
*/
const mutationReason: MutationReason | null =
value.kind === 'PropertyStore' && value.property === 'current'
value.kind === 'PropertyStore' &&
value.property === 'current' &&
value.object.identifier.type.kind === 'Type'
? {kind: 'AssignCurrentProperty'}
: null;
effects.push({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

## Input

```javascript
// @validateRefAccessDuringRender

function useHook(parentRef) {
// Some components accept a union of "callback" refs and ref objects, which
// we can't currently represent
const elementRef = useRef(null);
const handler = instance => {
elementRef.current = instance;
if (parentRef != null) {
if (typeof parentRef === 'function') {
// This call infers the type of `parentRef` as a function...
parentRef(instance);
} else {
// So this assignment fails since we don't know its a ref
parentRef.current = instance;
}
}
};
return handler;
}

```


## Error

```
Found 1 error:

Error: This value cannot be modified

Modifying component props or hook arguments is not allowed. Consider using a local variable instead.

error.todo-allow-assigning-to-inferred-ref-prop-in-callback.ts:15:8
13 | } else {
14 | // So this assignment fails since we don't know its a ref
> 15 | parentRef.current = instance;
| ^^^^^^^^^ `parentRef` cannot be modified
16 | }
17 | }
18 | };
```


Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// @validateRefAccessDuringRender

function useHook(parentRef) {
// Some components accept a union of "callback" refs and ref objects, which
// we can't currently represent
const elementRef = useRef(null);
const handler = instance => {
elementRef.current = instance;
if (parentRef != null) {
if (typeof parentRef === 'function') {
// This call infers the type of `parentRef` as a function...
parentRef(instance);
} else {
// So this assignment fails since we don't know its a ref
parentRef.current = instance;
}
}
};
return handler;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@

## Input

```javascript
function id<T>(x: T): T {
return x;
}

export function Component<T = string>({fn = id<T>}: {fn?: (x: T) => T}) {
const value = fn('hi' as T);
return <div>{String(value)}</div>;
}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};

```

## Code

```javascript
import { c as _c } from "react/compiler-runtime";
function id(x) {
return x;
}

export function Component(t0) {
const $ = _c(4);
const { fn: t1 } = t0;
const fn = t1 === undefined ? id : t1;
let t2;
if ($[0] !== fn) {
t2 = fn("hi" as T);
$[0] = fn;
$[1] = t2;
} else {
t2 = $[1];
}
const value = t2;
const t3 = String(value);
let t4;
if ($[2] !== t3) {
t4 = <div>{t3}</div>;
$[2] = t3;
$[3] = t4;
} else {
t4 = $[3];
}
return t4;
}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};

```

### Eval output
(kind: ok) <div>hi</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
function id<T>(x: T): T {
return x;
}

export function Component<T = string>({fn = id<T>}: {fn?: (x: T) => T}) {
const value = fn('hi' as T);
return <div>{String(value)}</div>;
}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
22 changes: 21 additions & 1 deletion packages/react-devtools-shared/src/backend/legacy/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import {
TREE_OPERATION_ADD,
TREE_OPERATION_REMOVE,
TREE_OPERATION_REORDER_CHILDREN,
SUSPENSE_TREE_OPERATION_ADD,
SUSPENSE_TREE_OPERATION_REMOVE,
UNKNOWN_SUSPENDERS_NONE,
} from '../../constants';
import {decorateMany, forceUpdate, restoreMany} from './utils';
Expand Down Expand Up @@ -411,6 +413,13 @@ export function attach(
pushOperation(0); // StrictMode supported?
pushOperation(hasOwnerMetadata ? 1 : 0);
pushOperation(supportsTogglingSuspense ? 1 : 0);

pushOperation(SUSPENSE_TREE_OPERATION_ADD);
pushOperation(id);
pushOperation(parentID);
pushOperation(getStringID(null)); // name
// TODO: Measure rect of root
pushOperation(-1);
} else {
const type = getElementType(internalInstance);
const {displayName, key} = getData(internalInstance);
Expand Down Expand Up @@ -449,7 +458,12 @@ export function attach(
}

function recordUnmount(internalInstance: InternalInstance, id: number) {
pendingUnmountedIDs.push(id);
const isRoot = parentIDStack.length === 0;
if (isRoot) {
pendingUnmountedRootID = id;
} else {
pendingUnmountedIDs.push(id);
}
idToInternalInstanceMap.delete(id);
}

Expand Down Expand Up @@ -519,6 +533,8 @@ export function attach(
// All unmounts are batched in a single message.
// [TREE_OPERATION_REMOVE, removedIDLength, ...ids]
(numUnmountIDs > 0 ? 2 + numUnmountIDs : 0) +
// [SUSPENSE_TREE_OPERATION_REMOVE, 1, pendingUnmountedRootID]
(pendingUnmountedRootID === null ? 0 : 3) +
// Mount operations
pendingOperations.length,
);
Expand Down Expand Up @@ -555,6 +571,10 @@ export function attach(
if (pendingUnmountedRootID !== null) {
operations[i] = pendingUnmountedRootID;
i++;

operations[i++] = SUSPENSE_TREE_OPERATION_REMOVE;
operations[i++] = 1;
operations[i++] = pendingUnmountedRootID;
}
}

Expand Down
6 changes: 6 additions & 0 deletions packages/react-devtools-shared/src/devtools/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any, ...} = {
'--color-scroll-track': '#fafafa',
'--color-tooltip-background': 'rgba(0, 0, 0, 0.9)',
'--color-tooltip-text': '#ffffff',

'--elevation-4':
'0 2px 4px -1px rgba(0,0,0,.2),0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12)',
},
dark: {
'--color-attribute-name': '#9d87d2',
Expand Down Expand Up @@ -315,6 +318,9 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any, ...} = {
'--color-scroll-track': '#313640',
'--color-tooltip-background': 'rgba(255, 255, 255, 0.95)',
'--color-tooltip-text': '#000000',

'--elevation-4':
'0 2px 8px 0 rgba(0,0,0,0.32),0 4px 12px 0 rgba(0,0,0,0.24),0 1px 10px 0 rgba(0,0,0,0.18)',
},
compact: {
'--font-size-monospace-small': '9px',
Expand Down
4 changes: 2 additions & 2 deletions packages/react-devtools-shared/src/devtools/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -895,11 +895,11 @@ export default class Store extends EventEmitter<{
if (root === null) {
return [];
}
if (!this.supportsTogglingSuspense(root.id)) {
if (!this.supportsTogglingSuspense(rootID)) {
return [];
}
const list: SuspenseNode['id'][] = [];
const suspense = this.getSuspenseByID(root.id);
const suspense = this.getSuspenseByID(rootID);
if (suspense !== null) {
const stack = [suspense];
while (stack.length > 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,40 @@
padding: .25rem;
}

.SuspenseRect {
fill: transparent;
stroke: var(--color-background-selected);
stroke-width: 1px;
vector-effect: non-scaling-stroke;
paint-order: stroke;
.SuspenseRectsViewBox {
position: relative;
}

[data-highlighted='true'] > .SuspenseRect {
fill: var(--color-selected-tree-highlight-active);
.SuspenseRectsBoundary {
pointer-events: all;
}

.SuspenseRectsBoundaryChildren {
pointer-events: none;
/**
* So that the shadow of Boundaries within is clipped off.
* Otherwise it would look like this boundary is further elevated.
*/
overflow: hidden;
}

.SuspenseRectsRect {
box-shadow: var(--elevation-4);
pointer-events: all;
outline-style: solid;
outline-width: 1px;
}

.SuspenseRectsScaledRect {
position: absolute;
outline-color: var(--color-background-selected);
}

/* highlight this boundary */
.SuspenseRectsBoundary:hover:not(:has(.SuspenseRectsBoundary:hover)) > .SuspenseRectsRect, .SuspenseRectsBoundary[data-highlighted='true'] > .SuspenseRectsRect {
background-color: var(--color-background-hover);
}

.SuspenseRectsRect[data-highlighted='true'] {
background-color: var(--color-selected-tree-highlight-active);
}
Loading
Loading