Skip to content

Commit 661ba8e

Browse files
authored
fix: Prevent crash in RAC collections when used with Suspence/useTransition (#8835)
1 parent ecb05e2 commit 661ba8e

File tree

1 file changed

+26
-26
lines changed

1 file changed

+26
-26
lines changed

packages/@react-aria/collections/src/Document.ts

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -257,14 +257,14 @@ export class BaseNode<T> {
257257
*/
258258
export class ElementNode<T> extends BaseNode<T> {
259259
nodeType = 8; // COMMENT_NODE (we'd use ELEMENT_NODE but React DevTools will fail to get its dimensions)
260-
private _node: CollectionNode<T> | null;
260+
node: CollectionNode<T> | null;
261261
isMutated = true;
262262
private _index: number = 0;
263263
isHidden = false;
264264

265265
constructor(type: string, ownerDocument: Document<T, any>) {
266266
super(ownerDocument);
267-
this._node = null;
267+
this.node = null;
268268
}
269269

270270
get index(): number {
@@ -284,23 +284,15 @@ export class ElementNode<T> extends BaseNode<T> {
284284
return 0;
285285
}
286286

287-
get node(): CollectionNode<T> {
288-
if (this._node == null) {
289-
throw Error('Attempted to access node before it was defined. Check if setProps wasn\'t called before attempting to access the node.');
290-
}
291-
292-
return this._node;
293-
}
294-
295-
set node(node: CollectionNode<T>) {
296-
this._node = node;
297-
}
298-
299287
/**
300288
* Lazily gets a mutable instance of a Node. If the node has already
301289
* been cloned during this update cycle, it just returns the existing one.
302290
*/
303-
private getMutableNode(): Mutable<CollectionNode<T>> {
291+
private getMutableNode(): Mutable<CollectionNode<T>> | null {
292+
if (this.node == null) {
293+
return null;
294+
}
295+
304296
if (!this.isMutated) {
305297
this.node = this.node.clone();
306298
this.isMutated = true;
@@ -313,30 +305,34 @@ export class ElementNode<T> extends BaseNode<T> {
313305
updateNode(): void {
314306
let nextSibling = this.nextVisibleSibling;
315307
let node = this.getMutableNode();
308+
if (node == null) {
309+
return;
310+
}
311+
316312
node.index = this.index;
317313
node.level = this.level;
318-
node.parentKey = this.parentNode instanceof ElementNode ? this.parentNode.node.key : null;
319-
node.prevKey = this.previousVisibleSibling?.node.key ?? null;
320-
node.nextKey = nextSibling?.node.key ?? null;
314+
node.parentKey = this.parentNode instanceof ElementNode ? this.parentNode.node?.key ?? null : null;
315+
node.prevKey = this.previousVisibleSibling?.node?.key ?? null;
316+
node.nextKey = nextSibling?.node?.key ?? null;
321317
node.hasChildNodes = !!this.firstChild;
322-
node.firstChildKey = this.firstVisibleChild?.node.key ?? null;
323-
node.lastChildKey = this.lastVisibleChild?.node.key ?? null;
318+
node.firstChildKey = this.firstVisibleChild?.node?.key ?? null;
319+
node.lastChildKey = this.lastVisibleChild?.node?.key ?? null;
324320

325321
// Update the colIndex of sibling nodes if this node has a colSpan.
326322
if ((node.colSpan != null || node.colIndex != null) && nextSibling) {
327323
// This queues the next sibling for update, which means this happens recursively.
328324
let nextColIndex = (node.colIndex ?? node.index) + (node.colSpan ?? 1);
329-
if (nextColIndex !== nextSibling.node.colIndex) {
325+
if (nextSibling.node != null && nextColIndex !== nextSibling.node.colIndex) {
330326
let siblingNode = nextSibling.getMutableNode();
331-
siblingNode.colIndex = nextColIndex;
327+
siblingNode!.colIndex = nextColIndex;
332328
}
333329
}
334330
}
335331

336332
setProps<E extends Element>(obj: {[key: string]: any}, ref: ForwardedRef<E>, CollectionNodeClass: CollectionNodeClass<any>, rendered?: ReactNode, render?: (node: Node<T>) => ReactElement): void {
337333
let node;
338334
let {value, textValue, id, ...props} = obj;
339-
if (this._node == null) {
335+
if (this.node == null) {
340336
node = new CollectionNodeClass(id ?? `react-aria-${++this.ownerDocument.nodeId}`);
341337
this.node = node;
342338
} else {
@@ -450,7 +446,7 @@ export class Document<T, C extends BaseCollection<T> = BaseCollection<T>> extend
450446
}
451447

452448
private addNode(element: ElementNode<T>): void {
453-
if (element.isHidden) {
449+
if (element.isHidden || element.node == null) {
454450
return;
455451
}
456452

@@ -461,10 +457,14 @@ export class Document<T, C extends BaseCollection<T> = BaseCollection<T>> extend
461457
}
462458
}
463459

464-
collection.addNode(element.node!);
460+
collection.addNode(element.node);
465461
}
466462

467463
private removeNode(node: ElementNode<T>): void {
464+
if (node.node == null) {
465+
return;
466+
}
467+
468468
for (let child of node) {
469469
this.removeNode(child);
470470
}
@@ -516,7 +516,7 @@ export class Document<T, C extends BaseCollection<T> = BaseCollection<T>> extend
516516

517517
// Finally, update the collection.
518518
if (this.nextCollection) {
519-
this.nextCollection.commit(this.firstVisibleChild?.node.key ?? null, this.lastVisibleChild?.node.key ?? null, this.isSSR);
519+
this.nextCollection.commit(this.firstVisibleChild?.node?.key ?? null, this.lastVisibleChild?.node?.key ?? null, this.isSSR);
520520
if (!this.isSSR) {
521521
this.collection = this.nextCollection;
522522
this.nextCollection = null;

0 commit comments

Comments
 (0)