| 30 | } |
| 31 | |
| 32 | export function act<T>(callback: () => T | Thenable<T>): Thenable<T> { |
| 33 | if (__DEV__) { |
| 34 | class="cm">// When ReactSharedInternals.actQueue is not null, it signals to React that |
| 35 | class="cm">// we're currently inside an `act` scope. React will push all its tasks to |
| 36 | class="cm">// this queue instead of scheduling them with platform APIs. |
| 37 | class="cm">// |
| 38 | class="cm">// We set this to an empty array when we first enter an `act` scope, and |
| 39 | class="cm">// only unset it once we've left the outermost `act` scope — remember that |
| 40 | class="cm">// `act` calls can be nested. |
| 41 | class="cm">// |
| 42 | class="cm">// If we're already inside an `act` scope, reuse the existing queue. |
| 43 | const prevIsBatchingLegacy = !disableLegacyMode |
| 44 | ? ReactSharedInternals.isBatchingLegacy |
| 45 | : false; |
| 46 | const prevActQueue = ReactSharedInternals.actQueue; |
| 47 | const prevActScopeDepth = actScopeDepth; |
| 48 | actScopeDepth++; |
| 49 | const queue = (ReactSharedInternals.actQueue = |
| 50 | prevActQueue !== null ? prevActQueue : []); |
| 51 | class="cm">// Used to reproduce behavior of `batchedUpdates` in legacy mode. Only |
| 52 | class="cm">// set to `true` while the given callback is executed, not for updates |
| 53 | class="cm">// triggered during an async event, because this is how the legacy |
| 54 | class="cm">// implementation of `act` behaved. |
| 55 | if (!disableLegacyMode) { |
| 56 | ReactSharedInternals.isBatchingLegacy = true; |
| 57 | } |
| 58 | |
| 59 | let result; |
| 60 | class="cm">// This tracks whether the `act` call is awaited. In certain cases, not |
| 61 | class="cm">// awaiting it is a mistake, so we will detect that and warn. |
| 62 | let didAwaitActCall = false; |
| 63 | try { |
| 64 | class="cm">// Reset this to `false` right before entering the React work loop. The |
| 65 | class="cm">// only place we ever read this fields is just below, right after running |
| 66 | class="cm">// the callback. So we don't need to reset after the callback runs. |
| 67 | if (!disableLegacyMode) { |
| 68 | ReactSharedInternals.didScheduleLegacyUpdate = false; |
| 69 | } |
| 70 | result = callback(); |
| 71 | const didScheduleLegacyUpdate = !disableLegacyMode |
| 72 | ? ReactSharedInternals.didScheduleLegacyUpdate |
| 73 | : false; |
| 74 | |
| 75 | class="cm">// Replicate behavior of original `act` implementation in legacy mode, |
| 76 | class="cm">// which flushed updates immediately after the scope function exits, even |
| 77 | class="cm">// if it's an async function. |
| 78 | if (!prevIsBatchingLegacy && didScheduleLegacyUpdate) { |
| 79 | flushActQueue(queue); |
| 80 | } |
| 81 | class="cm">// `isBatchingLegacy` gets reset using the regular stack, not the async |
| 82 | class="cm">// one used to track `act` scopes. Why, you may be wondering? Because |
| 83 | class="cm">// thatclass="st">'s how it worked before version 18. Yes, it's confusing! We should |
| 84 | class="cm">// delete legacy mode!! |
| 85 | if (!disableLegacyMode) { |
| 86 | ReactSharedInternals.isBatchingLegacy = prevIsBatchingLegacy; |
| 87 | } |
| 88 | } catch (error) { |
| 89 | class="cm">// `isBatchingLegacy` gets reset using the regular stack, not the async |