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__) {