@samitouri / QOS-React / commits / ee7f9c9351

useId: Remove unnecessary try/finally blocks (#27340)

To generate IDs for useId, we modify a context variable whenever multiple siblings are rendered, or when a component includes a useId hook. When this happens, we must ensure that the context is reset properly on unwind if something errors or suspends. When I originally implemented this, I did this by wrapping the child's rendering with a try/finally block. But a better way to do this is by using the non-destructive renderNode path instead of renderNodeDestructive.

Andrew Clark committed Sep 6, 2023 at 16:30 UTC ee7f9c9351f8902e07ceacf4234ef75e7e4ecd73
1 file changed +28 -22
packages/react-server/src/ReactFizzServer.js
+28 -22
@@ -1021,12 +1021,13 @@ function renderIndeterminateComponent(
1021 const prevTreeContext = task.treeContext;
1022 const totalChildren = 1;
1023 const index = 0;
1024 + // Modify the id context. Because we'll need to reset this if something
1025 + // suspends or errors, we'll use the non-destructive render path.
1026 task.treeContext = pushTreeContext(prevTreeContext, totalChildren, index);
1025 - try {
1026 - renderNodeDestructive(request, task, null, value, 0);
1027 - } finally {
1028 - task.treeContext = prevTreeContext;
1029 - }
1027 + renderNode(request, task, value, 0);
1028 + // Like the other contexts, this does not need to be in a finally block
1029 + // because renderNode takes care of unwinding the stack.
1030 + task.treeContext = prevTreeContext;
1031 } else {
1032 renderNodeDestructive(request, task, null, value, 0);
1033 }
@@ -1126,12 +1127,12 @@ function renderForwardRef(
1127 const prevTreeContext = task.treeContext;
1128 const totalChildren = 1;
1129 const index = 0;
1130 + // Modify the id context. Because we'll need to reset this if something
1131 + // suspends or errors, we'll use the non-destructive render path.
1132 task.treeContext = pushTreeContext(prevTreeContext, totalChildren, index);
1130 - try {
1131 - renderNodeDestructive(request, task, null, children, 0);
1132 - } finally {
1133 - task.treeContext = prevTreeContext;
1134 - }
1133 + renderNode(request, task, children, 0);
1134 + // Like the other contexts, this does not need to be in a finally block
1135 + // because renderNode takes care of unwinding the stack.
1136 } else {
1137 renderNodeDestructive(request, task, null, children, 0);
1138 }
@@ -1656,26 +1657,27 @@ function renderChildrenArray(
1657 children: Array<any>,
1658 childIndex: number,
1659 ) {
1659 - const prevKeyPath = task.keyPath;
1660 + const prevTreeContext = task.treeContext;
1661 const totalChildren = children.length;
1662 for (let i = 0; i < totalChildren; i++) {
1662 - const prevTreeContext = task.treeContext;
1663 + const node = children[i];
1664 task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i);
1664 - try {
1665 - const node = children[i];
1666 - if (isArray(node) || getIteratorFn(node)) {
1667 - // Nested arrays behave like a "fragment node" which is keyed.
1668 - // Therefore we need to add the current index as a parent key.
1669 - task.keyPath = [task.keyPath, '', childIndex];
1670 - }
1665 + if (isArray(node) || getIteratorFn(node)) {
1666 + // Nested arrays behave like a "fragment node" which is keyed.
1667 + // Therefore we need to add the current index as a parent key.
1668 + const prevKeyPath = task.keyPath;
1669 + task.keyPath = [task.keyPath, '', childIndex];
1670 + renderNode(request, task, node, i);
1671 + task.keyPath = prevKeyPath;
1672 + } else {
1673 // We need to use the non-destructive form so that we can safely pop back
1674 // up and render the sibling if something suspends.
1675 renderNode(request, task, node, i);
1674 - } finally {
1675 - task.treeContext = prevTreeContext;
1676 - task.keyPath = prevKeyPath;
1676 }
1677 }
1678 + // Because this context is always set right before rendering every child, we
1679 + // only need to reset it to the previous value at the very end.
1680 + task.treeContext = prevTreeContext;
1681 }
1682
1683 function trackPostpone(
@@ -1824,6 +1826,7 @@ function renderNode(
1826 const previousLegacyContext = task.legacyContext;
1827 const previousContext = task.context;
1828 const previousKeyPath = task.keyPath;
1829 + const previousTreeContext = task.treeContext;
1830 let previousComponentStack = null;
1831 if (__DEV__) {
1832 previousComponentStack = task.componentStack;
@@ -1860,6 +1863,7 @@ function renderNode(
1863 task.legacyContext = previousLegacyContext;
1864 task.context = previousContext;
1865 task.keyPath = previousKeyPath;
1866 + task.treeContext = previousTreeContext;
1867 // Restore all active ReactContexts to what they were before.
1868 switchContext(previousContext);
1869 if (__DEV__) {
@@ -1892,6 +1896,7 @@ function renderNode(
1896 task.legacyContext = previousLegacyContext;
1897 task.context = previousContext;
1898 task.keyPath = previousKeyPath;
1899 + task.treeContext = previousTreeContext;
1900 // Restore all active ReactContexts to what they were before.
1901 switchContext(previousContext);
1902 if (__DEV__) {
@@ -1906,6 +1911,7 @@ function renderNode(
1911 task.legacyContext = previousLegacyContext;
1912 task.context = previousContext;
1913 task.keyPath = previousKeyPath;
1914 + task.treeContext = previousTreeContext;
1915 // Restore all active ReactContexts to what they were before.
1916 switchContext(previousContext);
1917 if (__DEV__) {