@@ -1115,4 +1115,148 @@ describe('ReactOffscreen', () => {
1115
1115
'Mount More 2' ,
1116
1116
] ) ;
1117
1117
} ) ;
1118
+
1119
+ // @gate enableOffscreen
1120
+ it ( 'does not mount effects when prerendering a nested Offscreen boundary' , async ( ) => {
1121
+ function Child ( { label} ) {
1122
+ useEffect ( ( ) => {
1123
+ Scheduler . unstable_yieldValue ( 'Mount ' + label ) ;
1124
+ return ( ) => {
1125
+ Scheduler . unstable_yieldValue ( 'Unmount ' + label ) ;
1126
+ } ;
1127
+ } , [ label ] ) ;
1128
+ return < Text text = { label } /> ;
1129
+ }
1130
+
1131
+ function App ( { showOuter, showInner} ) {
1132
+ return (
1133
+ < Offscreen mode = { showOuter ? 'visible' : 'hidden' } >
1134
+ { useMemo (
1135
+ ( ) => (
1136
+ < div >
1137
+ < Child label = "Outer" />
1138
+ { showInner ? (
1139
+ < Offscreen mode = "visible" >
1140
+ < div >
1141
+ < Child label = "Inner" />
1142
+ </ div >
1143
+ </ Offscreen >
1144
+ ) : null }
1145
+ </ div >
1146
+ ) ,
1147
+ [ showInner ] ,
1148
+ ) }
1149
+ </ Offscreen >
1150
+ ) ;
1151
+ }
1152
+
1153
+ const root = ReactNoop . createRoot ( ) ;
1154
+
1155
+ // Prerender the outer contents. No effects should mount.
1156
+ await act ( async ( ) => {
1157
+ root . render ( < App showOuter = { false } showInner = { false } /> ) ;
1158
+ } ) ;
1159
+ expect ( Scheduler ) . toHaveYielded ( [ 'Outer' ] ) ;
1160
+ expect ( root ) . toMatchRenderedOutput (
1161
+ < div hidden = { true } >
1162
+ < span prop = "Outer" />
1163
+ </ div > ,
1164
+ ) ;
1165
+
1166
+ // Prerender the inner contents. No effects should mount.
1167
+ await act ( async ( ) => {
1168
+ root . render ( < App showOuter = { false } showInner = { true } /> ) ;
1169
+ } ) ;
1170
+ expect ( Scheduler ) . toHaveYielded ( [ 'Outer' , 'Inner' ] ) ;
1171
+ expect ( root ) . toMatchRenderedOutput (
1172
+ < div hidden = { true } >
1173
+ < span prop = "Outer" />
1174
+ < div >
1175
+ < span prop = "Inner" />
1176
+ </ div >
1177
+ </ div > ,
1178
+ ) ;
1179
+
1180
+ // Reveal the prerendered tree
1181
+ await act ( async ( ) => {
1182
+ root . render ( < App showOuter = { true } showInner = { true } /> ) ;
1183
+ } ) ;
1184
+ // The effects fire, but the tree is not re-rendered because it already
1185
+ // prerendered.
1186
+ expect ( Scheduler ) . toHaveYielded ( [ 'Mount Outer' , 'Mount Inner' ] ) ;
1187
+ expect ( root ) . toMatchRenderedOutput (
1188
+ < div >
1189
+ < span prop = "Outer" />
1190
+ < div >
1191
+ < span prop = "Inner" />
1192
+ </ div >
1193
+ </ div > ,
1194
+ ) ;
1195
+ } ) ;
1196
+
1197
+ // @gate enableOffscreen
1198
+ it ( 'reveal an outer Offscreen boundary without revealing an inner one' , async ( ) => {
1199
+ function Child ( { label} ) {
1200
+ useEffect ( ( ) => {
1201
+ Scheduler . unstable_yieldValue ( 'Mount ' + label ) ;
1202
+ return ( ) => {
1203
+ Scheduler . unstable_yieldValue ( 'Unmount ' + label ) ;
1204
+ } ;
1205
+ } , [ label ] ) ;
1206
+ return < Text text = { label } /> ;
1207
+ }
1208
+
1209
+ function App ( { showOuter, showInner} ) {
1210
+ return (
1211
+ < Offscreen mode = { showOuter ? 'visible' : 'hidden' } >
1212
+ { useMemo (
1213
+ ( ) => (
1214
+ < div >
1215
+ < Child label = "Outer" />
1216
+ < Offscreen mode = { showInner ? 'visible' : 'hidden' } >
1217
+ < div >
1218
+ < Child label = "Inner" />
1219
+ </ div >
1220
+ </ Offscreen >
1221
+ </ div >
1222
+ ) ,
1223
+ [ showInner ] ,
1224
+ ) }
1225
+ </ Offscreen >
1226
+ ) ;
1227
+ }
1228
+
1229
+ const root = ReactNoop . createRoot ( ) ;
1230
+
1231
+ // Prerender the whole tree.
1232
+ await act ( async ( ) => {
1233
+ root . render ( < App showOuter = { false } showInner = { false } /> ) ;
1234
+ } ) ;
1235
+ expect ( Scheduler ) . toHaveYielded ( [ 'Outer' , 'Inner' ] ) ;
1236
+ // Both the inner and the outer tree should be hidden. Hiding the inner tree
1237
+ // is arguably redundant, but the advantage of hiding both is that later you
1238
+ // can reveal the outer tree without having to examine the inner one.
1239
+ expect ( root ) . toMatchRenderedOutput (
1240
+ < div hidden = { true } >
1241
+ < span prop = "Outer" />
1242
+ < div hidden = { true } >
1243
+ < span prop = "Inner" />
1244
+ </ div >
1245
+ </ div > ,
1246
+ ) ;
1247
+
1248
+ // Reveal the outer contents. The inner tree remains hidden.
1249
+ await act ( async ( ) => {
1250
+ root . render ( < App showOuter = { true } showInner = { false } /> ) ;
1251
+ } ) ;
1252
+ expect ( Scheduler ) . toHaveYielded ( [ 'Mount Outer' ] ) ;
1253
+ expect ( root ) . toMatchRenderedOutput (
1254
+ < div >
1255
+ < span prop = "Outer" />
1256
+ < div hidden = { true } >
1257
+ < span prop = "Inner" />
1258
+ </ div >
1259
+ </ div > ,
1260
+ ) ;
1261
+ } ) ;
1118
1262
} ) ;
0 commit comments