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

[Flight] Implement useId hook #24172

Merged
merged 6 commits into from May 31, 2022
Merged

[Flight] Implement useId hook #24172

merged 6 commits into from May 31, 2022

Conversation

gnoff
Copy link
Collaborator

@gnoff gnoff commented Mar 27, 2022

The approach for ids for Flight is different from Fizz/Client where there is a need for determinancy. Flight rendered elements will not be rendered on the client and as such the ids generated in a request only need to be unique. However since FLight does support refetching subtrees it is possible a client will need to patch up a part of the tree rather than replacing the entire thing so it is not safe to use a simple incrementing counter. To solve for this we allow the caller to specify a prefix. On an initial fetch it is likely this will be empty but on refetches or subtrees we expect to have a client useId provide the prefix since it will guaranteed be unique for that subtree and thus for the entire tree. It is also possible that we will automatically provide prefixes based on a client/Fizz useId on refetches

in addition to the core change I also modified the structure of options for renderToReadableStream where onError, context, and the new identifierPrefix are properties of an Options object argument to avoid the clumsiness of a growing list of optional function arguments.

@sizebot
Copy link

sizebot commented Mar 27, 2022

Comparing: 26a5b3c...d72d44d

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 = 131.54 kB 131.54 kB = 42.26 kB 42.26 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js = 136.80 kB 136.80 kB = 43.81 kB 43.81 kB
facebook-www/ReactDOM-prod.classic.js = 439.85 kB 439.85 kB = 80.44 kB 80.44 kB
facebook-www/ReactDOM-prod.modern.js = 425.14 kB 425.14 kB = 78.29 kB 78.28 kB
facebook-www/ReactDOMForked-prod.classic.js = 439.85 kB 439.85 kB = 80.45 kB 80.44 kB
oss-experimental/react-noop-renderer/cjs/react-noop-renderer-flight-server.production.min.js +4.20% 1.02 kB 1.07 kB +3.02% 0.53 kB 0.55 kB
oss-stable-semver/react-noop-renderer/cjs/react-noop-renderer-flight-server.production.min.js +4.20% 1.02 kB 1.07 kB +3.02% 0.53 kB 0.55 kB
oss-stable/react-noop-renderer/cjs/react-noop-renderer-flight-server.production.min.js +4.20% 1.02 kB 1.07 kB +3.02% 0.53 kB 0.55 kB
oss-experimental/react-noop-renderer/cjs/react-noop-renderer-flight-server.development.js +3.34% 2.07 kB 2.13 kB +1.88% 0.85 kB 0.87 kB
oss-stable-semver/react-noop-renderer/cjs/react-noop-renderer-flight-server.development.js +3.34% 2.07 kB 2.13 kB +1.88% 0.85 kB 0.87 kB
oss-stable/react-noop-renderer/cjs/react-noop-renderer-flight-server.development.js +3.34% 2.07 kB 2.13 kB +1.88% 0.85 kB 0.87 kB
oss-experimental/react-server/cjs/react-server-flight.production.min.js +2.43% 9.90 kB 10.14 kB +2.83% 3.71 kB 3.81 kB
oss-stable-semver/react-server/cjs/react-server-flight.production.min.js +2.43% 9.90 kB 10.14 kB +2.83% 3.71 kB 3.81 kB
oss-stable/react-server/cjs/react-server-flight.production.min.js +2.43% 9.90 kB 10.14 kB +2.83% 3.71 kB 3.81 kB
facebook-relay/flight/ReactFlightNativeRelayServer-prod.js +2.05% 21.21 kB 21.65 kB +2.42% 5.24 kB 5.37 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-noop-renderer/cjs/react-noop-renderer-flight-server.production.min.js +4.20% 1.02 kB 1.07 kB +3.02% 0.53 kB 0.55 kB
oss-stable-semver/react-noop-renderer/cjs/react-noop-renderer-flight-server.production.min.js +4.20% 1.02 kB 1.07 kB +3.02% 0.53 kB 0.55 kB
oss-stable/react-noop-renderer/cjs/react-noop-renderer-flight-server.production.min.js +4.20% 1.02 kB 1.07 kB +3.02% 0.53 kB 0.55 kB
oss-experimental/react-noop-renderer/cjs/react-noop-renderer-flight-server.development.js +3.34% 2.07 kB 2.13 kB +1.88% 0.85 kB 0.87 kB
oss-stable-semver/react-noop-renderer/cjs/react-noop-renderer-flight-server.development.js +3.34% 2.07 kB 2.13 kB +1.88% 0.85 kB 0.87 kB
oss-stable/react-noop-renderer/cjs/react-noop-renderer-flight-server.development.js +3.34% 2.07 kB 2.13 kB +1.88% 0.85 kB 0.87 kB
oss-experimental/react-server/cjs/react-server-flight.production.min.js +2.43% 9.90 kB 10.14 kB +2.83% 3.71 kB 3.81 kB
oss-stable-semver/react-server/cjs/react-server-flight.production.min.js +2.43% 9.90 kB 10.14 kB +2.83% 3.71 kB 3.81 kB
oss-stable/react-server/cjs/react-server-flight.production.min.js +2.43% 9.90 kB 10.14 kB +2.83% 3.71 kB 3.81 kB
facebook-relay/flight/ReactFlightNativeRelayServer-prod.js +2.05% 21.21 kB 21.65 kB +2.42% 5.24 kB 5.37 kB
facebook-www/ReactFlightDOMRelayServer-prod.classic.js +1.90% 26.94 kB 27.46 kB +1.96% 7.09 kB 7.23 kB
facebook-www/ReactFlightDOMRelayServer-prod.modern.js +1.89% 27.02 kB 27.53 kB +1.95% 7.13 kB 7.27 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.production.min.server.js +1.77% 16.07 kB 16.35 kB +1.76% 6.03 kB 6.14 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.production.min.server.js +1.77% 16.07 kB 16.35 kB +1.76% 6.03 kB 6.14 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.production.min.server.js +1.76% 16.12 kB 16.40 kB +1.62% 6.05 kB 6.15 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.production.min.server.js +1.71% 16.64 kB 16.92 kB +1.65% 6.17 kB 6.27 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.production.min.server.js +1.71% 16.64 kB 16.92 kB +1.65% 6.17 kB 6.27 kB
facebook-relay/flight/ReactFlightNativeRelayServer-dev.js +1.70% 38.86 kB 39.52 kB +2.16% 9.88 kB 10.10 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.production.min.server.js +1.70% 16.68 kB 16.97 kB +1.60% 6.20 kB 6.30 kB
oss-stable-semver/react-server-dom-webpack/umd/react-server-dom-webpack-writer.browser.production.min.server.js +1.68% 16.26 kB 16.54 kB +1.56% 6.08 kB 6.17 kB
oss-stable/react-server-dom-webpack/umd/react-server-dom-webpack-writer.browser.production.min.server.js +1.68% 16.26 kB 16.54 kB +1.56% 6.08 kB 6.17 kB
oss-experimental/react-server/cjs/react-server-flight.development.js +1.68% 38.53 kB 39.18 kB +1.95% 9.79 kB 9.98 kB
oss-stable-semver/react-server/cjs/react-server-flight.development.js +1.68% 38.53 kB 39.18 kB +1.95% 9.79 kB 9.98 kB
oss-stable/react-server/cjs/react-server-flight.development.js +1.68% 38.53 kB 39.18 kB +1.95% 9.79 kB 9.98 kB
oss-experimental/react-server-dom-webpack/umd/react-server-dom-webpack-writer.browser.production.min.server.js +1.68% 16.31 kB 16.58 kB +1.54% 6.10 kB 6.19 kB
facebook-www/ReactFlightDOMRelayServer-dev.classic.js +1.46% 53.37 kB 54.15 kB +1.79% 13.54 kB 13.79 kB
facebook-www/ReactFlightDOMRelayServer-dev.modern.js +1.46% 53.42 kB 54.20 kB +1.78% 13.56 kB 13.80 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.development.server.js +1.16% 62.10 kB 62.81 kB +1.23% 16.12 kB 16.31 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.development.server.js +1.16% 62.10 kB 62.81 kB +1.23% 16.12 kB 16.31 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.development.server.js +1.16% 62.15 kB 62.87 kB +1.24% 16.13 kB 16.33 kB
oss-stable-semver/react-server-dom-webpack/umd/react-server-dom-webpack-writer.browser.development.server.js +1.16% 65.28 kB 66.03 kB +1.30% 16.33 kB 16.54 kB
oss-stable/react-server-dom-webpack/umd/react-server-dom-webpack-writer.browser.development.server.js +1.16% 65.28 kB 66.03 kB +1.30% 16.33 kB 16.54 kB
oss-experimental/react-server-dom-webpack/umd/react-server-dom-webpack-writer.browser.development.server.js +1.15% 65.34 kB 66.09 kB +1.30% 16.35 kB 16.56 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.development.server.js +1.14% 63.78 kB 64.51 kB +1.24% 16.27 kB 16.47 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.development.server.js +1.14% 63.78 kB 64.51 kB +1.24% 16.27 kB 16.47 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.development.server.js +1.14% 63.83 kB 64.56 kB +1.23% 16.29 kB 16.49 kB

Generated by 🚫 dangerJS against d72d44d

@@ -512,6 +512,59 @@ describe('ReactFlight', () => {
);
});

describe('Hooks', () => {
Copy link
Member

@rickhanlonii rickhanlonii Mar 27, 2022

Choose a reason for hiding this comment

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

Do we have a test for how useId would work for sever components that have children that are client components and need the same ID?

Copy link
Collaborator Author

@gnoff gnoff Mar 28, 2022

Choose a reason for hiding this comment

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

I don't know if I'm misunderstanding some finer point but my expectation is that if you use useId in Flight you will if necessary pass that id as props to client components like any other prop when needed for some purpose

Copy link
Member

@rickhanlonii rickhanlonii Mar 29, 2022

Choose a reason for hiding this comment

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

Yeah, just checking if there's a test for it already!

@gnoff gnoff changed the title Implements useId hook for Flight server. [Flight] Implement useId hook Mar 28, 2022
let currentRequest = null;

export function prepareToUseHooksForRequest(request: Request) {
currentRequest = request;
}

export function resetHooksForRequest() {
currentRequest = null;
}

Copy link
Collaborator Author

@gnoff gnoff Mar 28, 2022

Choose a reason for hiding this comment

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

@sebmarkbage there is already setCurrentCache behavior that I could consolidate since they are set and reset at the same points in performWork. For setting the cache I noticed that the previous cache is restored but my understanding of the code is the previous cache will always be null (meaning you cannot have a second performWork start before a first performWork finishes)

If the prevCache restoration is important it makes consolidation of multiple things to set awkward but we could move to just reading the cache from the request given it is set for the duration of performWork and holds the cache

: '';
const id = currentRequest.identifierCount++;
// use 'S' for Flight components to distinguish from 'R' and 'r' in Fizz/Client
return ':' + prefix + 'S' + id.toString(32) + ':';
Copy link
Collaborator Author

@gnoff gnoff Mar 28, 2022

Choose a reason for hiding this comment

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

@sebmarkbage I picked S for Server, bikeshed?

Copy link
Collaborator

@sebmarkbage sebmarkbage left a comment

One decision we made was that we recognize that this case won't be semantically equivalent:

<ClientComponent>
  <ServerComponent />
</ClientComponent>
let ServerComponent = () => <div id={useId} />;
let ClientComponent = ({children}) => <div>{children}{children}</div>;

Since the same ID will be used for both children. We're ok with that. We might add a DEV warning in the future.

@sebmarkbage
Copy link
Collaborator

sebmarkbage commented May 27, 2022

An unfortunate consequence of using an incrementing counter is that it won't be deterministic but Flight is already not deterministic since it doesn't yet inline suspended points if they unsuspend before flushing.

@@ -826,6 +833,7 @@ function performWork(request: Request): void {
const prevCache = getCurrentCache();
ReactCurrentDispatcher.current = Dispatcher;
setCurrentCache(request.cache);
Copy link
Collaborator

@sebmarkbage sebmarkbage May 30, 2022

Choose a reason for hiding this comment

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

Instead of setting up two different contextual variables (and more to come) we should just use the request for the Cache too. That can be a follow up but should fast follow.

While we're add it we should also put the Cache behind the enableCache since it's not coupled to Flight.

Copy link
Collaborator

@sebmarkbage sebmarkbage left a comment

Nits.

@@ -102,14 +108,12 @@ export type Request = {
writtenSymbols: Map<Symbol, number>,
writtenModules: Map<ModuleKey, number>,
writtenProviders: Map<string, number>,
identifierPrefix?: string,
Copy link
Collaborator

@sebmarkbage sebmarkbage May 30, 2022

Choose a reason for hiding this comment

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

We should never have optional properties in internal data structures since we want consistent hidden classes. Only short lived and rare config objects and such should have those.

This should just be string always and set to empty string when you initialize it if no option is passed.

if (currentRequest === null) {
throw new Error('useId can only be used while React is rendering');
}
const prefix = currentRequest.identifierPrefix
Copy link
Collaborator

@sebmarkbage sebmarkbage May 30, 2022

Choose a reason for hiding this comment

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

Instead of checking this every useId call, just set it up to an empty string in createRequest.

Copy link
Collaborator Author

@gnoff gnoff May 31, 2022

Choose a reason for hiding this comment

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

Would it make sense to go even further and stash the identifierPrefix and identifierCount in module scope alonside the request and then reference prefix / increment count until resetHooks is called and stash the results back on the request?

gnoff added 4 commits May 31, 2022
The approach for ids for Flight is different from Fizz/Client where there is a need for determinancy. Flight rendered elements will not be rendered on the client and as such the ids generated in a request only need to be unique. However since FLight does support refetching subtrees it is possible a client will need to patch up a part of the tree rather than replacing the entire thing so it is not safe to use a simple incrementing counter. To solve for this we allow the caller to specify a prefix. On an initial fetch it is likely this will be empty but on refetches or subtrees we expect to have a client `useId` provide the prefix since it will guaranteed be unique for that subtree and thus for the entire tree. It is also possible that we will automatically provide prefixes based on a client/Fizz useId on refetches

in addition to the core change I also modified the structure of options for renderToReadableStream where `onError`, `context`, and the new `identifierPrefix` are properties of an Options object argument to avoid the clumsiness of a growing list of optional function arguments.
@gnoff gnoff requested review from sebmarkbage and removed request for salazarm May 31, 2022
@gnoff gnoff merged commit dd4950c into facebook:main May 31, 2022
36 checks passed
@gnoff gnoff deleted the flight-useid branch May 31, 2022
facebook-github-bot pushed a commit to facebook/react-native that referenced this issue Jun 6, 2022
Summary:
This sync includes the following changes:
- **[dd4950c90](facebook/react@dd4950c90 )**: [Flight] Implement useId hook ([#24172](facebook/react#24172)) //<Josh Story>//
- **[26a5b3c7f](facebook/react@26a5b3c7f )**: Explicitly set `highWaterMark` to 0 for `ReadableStream` ([#24641](facebook/react#24641)) //<Josh Larson>//
- **[aec575914](facebook/react@aec575914 )**: [Fizz] Send errors down to client ([#24551](facebook/react#24551)) //<Josh Story>//
- **[a2766387e](facebook/react@a2766387e )**: [Fizz] Improve text separator byte efficiency ([#24630](facebook/react#24630)) //<Josh Story>//
- **[f7860538a](facebook/react@f7860538a )**: Fix typo in useSyncExternalStore main entry point error ([#24631](facebook/react#24631)) //<François Chalifour>//
- **[1bed20731](facebook/react@1bed20731 )**: Add a module map option to the Webpack Flight Client ([#24629](facebook/react#24629)) //<Sebastian Markbåge>//
- **[b2763d3ea](facebook/react@b2763d3ea )**: Move hydration code out of normal Suspense path ([#24532](facebook/react#24532)) //<Andrew Clark>//
- **[357a61324](facebook/react@357a61324 )**: [DevTools][Transition Tracing] Added support for Suspense Boundaries ([#23365](facebook/react#23365)) //<Luna Ruan>//
- **[2c8a1452b](facebook/react@2c8a1452b )**: Fix ignored setState in Safari when iframe is touched ([#24459](facebook/react#24459)) //<dan>//
- **[62662633d](facebook/react@62662633d )**: Remove enableFlipOffscreenUnhideOrder ([#24545](facebook/react#24545)) //<Ricky>//
- **[34da5aa69](facebook/react@34da5aa69 )**: Only treat updates to lazy as a new mount in legacy mode ([#24530](facebook/react#24530)) //<Ricky>//
- **[46a6d77e3](facebook/react@46a6d77e3 )**: Unify JSResourceReference Interfaces ([#24507](facebook/react#24507)) //<Timothy Yung>//
- **[6cbf0f7fa](facebook/react@6cbf0f7fa )**: Fork ReactSymbols ([#24484](facebook/react#24484)) //<Ricky>//
- **[a10a9a6b5](facebook/react@a10a9a6b5 )**: Add test for hiding children after layout destroy ([#24483](facebook/react#24483)) //<Ricky>//
- **[b4eb0ad71](facebook/react@b4eb0ad71 )**: Do not replay erroring beginWork with invokeGuardedCallback when suspended or previously errored ([#24480](facebook/react#24480)) //<Josh Story>//
- **[99eef9e2d](facebook/react@99eef9e2d )**: Hide children of Offscreen after destroy effects ([#24446](facebook/react#24446)) //<Ricky>//
- **[ce1386028](facebook/react@ce1386028 )**: Remove enablePersistentOffscreenHostContainer flag ([#24460](facebook/react#24460)) //<Andrew Clark>//
- **[72b7462fe](facebook/react@72b7462fe )**: Bump local package.json versions for 18.1 release ([#24447](facebook/react#24447)) //<Andrew Clark>//
- **[22edb9f77](facebook/react@22edb9f77 )**: React `version` field should match package.json ([#24445](facebook/react#24445)) //<Andrew Clark>//
- **[6bf3deef5](facebook/react@6bf3deef5 )**: Upgrade react-shallow-renderer to support react 18 ([#24442](facebook/react#24442)) //<Michael サイトー 中村 Bashurov>//

Changelog:
[General][Changed] - React Native sync for revisions bd4784c...d300ceb

jest_e2e[run_all_tests]

Reviewed By: cortinico, kacieb

Differential Revision: D36874368

fbshipit-source-id: c0ee015f4ef2fa56e57f7a1f6bc37dd05c949877
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.

None yet

5 participants