Skip to content

Commit 3d5f7fb

Browse files
committed
initial implementation
1 parent c80e541 commit 3d5f7fb

File tree

2 files changed

+131
-3
lines changed

2 files changed

+131
-3
lines changed

packages/react-reconciler/src/ReactFiberHooks.new.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,6 @@ let ignorePreviousDependencies: boolean = false;
232232
function mountHookTypesDev() {
233233
if (__DEV__) {
234234
const hookName = ((currentHookNameInDev: any): HookType);
235-
236235
if (hookTypesDev === null) {
237236
hookTypesDev = [hookName];
238237
} else {
@@ -485,7 +484,21 @@ export function renderWithHooks<Props, SecondArg>(
485484
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
486485

487486
if (__DEV__) {
488-
workInProgress._debugHookTypes = hookTypesDev;
487+
workInProgress._debugHookTypes =
488+
workInProgress.mode & ConcurrentMode ? hookTypesDev ?? [] : hookTypesDev;
489+
const renderedNoHookFromHooks =
490+
currentHookNameInDev == null && hookTypesDev && hookTypesDev.length;
491+
if (renderedNoHookFromHooks) {
492+
throw new Error(
493+
'Rendered fewer hooks than expected. This may be caused by an accidental ' +
494+
'early return statement.',
495+
);
496+
}
497+
const rendererdHookFromNoHook =
498+
currentHookNameInDev != null && (!hookTypesDev || !hookTypesDev.length);
499+
if (rendererdHookFromNoHook) {
500+
throw new Error('Rendered more hooks than during the previous render.');
501+
}
489502
}
490503

491504
// This check uses currentHook so that it works the same in DEV and prod bundles.

packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3847,6 +3847,38 @@ describe('ReactHooksWithNoopRenderer', () => {
38473847
// expect(ReactNoop.getChildren()).toEqual([span('A: 2, B: 3, C: 4')]);
38483848
});
38493849

3850+
it('mount first state', () => {
3851+
function App(props) {
3852+
let A;
3853+
if (props.loadA) {
3854+
[A, _] = useState(0);
3855+
} else {
3856+
A = '[not loaded]';
3857+
}
3858+
3859+
return <Text text={`A: ${A}`} />;
3860+
}
3861+
3862+
ReactNoop.render(<App loadA={false} />);
3863+
expect(Scheduler).toFlushAndYield(['A: [not loaded]']);
3864+
expect(ReactNoop.getChildren()).toEqual([span('A: [not loaded]')]);
3865+
3866+
ReactNoop.render(<App loadA={true} />);
3867+
expect(() => {
3868+
expect(() => {
3869+
expect(Scheduler).toFlushAndYield(['A: 0']);
3870+
}).toThrow('Rendered more hooks than during the previous render');
3871+
}).toErrorDev([
3872+
'Warning: React has detected a change in the order of Hooks called by App. ' +
3873+
'This will lead to bugs and errors if not fixed. For more information, ' +
3874+
'read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks\n\n' +
3875+
' Previous render Next render\n' +
3876+
' ------------------------------------------------------\n' +
3877+
'1. undefined useState\n' +
3878+
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
3879+
]);
3880+
});
3881+
38503882
it('unmount state', () => {
38513883
let updateA;
38523884
let updateB;
@@ -3887,7 +3919,90 @@ describe('ReactHooksWithNoopRenderer', () => {
38873919
);
38883920
});
38893921

3890-
it('unmount effects', () => {
3922+
it('unmount last state', () => {
3923+
function App(props) {
3924+
let A;
3925+
if (props.loadA) {
3926+
[A, _] = useState(0);
3927+
} else {
3928+
A = '[not loaded]';
3929+
}
3930+
3931+
return <Text text={`A: ${A}`} />;
3932+
}
3933+
3934+
ReactNoop.render(<App loadA={true} />);
3935+
expect(Scheduler).toFlushAndYield(['A: 0']);
3936+
expect(ReactNoop.getChildren()).toEqual([span('A: 0')]);
3937+
ReactNoop.render(<App loadA={false} />);
3938+
expect(Scheduler).toFlushAndThrow(
3939+
'Rendered fewer hooks than expected. This may be caused by an ' +
3940+
'accidental early return statement.',
3941+
);
3942+
});
3943+
3944+
it('mount effect', () => {
3945+
function App(props) {
3946+
if (props.showMore) {
3947+
useEffect(() => {
3948+
Scheduler.unstable_yieldValue('Mount A');
3949+
return () => {
3950+
Scheduler.unstable_yieldValue('Unmount A');
3951+
};
3952+
}, []);
3953+
}
3954+
3955+
return null;
3956+
}
3957+
3958+
ReactNoop.render(<App showMore={false} />);
3959+
expect(Scheduler).toFlushAndYield([]);
3960+
3961+
act(() => {
3962+
ReactNoop.render(<App showMore={true} />);
3963+
expect(() => {
3964+
expect(() => {
3965+
expect(Scheduler).toFlushAndYield(['Mount A']);
3966+
}).toThrow('Rendered more hooks than during the previous render');
3967+
}).toErrorDev([
3968+
'Warning: React has detected a change in the order of Hooks called by App. ' +
3969+
'This will lead to bugs and errors if not fixed. For more information, ' +
3970+
'read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks\n\n' +
3971+
' Previous render Next render\n' +
3972+
' ------------------------------------------------------\n' +
3973+
'1. undefined useEffect\n' +
3974+
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
3975+
]);
3976+
});
3977+
});
3978+
3979+
it('unmount effect', () => {
3980+
function App(props) {
3981+
if (props.showMore) {
3982+
useEffect(() => {
3983+
Scheduler.unstable_yieldValue('Mount A');
3984+
return () => {
3985+
Scheduler.unstable_yieldValue('Unmount A');
3986+
};
3987+
}, []);
3988+
}
3989+
3990+
return null;
3991+
}
3992+
3993+
ReactNoop.render(<App showMore={true} />);
3994+
expect(Scheduler).toFlushAndYield(['Mount A']);
3995+
3996+
ReactNoop.render(<App loadA={false} />);
3997+
expect(() => {
3998+
expect(Scheduler).toFlushAndThrow(
3999+
'Rendered fewer hooks than expected. This may be caused by an ' +
4000+
'accidental early return statement.',
4001+
);
4002+
});
4003+
});
4004+
4005+
it('unmount additional effects', () => {
38914006
function App(props) {
38924007
useEffect(() => {
38934008
Scheduler.unstable_yieldValue('Mount A');

0 commit comments

Comments
 (0)