Skip to content

Commit

Permalink
[Fizz] useEvent
Browse files Browse the repository at this point in the history
  • Loading branch information
gaearon committed Sep 27, 2022
1 parent cb5084d commit 73a0d14
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 2 deletions.
101 changes: 101 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5545,4 +5545,105 @@ describe('ReactDOMFizzServer', () => {
expect(getVisibleChildren(container)).toEqual('Hi');
});
});

describe('useEvent', () => {
// @gate enableUseEventHook
it('can server render a component with useEvent', async () => {
const ref = React.createRef();
function App() {
const [count, setCount] = React.useState(0);
const onClick = React.experimental_useEvent(() => {
setCount(c => c + 1);
});
return (
<button ref={ref} onClick={() => onClick()}>
{count}
</button>
);
}
await act(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
pipe(writable);
});
expect(getVisibleChildren(container)).toEqual(<button>0</button>);

ReactDOMClient.hydrateRoot(container, <App />);
expect(Scheduler).toFlushAndYield([]);
expect(getVisibleChildren(container)).toEqual(<button>0</button>);

ref.current.dispatchEvent(
new window.MouseEvent('click', {bubbles: true}),
);
await jest.runAllTimers();
expect(getVisibleChildren(container)).toEqual(<button>1</button>);
});

// @gate enableUseEventHook
it('throws if useEvent is called during a server render', async () => {
const logs = [];
function App() {
const onRender = React.experimental_useEvent(() => {
logs.push('rendered');
});
onRender();
return <p>Hello</p>;
}

const reportedServerErrors = [];
let caughtError;
try {
await act(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />, {
onError(e) {
reportedServerErrors.push(e);
},
});
pipe(writable);
});
} catch (err) {
caughtError = err;
}
expect(logs).toEqual([]);
expect(caughtError.message).toContain(
'Cannot call a function returned by useEvent',
);
expect(reportedServerErrors).toEqual([caughtError]);
});

// @gate enableUseEventHook
it('does not guarantee useEvent return values during server rendering are distinct', async () => {
function App() {
const onClick1 = React.experimental_useEvent(() => {});
const onClick2 = React.experimental_useEvent(() => {});
if (onClick1 === onClick2) {
return <div />;
} else {
return <span />;
}
}
await act(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
pipe(writable);
});
expect(getVisibleChildren(container)).toEqual(<div />);

const errors = [];
ReactDOMClient.hydrateRoot(container, <App />, {
onRecoverableError(error) {
errors.push(error);
},
});
expect(() => {
expect(Scheduler).toFlushAndYield([]);
}).toErrorDev(
[
'Expected server HTML to contain a matching <span> in <div>',
'An error occurred during hydration',
],
{withoutStack: 1},
);
expect(errors.length).toEqual(2);
expect(getVisibleChildren(container)).toEqual(<span />);
});
});
});
14 changes: 14 additions & 0 deletions packages/react-server/src/ReactFizzHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {makeId} from './ReactServerFormatConfig';
import {
enableCache,
enableUseHook,
enableUseEventHook,
enableUseMemoCacheHook,
} from 'shared/ReactFeatureFlags';
import is from 'shared/objectIs';
Expand Down Expand Up @@ -502,6 +503,16 @@ export function useCallback<T>(
return useMemo(() => callback, deps);
}

function throwOnUseEventCall() {
throw new Error(
'Cannot call a function returned by useEvent during server rendering.',
);
}

export function useEvent<T>(callback: () => T): () => T {
return throwOnUseEventCall;
}

// TODO Decide on how to implement this hook for server rendering.
// If a mutation occurs during render, consider triggering a Suspense boundary
// and falling back to client rendering.
Expand Down Expand Up @@ -675,6 +686,9 @@ if (enableCache) {
Dispatcher.getCacheForType = getCacheForType;
Dispatcher.useCacheRefresh = useCacheRefresh;
}
if (enableUseEventHook) {
Dispatcher.useEvent = useEvent;
}
if (enableUseMemoCacheHook) {
Dispatcher.useMemoCache = useMemoCache;
}
Expand Down
5 changes: 3 additions & 2 deletions scripts/error-codes/codes.json
Original file line number Diff line number Diff line change
Expand Up @@ -426,5 +426,6 @@
"438": "An unsupported type was passed to use(): %s",
"439": "We didn't expect to see a forward reference. This is a bug in the React Server.",
"440": "An event from useEvent was called during render.",
"441": "An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error."
}
"441": "An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.",
"442": "Cannot call a function returned by useEvent during server rendering."
}

0 comments on commit 73a0d14

Please sign in to comment.