Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

useMemoCache implementation #25143

Merged
merged 12 commits into from
Sep 13, 2022
Merged

Conversation

josephsavona
Copy link
Contributor

@josephsavona josephsavona commented Aug 25, 2022

Summary

context: I'm still a noob to this codebase so this started as a hacky implementation to get feedback.

Initial implementation of useMemoCache that aims to at least get the basics correct, even if it doesn't handle all cases and/or there are better ways to implement it (feedback please!). The approach adds a new memoCache: MemoCache | null property on Fiber. MemoCache contains a current version of the cache and an optional previous version: the previous version is used to restore in the case of a setState during render and/or an error during render. In both cases the cache could become invalid (only partially updated), so restoring in these cases ensures the cache is always in a consistent state. To avoid corrupting the "previous" cache, it is cloned prior to rendering.

I wasn't sure if it would be safe to use the alternate fiber's memoCache as the previous instance — both the main and alternate fiber could be rendered concurrently (right?) in which case they could fail and need to restore independently. Let me know if this assumption is incorrect.

How did you test this change?

New unit test.

@sizebot
Copy link

sizebot commented Aug 25, 2022

Comparing: 0556bab...32cd35e

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.min.js = 134.98 kB 134.99 kB +0.04% 43.24 kB 43.25 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js +0.26% 141.70 kB 142.07 kB +0.29% 45.23 kB 45.36 kB
facebook-www/ReactDOM-prod.classic.js +0.23% 487.79 kB 488.89 kB +0.23% 86.82 kB 87.02 kB
facebook-www/ReactDOM-prod.modern.js +0.23% 473.10 kB 474.20 kB +0.24% 84.58 kB 84.78 kB
facebook-www/ReactDOMForked-prod.classic.js +0.23% 487.79 kB 488.89 kB +0.23% 86.82 kB 87.02 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-experimental/react-art/cjs/react-art.production.min.js +0.41% 90.60 kB 90.97 kB +0.45% 27.93 kB 28.06 kB
oss-experimental/react-reconciler/cjs/react-reconciler.production.min.js +0.36% 102.23 kB 102.60 kB +0.43% 31.29 kB 31.43 kB
facebook-www/ReactART-prod.modern.js +0.35% 314.25 kB 315.35 kB +0.39% 53.74 kB 53.95 kB
facebook-www/ReactART-prod.classic.js +0.34% 325.02 kB 326.12 kB +0.37% 55.55 kB 55.76 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.min.js +0.33% 111.08 kB 111.45 kB +0.37% 33.42 kB 33.54 kB
oss-experimental/react-art/umd/react-art.production.min.js +0.29% 126.45 kB 126.82 kB +0.23% 39.26 kB 39.35 kB
oss-experimental/react-art/cjs/react-art.development.js +0.26% 745.27 kB 747.22 kB +0.33% 160.83 kB 161.36 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js +0.26% 141.70 kB 142.07 kB +0.29% 45.23 kB 45.36 kB
oss-experimental/react-dom/umd/react-dom.production.min.js +0.26% 141.76 kB 142.12 kB +0.30% 45.90 kB 46.04 kB
oss-experimental/react-dom/umd/react-dom.profiling.min.js +0.24% 150.56 kB 150.93 kB +0.27% 48.16 kB 48.29 kB
oss-experimental/react-dom/cjs/react-dom.profiling.min.js +0.24% 151.18 kB 151.55 kB +0.25% 47.62 kB 47.73 kB
oss-experimental/react-art/umd/react-art.development.js +0.24% 851.58 kB 853.63 kB +0.30% 178.95 kB 179.48 kB
oss-experimental/react-reconciler/cjs/react-reconciler.development.js +0.24% 812.99 kB 814.94 kB +0.31% 173.07 kB 173.61 kB
facebook-www/ReactART-dev.modern.js +0.24% 837.98 kB 839.97 kB +0.28% 177.15 kB 177.65 kB
facebook-www/ReactART-dev.classic.js +0.23% 848.20 kB 850.19 kB +0.28% 179.22 kB 179.73 kB
facebook-www/ReactDOM-prod.modern.js +0.23% 473.10 kB 474.20 kB +0.24% 84.58 kB 84.78 kB
facebook-www/ReactDOMForked-prod.modern.js +0.23% 473.10 kB 474.20 kB +0.24% 84.58 kB 84.78 kB
facebook-www/ReactDOM-prod.classic.js +0.23% 487.79 kB 488.89 kB +0.23% 86.82 kB 87.02 kB
facebook-www/ReactDOMForked-prod.classic.js +0.23% 487.79 kB 488.89 kB +0.23% 86.82 kB 87.02 kB
facebook-www/ReactDOM-profiling.modern.js +0.22% 502.91 kB 504.01 kB +0.22% 89.09 kB 89.28 kB
facebook-www/ReactDOMForked-profiling.modern.js +0.22% 502.91 kB 504.01 kB +0.22% 89.09 kB 89.28 kB
facebook-www/ReactDOM-profiling.classic.js +0.21% 517.67 kB 518.77 kB +0.22% 91.45 kB 91.64 kB
facebook-www/ReactDOMForked-profiling.classic.js +0.21% 517.67 kB 518.77 kB +0.22% 91.45 kB 91.65 kB

Generated by 🚫 dangerJS against 32cd35e

@josephsavona
Copy link
Contributor Author

Chatted with @acdlite, i had misunderstood and thought both versions of the fiber tree could be updated at the same time. Since that can't happen we don't need the indirection for current/previous, i'll remove and add handling for multiple useMemoCache calls.

Copy link
Member

@poteto poteto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will we still be adding a dev-only guard for read before writes? Or is that out of scope for the official impl?

test('render component using cache', async () => {
function Component(props) {
const cache = useMemoCache(1);
expect(cache).toBeInstanceOf(Array);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor nit: Array.isArray(cache) might be more accurate?

Comment on lines 847 to 858
console.warn(
'Expected each useMemoCache to receive a consistent size argument, found different sizes',
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this error instead of warn? If this ever happens it seems like something might have gone horribly wrong

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, fair enough that this should basically never happen with correctly compiled code so we might as well error.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also don’t use warn() for anything except deprecated methods

@@ -579,7 +611,7 @@ export function bailoutHooks(
current.lanes = removeLanes(current.lanes, lanes);
}

export function resetHooksAfterThrow(): void {
export function resetHooksAfterThrow(erroredWork: Fiber | null): void {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't erroredWork the same as currentlyRenderingFiber at this point which is also used elsewhere in this reset function. for consistency maybe just use the module global?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, let me try that. It's hard to tell when i should use the global and when not to (lots of functions explicitly pass around a Fiber, including some of the callers of this function)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I confirmed that currentlyRenderingFiber can be null here. @acdlite is there some global for "current fiber" that I can rely on here? Or is the approach of passing in the fiber from the caller ok?

packages/react-reconciler/src/ReactFiberHooks.new.js Outdated Show resolved Hide resolved
@kassens
Copy link
Member

kassens commented Aug 31, 2022

@poteto I actually haven't seen that fire since we ironed out the first bigger issues. We can keep patching it in for our in progress debugging, but I don't see it as a risk later. (These are fairly bad compiler bugs if they happen)

let previousMemoCache = null;
if (memoCache !== null) {
previousMemoCache = memoCache.data.map(array => array.slice());
if (workInProgress.alternate != null) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Use the current variable. We try to minimize the number of references to .alternate so it's easier to refactor later. Also it happens to minify slightly better if we minimize property access.

if (memoCache !== null) {
// Setting state during render could leave the cache in an inconsistent state,
// reset to the previous state before re-rendering.
if (previousMemoCache !== null) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this comment about setState during render. If we're cloning the current cache, copy-on-write style, we shouldn't have to do anything on unwind. The interrupted cache will get dropped.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT workInProgress isn't fully reset when we retry rendering after a setState: the only thing that is reset is what is explicitly done here in this function. So at the beginning we set wIP.memoCache to a clone of the previous value. The render will then mutate that fresh clone, and if there is a setstate that clone will be corrupt. We have to explicitly reset it to a fresh clone of the original value.

Copy link
Collaborator

@acdlite acdlite Sep 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that's true, that suggests there's a bug somewhere because we access it here as if it's non-null:

let hook: Hook | null = currentlyRenderingFiber.memoizedState;

Are you sure it was null at the beginning of this function and not the end? The purpose of resetHooksAfterError is to reset all the module-level variables, including currentlyRenderingFiber.

Could you show what scenario you found where it was null at the beginning of this function?

Aside from confirming there's no existing bug, though, passing it from the caller is also fine

previousMemoCache = memoCache.data.map(array => array.slice());
if (workInProgress.alternate != null) {
// Backup to the alternate fiber in case the component throws
workInProgress.alternate.memoCache = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need to mutate the current fiber at all. My understanding is we're starting with a copy-on-write approach, same as we do for fibers and hooks: inside useMemoCache, copy the cache from the previous current and put it on the work-in-progress fiber. If something errors, or the render is interrupted, the work-in-progress fiber will be dropped. So you don't need to do any resetting of the current cache or fiber.

@@ -851,6 +853,7 @@ export function assignFiberPropertiesInDEV(
target.pendingProps = source.pendingProps;
target.memoizedProps = source.memoizedProps;
target.updateQueue = source.updateQueue;
target.memoCache = source.memoCache;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this field is only relevant to functional components, I suggest putting this field on a different object that only exists for hooks.

I think the best place is the FunctionalComponentUpdateQueue object. The existing fields on that object are conceptually similar because they, too, are copy-on-write. For example, this is how we track usages of useSyncExternalStore:

if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
componentUpdateQueue.stores = [check];
} else {
const stores = componentUpdateQueue.stores;
if (stores === null) {
componentUpdateQueue.stores = [check];
} else {
stores.push(check);
}
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible we may eventually want to move this to a fiber-level field if we end up using the Forget cache for non-user components, too, but since that's only theoretical for now I think it's best to move it to reduce the memory impact.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You had suggested that but the updateQueue property is set to null prior to calling the render function, so it isn't clear to me how we'd make the cache value available to useMemoCache(). Do you mean to add a new global that we set/unset as we enter/exit each component?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can read the previous values from the current fiber: current.updateQueue

@acdlite
Copy link
Collaborator

acdlite commented Aug 31, 2022

Make sure you wrap all the code in the feature flag. To confirm, oss-stable/react-dom/cjs/react-dom.production.min.js should not increase in size (click on the filename in the Sizebot comment to see a diff view).

Copy link
Contributor Author

@josephsavona josephsavona left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks all for the comments! Outstanding items that i'm not sure how to address:

  • @acdlite See questions about how to store memoCache on the updateQueue, given that the latter is nulled our prior to rendering
  • @acdlite During error recovery i'm not sure of the best way to access the fiber that was rendered and failed, see comments
  • @gaearon Is throwing reasonable instead of console.warn? Or should I just console.error?

Thanks in advance!

packages/react-reconciler/src/ReactFiberHooks.new.js Outdated Show resolved Hide resolved
@@ -579,7 +611,7 @@ export function bailoutHooks(
current.lanes = removeLanes(current.lanes, lanes);
}

export function resetHooksAfterThrow(): void {
export function resetHooksAfterThrow(erroredWork: Fiber | null): void {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, let me try that. It's hard to tell when i should use the global and when not to (lots of functions explicitly pass around a Fiber, including some of the callers of this function)

packages/react-reconciler/src/ReactFiberHooks.new.js Outdated Show resolved Hide resolved
@@ -851,6 +853,7 @@ export function assignFiberPropertiesInDEV(
target.pendingProps = source.pendingProps;
target.memoizedProps = source.memoizedProps;
target.updateQueue = source.updateQueue;
target.memoCache = source.memoCache;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You had suggested that but the updateQueue property is set to null prior to calling the render function, so it isn't clear to me how we'd make the cache value available to useMemoCache(). Do you mean to add a new global that we set/unset as we enter/exit each component?

if (memoCache !== null) {
// Setting state during render could leave the cache in an inconsistent state,
// reset to the previous state before re-rendering.
if (previousMemoCache !== null) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT workInProgress isn't fully reset when we retry rendering after a setState: the only thing that is reset is what is explicitly done here in this function. So at the beginning we set wIP.memoCache to a clone of the previous value. The render will then mutate that fresh clone, and if there is a setstate that clone will be corrupt. We have to explicitly reset it to a fresh clone of the original value.

@@ -579,7 +611,7 @@ export function bailoutHooks(
current.lanes = removeLanes(current.lanes, lanes);
}

export function resetHooksAfterThrow(): void {
export function resetHooksAfterThrow(erroredWork: Fiber | null): void {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I confirmed that currentlyRenderingFiber can be null here. @acdlite is there some global for "current fiber" that I can rely on here? Or is the approach of passing in the fiber from the caller ok?

workInProgress.memoCache = memoCache;
}
}

workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@acdlite updateQueue is set to null here, so i'm not sure how to pass the memo cache via that object.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's on the current fiber: current.updateQueue. This is a really common pattern in the reconciler — conceptually the whole render phase is a copy-on-write of the "current" tree, where "work-in-progress" is the copy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

current isn't in scope in this file, how do i access this? you mentioned elsewhere that we try not to access .alternate, so it seems like i shouldn't do currentlyRenderingFiber.alternate.

@acdlite
Copy link
Collaborator

acdlite commented Sep 7, 2022

AFAICT workInProgress isn't fully reset when we retry rendering after a setState: the only thing that is reset is what is explicitly done here in this function. So at the beginning we set wIP.memoCache to a clone of the previous value. The render will then mutate that fresh clone, and if there is a setstate that clone will be corrupt. We have to explicitly reset it to a fresh clone of the original value.

Why do you need to reset after a setState, though? Unlike errors and suspends, we didn't throw / interrupt the function call, so the cache should be consistent even in the current Forget implementation.

@acdlite
Copy link
Collaborator

acdlite commented Sep 7, 2022

I confirmed that currentlyRenderingFiber can be null here. @acdlite is there some global for "current fiber" that I can rely on here? Or is the approach of passing in the fiber from the caller ok?

currentlyRenderingFiber is what you want in this case (the naming is confusing since it's a "work-in-progress" clone, should probably just call it workInProgress like we do literally everywhere else :D) but passing it in from the caller is also fine.

@josephsavona
Copy link
Contributor Author

josephsavona commented Sep 7, 2022

Why do you need to reset after a setState, though? Unlike errors and suspends, we didn't throw / interrupt the function call, so the cache should be consistent even in the current Forget implementation.

To clarify, it's really the combo of setState + early return that's problematic. Simplifying from the test cases that fail w/o this reset:

const c_x = x !== $[0];
$[0] = x;

if (cond) {
  setState(...);
  return;
}

let y;
if (c_x) {
  y = $[1] = ...;
} else {
  y = $[1];

If you hit the setState and early return, then upon the re-render $[0] has already been updated for the new x value but $[1] still has the old y value. When you re-evaluate c_x will be false and we inadvertently use the stale y value instead of recomputing. It is likely possible to account for this purely via Forget (the control flow analysis we're building right now should help) but we don't yet today so it's helpful to have the reset.

@josephsavona
Copy link
Contributor Author

Turns out switching to store the memoCache on updateQueue makes the question of resetting on setState+render (and error) mute, since both of those cases already reset updateQueue. Nice.

@josephsavona josephsavona force-pushed the use-memo-cache-impl branch 2 times, most recently from 0719b66 to 45fb607 Compare September 8, 2022 23:53
Comment on lines 793 to 832
let updateQueue: FunctionComponentUpdateQueue | null = (currentlyRenderingFiber.updateQueue: any);
if (updateQueue !== null) {
memoCache = updateQueue.memoCache;
}
// Otherwise clone from the current fiber
// TODO: not sure how to access the current fiber here other than going through
// currentlyRenderingFiber.alternate
if (memoCache === null) {
const current: Fiber | null = currentlyRenderingFiber.alternate;
if (current !== null) {
const currentUpdateQueue: FunctionComponentUpdateQueue | null = (current.updateQueue: any);
if (currentUpdateQueue !== null) {
const currentMemoCache: MemoCache | null = currentUpdateQueue.memoCache;
if (currentMemoCache !== null) {
memoCache = {
data: currentMemoCache.data.map(array => array.slice()),
index: 0,
};
}
}
}
}
// Finally fall back to allocating a fresh instance
if (memoCache === null) {
memoCache = {
data: [],
index: 0,
};
}
if (updateQueue === null) {
updateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = updateQueue;
}
updateQueue.memoCache = memoCache;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this whole thing is so tedious without nullish coalescing. is there a reason we don't use that?

let memoCache = currentlyRenderFiber.updateQueue ?? null;
if (memoCache === null) {
  const currentMemoCache = currentlyRenderingFiber.alternate?.updateQueue?.memoCache ?? null;
  if (currentMemoCache !== null) {
    memoCache = cow(currentMemoCache);
  }
}
...and similar

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly that we're paranoid about compiler output and it encourages people to reduce redundant type checks by combining branches

Copy link
Collaborator

@gaearon gaearon Sep 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good if code that becomes long after compilation also looks long in the source. So that we see when we're blowing up the code size. We generally avoid features that generate disproportionate bundler output. It also forces us to be very explicit about checks. (E.g. why is so much stuff nullable? Is this modeled correctly? — Not talking about your code per se but in general.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair, though I wonder if there are other ways to achieve those ends. We have a bot that alerts us to unexpected code size increases, and the type system (if used more) would help us with data modeling. A lot of types are any or mixed right now and so you have to code more defensively than may be necessary.

memoCache = updateQueue.memoCache;
}
// Otherwise clone from the current fiber
// TODO: not sure how to access the current fiber here other than going through
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine, we don't access it that often in this module so probably not worth stashing in a module-level variable

if (data === undefined) {
data = memoCache.data[memoCache.index] = new Array(size);
} else if (data.length !== size) {
// TODO: consider warning or throwing here
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

console.error is usually what we use for internal invariants because it doesn't add runtime overhead in prod

} else if (data.length !== size) {
// TODO: consider warning or throwing here
}
memoCache.index++;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need this index? Looks like it's always data.length

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whenever we initialize a cache we set the index to 0, and each call to useMemoCache increments it.

}
}
// Finally fall back to allocating a fresh instance
if (memoCache === null) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing you can do is split this into separate mountMemoCache and updateMemoCache. That's how most of the other hooks are implemented. Then mountMemoCache would always create a new one, and updateMemoCache would always copy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might do this in a followup. To confirm, in mount there is no current (ie currentlyRenderingFiber.alternate == null), and in update there is an alternate?

let useMemoCache;
let ErrorBoundary;

describe('ReactCache', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "cache" is a bit overloaded at this point :D Maybe ReactForgetCache or something

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh lol this is just copypasta from the ReactCache test, i'll update

ErrorBoundary = _ErrorBoundary;
});

// @gate experimental || www
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I usually prefer to gate on the feature flag, enableUseMemoCacheHook. That way you could change which channels a feature is available in without updating all the gate conditions.

Copy link
Collaborator

@acdlite acdlite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just nits, looks good!

@josephsavona josephsavona merged commit 3401e92 into facebook:main Sep 13, 2022
facebook-github-bot pushed a commit to facebook/react-native that referenced this pull request Sep 22, 2022
Summary:
This sync includes the following changes:
- **[0cac4d54c](facebook/react@0cac4d54c )**: Double invoked effects on suspended children ([#25307](facebook/react#25307)) //<Samuel Susla>//
- **[3d615fc14](facebook/react@3d615fc14 )**: Grammar. Removed doubles of the word "the". ([#25295](facebook/react#25295)) //<Victoria Graf>//
- **[6e3bc8a2e](facebook/react@6e3bc8a2e )**: [DevTools] Check if Proxy exists before creating DispatcherProxy ([#25278](facebook/react#25278)) //<Tianyu Yao>//
- **[e7fc04b29](facebook/react@e7fc04b29 )**: [react-dom] Reorganize react-dom internals to match react ([#25277](facebook/react#25277)) //<Josh Story>//
- **[0b54e0047](facebook/react@0b54e0047 )**: Handle rejections to avoid uncaught rejections ([#25272](facebook/react#25272)) //<Sebastian Markbåge>//
- **[c5d06fdc5](facebook/react@c5d06fdc5 )**: [Flight] Fix Webpack Chunk Loading ([#25271](facebook/react#25271)) //<Sebastian Markbåge>//
- **[975b64464](facebook/react@975b64464 )**: [Flight] response.readRoot() -> use(response) ([#25267](facebook/react#25267)) //<Sebastian Markbåge>//
- **[60fbb7b14](facebook/react@60fbb7b14 )**: [Flight] Implement FlightClient in terms of Thenable/Promises instead of throwing Promises ([#25260](facebook/react#25260)) //<Sebastian Markbåge>//
- **[c91a1e03b](facebook/react@c91a1e03b )**: experimental_useEvent ([#25229](facebook/react#25229)) //<Lauren Tan>//
- **[346c7d4c4](facebook/react@346c7d4c4 )**: straightford explicit types ([#25253](facebook/react#25253)) //<Jan Kassens>//
- **[3401e9200](facebook/react@3401e9200 )**: useMemoCache implementation ([#25143](facebook/react#25143)) //<Joseph Savona>//
- **[0556bab32](facebook/react@0556bab32 )**: [Transition Tracing] More Accurate End Time ([#25105](facebook/react#25105)) //<Luna Ruan>//
- **[5fdcd23aa](facebook/react@5fdcd23aa )**: Flow: upgrade to 0.140 ([#25252](facebook/react#25252)) //<Jan Kassens>//
- **[5c43c6f02](facebook/react@5c43c6f02 )**: Unwind the current workInProgress if it's suspended ([#25247](facebook/react#25247)) //<Sebastian Markbåge>//
- **[e52fa4c57](facebook/react@e52fa4c57 )**: Add early exit to strict mode ([#25235](facebook/react#25235)) //<Samuel Susla>//
- **[6aa38e74c](facebook/react@6aa38e74c )**: Flow: enable unsafe-addition error ([#25242](facebook/react#25242)) //<Jan Kassens>//
- **[ba7b6f418](facebook/react@ba7b6f418 )**: Flow: upgrade to 0.132 ([#25244](facebook/react#25244)) //<Jan Kassens>//
- **[9328988c0](facebook/react@9328988c0 )**: Flow: fix Fiber typed as any ([#25241](facebook/react#25241)) //<Jan Kassens>//
- **[c739cef2f](facebook/react@c739cef2f )**: Flow: ReactFiberHotReloading recursive type ([#25225](facebook/react#25225)) //<Jan Kassens>//
- **[c156ecd48](facebook/react@c156ecd48 )**: Add some test coverage for some error cases ([#25240](facebook/react#25240)) //<Sebastian Markbåge>//
- **[3613284dc](facebook/react@3613284dc )**: experimental_use(context) for server components and ssr ([#25226](facebook/react#25226)) //<mofeiZ>//
- **[269c4e975](facebook/react@269c4e975 )**: Prevent infinite re-renders in StrictMode + Offscreen ([#25203](facebook/react#25203)) //<Samuel Susla>//
- **[8003ab9cf](facebook/react@8003ab9cf )**: Flow: remove explicit object syntax ([#25223](facebook/react#25223)) //<Jan Kassens>//
- **[492c6e29e](facebook/react@492c6e29e )**: Flow: upgrade to 0.127 ([#25221](facebook/react#25221)) //<Jan Kassens>//
- **[8a9e7b6ce](facebook/react@8a9e7b6ce )**: Flow: implicit-inexact-object=error ([#25210](facebook/react#25210)) //<Jan Kassens>//
- **[37cc6bf12](facebook/react@37cc6bf12 )**: Remove useDeferredValue and useTransition from Flight subset ([#25215](facebook/react#25215)) //<Sebastian Markbåge>//

Changelog:
[General][Changed] - React Native sync for revisions c28f313...0cac4d5

jest_e2e[run_all_tests]

Reviewed By: rickhanlonii

Differential Revision: D39696377

fbshipit-source-id: 113878d22d6244b8555b5fb86db1da5d43f7cfd9
rickhanlonii pushed a commit that referenced this pull request Oct 5, 2022
* useMemoCache impl
* test for multiple calls in a component (from custom hook)
* Use array of arrays for multiple calls; use alternate/local as the backup
* code cleanup
* fix internal test
* oops we do not support nullable property access
* Simplify implementation, still have questions on some of the PR feedback though
* Gate all code based on the feature flag
* refactor to use updateQueue
* address feedback
* Try to eliminate size increase in prod bundle
* update to retrigger ci
OlimpiaZurek pushed a commit to OlimpiaZurek/react-native that referenced this pull request May 22, 2023
Summary:
This sync includes the following changes:
- **[0cac4d54c](facebook/react@0cac4d54c )**: Double invoked effects on suspended children ([facebook#25307](facebook/react#25307)) //<Samuel Susla>//
- **[3d615fc14](facebook/react@3d615fc14 )**: Grammar. Removed doubles of the word "the". ([facebook#25295](facebook/react#25295)) //<Victoria Graf>//
- **[6e3bc8a2e](facebook/react@6e3bc8a2e )**: [DevTools] Check if Proxy exists before creating DispatcherProxy ([facebook#25278](facebook/react#25278)) //<Tianyu Yao>//
- **[e7fc04b29](facebook/react@e7fc04b29 )**: [react-dom] Reorganize react-dom internals to match react ([facebook#25277](facebook/react#25277)) //<Josh Story>//
- **[0b54e0047](facebook/react@0b54e0047 )**: Handle rejections to avoid uncaught rejections ([facebook#25272](facebook/react#25272)) //<Sebastian Markbåge>//
- **[c5d06fdc5](facebook/react@c5d06fdc5 )**: [Flight] Fix Webpack Chunk Loading ([facebook#25271](facebook/react#25271)) //<Sebastian Markbåge>//
- **[975b64464](facebook/react@975b64464 )**: [Flight] response.readRoot() -> use(response) ([facebook#25267](facebook/react#25267)) //<Sebastian Markbåge>//
- **[60fbb7b14](facebook/react@60fbb7b14 )**: [Flight] Implement FlightClient in terms of Thenable/Promises instead of throwing Promises ([facebook#25260](facebook/react#25260)) //<Sebastian Markbåge>//
- **[c91a1e03b](facebook/react@c91a1e03b )**: experimental_useEvent ([facebook#25229](facebook/react#25229)) //<Lauren Tan>//
- **[346c7d4c4](facebook/react@346c7d4c4 )**: straightford explicit types ([facebook#25253](facebook/react#25253)) //<Jan Kassens>//
- **[3401e9200](facebook/react@3401e9200 )**: useMemoCache implementation ([facebook#25143](facebook/react#25143)) //<Joseph Savona>//
- **[0556bab32](facebook/react@0556bab32 )**: [Transition Tracing] More Accurate End Time ([facebook#25105](facebook/react#25105)) //<Luna Ruan>//
- **[5fdcd23aa](facebook/react@5fdcd23aa )**: Flow: upgrade to 0.140 ([facebook#25252](facebook/react#25252)) //<Jan Kassens>//
- **[5c43c6f02](facebook/react@5c43c6f02 )**: Unwind the current workInProgress if it's suspended ([facebook#25247](facebook/react#25247)) //<Sebastian Markbåge>//
- **[e52fa4c57](facebook/react@e52fa4c57 )**: Add early exit to strict mode ([facebook#25235](facebook/react#25235)) //<Samuel Susla>//
- **[6aa38e74c](facebook/react@6aa38e74c )**: Flow: enable unsafe-addition error ([facebook#25242](facebook/react#25242)) //<Jan Kassens>//
- **[ba7b6f418](facebook/react@ba7b6f418 )**: Flow: upgrade to 0.132 ([facebook#25244](facebook/react#25244)) //<Jan Kassens>//
- **[9328988c0](facebook/react@9328988c0 )**: Flow: fix Fiber typed as any ([facebook#25241](facebook/react#25241)) //<Jan Kassens>//
- **[c739cef2f](facebook/react@c739cef2f )**: Flow: ReactFiberHotReloading recursive type ([facebook#25225](facebook/react#25225)) //<Jan Kassens>//
- **[c156ecd48](facebook/react@c156ecd48 )**: Add some test coverage for some error cases ([facebook#25240](facebook/react#25240)) //<Sebastian Markbåge>//
- **[3613284dc](facebook/react@3613284dc )**: experimental_use(context) for server components and ssr ([facebook#25226](facebook/react#25226)) //<mofeiZ>//
- **[269c4e975](facebook/react@269c4e975 )**: Prevent infinite re-renders in StrictMode + Offscreen ([facebook#25203](facebook/react#25203)) //<Samuel Susla>//
- **[8003ab9cf](facebook/react@8003ab9cf )**: Flow: remove explicit object syntax ([facebook#25223](facebook/react#25223)) //<Jan Kassens>//
- **[492c6e29e](facebook/react@492c6e29e )**: Flow: upgrade to 0.127 ([facebook#25221](facebook/react#25221)) //<Jan Kassens>//
- **[8a9e7b6ce](facebook/react@8a9e7b6ce )**: Flow: implicit-inexact-object=error ([facebook#25210](facebook/react#25210)) //<Jan Kassens>//
- **[37cc6bf12](facebook/react@37cc6bf12 )**: Remove useDeferredValue and useTransition from Flight subset ([facebook#25215](facebook/react#25215)) //<Sebastian Markbåge>//

Changelog:
[General][Changed] - React Native sync for revisions c28f313...0cac4d5

jest_e2e[run_all_tests]

Reviewed By: rickhanlonii

Differential Revision: D39696377

fbshipit-source-id: 113878d22d6244b8555b5fb86db1da5d43f7cfd9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants