Skip to content

Commit

Permalink
Implement experimental_useOptimisticState (facebook#26740)
Browse files Browse the repository at this point in the history
This adds an experimental hook tentatively called useOptimisticState.
(The actual name needs some bikeshedding.)

The headline feature is that you can use it to implement optimistic
updates. If you set some optimistic state during a transition/action,
the state will be automatically reverted once the transition completes.

Another feature is that the optimistic updates will be continually
rebased on top of the latest state.

It's easiest to explain with examples; we'll publish documentation as
the API gets closer to stabilizing. See tests for now.

Technically the use cases for this hook are broader than just optimistic
updates; you could use it implement any sort of "pending" state, such as
the ones exposed by useTransition and useFormStatus. But we expect
people will most often reach for this hook to implement the optimistic
update pattern; simpler cases are covered by those other hooks.
  • Loading branch information
acdlite authored and AndyPengc12 committed Apr 15, 2024
1 parent 4d621c1 commit d1e8512
Show file tree
Hide file tree
Showing 13 changed files with 846 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1243,10 +1243,9 @@ describe('Timeline profiler', () => {
function Example() {
const setHigh = React.useState(0)[1];
const setLow = React.useState(0)[1];
const startTransition = React.useTransition()[1];

updaterFn = () => {
startTransition(() => {
React.startTransition(() => {
setLow(prevLow => prevLow + 1);
});
setHigh(prevHigh => prevHigh + 1);
Expand All @@ -1265,24 +1264,6 @@ describe('Timeline profiler', () => {
const timelineData = stopProfilingAndGetTimelineData();
expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
[
{
"componentName": "Example",
"componentStack": "
in Example (at **)",
"lanes": "0b0000000000000000000000000001000",
"timestamp": 10,
"type": "schedule-state-update",
"warning": null,
},
{
"componentName": "Example",
"componentStack": "
in Example (at **)",
"lanes": "0b0000000000000000000000010000000",
"timestamp": 10,
"type": "schedule-state-update",
"warning": null,
},
{
"componentName": "Example",
"componentStack": "
Expand Down
19 changes: 19 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMFizzForm-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ let React;
let ReactDOMServer;
let ReactDOMClient;
let useFormStatus;
let useOptimisticState;

describe('ReactDOMFizzForm', () => {
beforeEach(() => {
Expand All @@ -30,6 +31,7 @@ describe('ReactDOMFizzForm', () => {
ReactDOMServer = require('react-dom/server.browser');
ReactDOMClient = require('react-dom/client');
useFormStatus = require('react-dom').experimental_useFormStatus;
useOptimisticState = require('react').experimental_useOptimisticState;
act = require('internal-test-utils').act;
container = document.createElement('div');
document.body.appendChild(container);
Expand Down Expand Up @@ -453,4 +455,21 @@ describe('ReactDOMFizzForm', () => {
expect(deletedTitle).toBe('Hello');
expect(rootActionCalled).toBe(false);
});

// @gate enableAsyncActions
it('useOptimisticState returns passthrough value', async () => {
function App() {
const [optimisticState] = useOptimisticState('hi');
return optimisticState;
}

const stream = await ReactDOMServer.renderToReadableStream(<App />);
await readIntoContainer(stream);
expect(container.textContent).toBe('hi');

await act(async () => {
ReactDOMClient.hydrateRoot(container, <App />);
});
expect(container.textContent).toBe('hi');
});
});
Loading

0 comments on commit d1e8512

Please sign in to comment.