Skip to content

Commit b5bd7c8

Browse files
committed
Pass Suspense config to startTransition
...instead of `useTransition`. This avoids the problem of having to bind the config object to `startTransition`, invalidating downstream memoizations.
1 parent 35280c5 commit b5bd7c8

File tree

3 files changed

+64
-87
lines changed

3 files changed

+64
-87
lines changed

packages/react-reconciler/src/ReactFiberHooks.js

Lines changed: 34 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,7 @@ export type Dispatcher = {
9494
props: Object,
9595
): ReactEventResponderListener<E, C>,
9696
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T,
97-
useTransition(
98-
config: SuspenseConfig | void | null,
99-
): [(() => void) => void, boolean],
97+
useTransition(): [(() => void) => void, boolean],
10098
};
10199

102100
type Update<S, A> = {
@@ -1172,50 +1170,32 @@ function updateDeferredValue<T>(
11721170
return prevValue;
11731171
}
11741172

1175-
function mountTransition(
1176-
config: SuspenseConfig | void | null,
1177-
): [(() => void) => void, boolean] {
1173+
function startTransition(setPending, callback, config) {
1174+
setPending(true);
1175+
Scheduler.unstable_next(() => {
1176+
const previousConfig = ReactCurrentBatchConfig.suspense;
1177+
ReactCurrentBatchConfig.suspense = config === undefined ? null : config;
1178+
try {
1179+
setPending(false);
1180+
callback();
1181+
} finally {
1182+
ReactCurrentBatchConfig.suspense = previousConfig;
1183+
}
1184+
});
1185+
}
1186+
1187+
function mountTransition(): [(() => void) => void, boolean] {
11781188
const [isPending, setPending] = mountState(false);
1179-
const startTransition = mountCallback(
1180-
callback => {
1181-
setPending(true);
1182-
Scheduler.unstable_next(() => {
1183-
const previousConfig = ReactCurrentBatchConfig.suspense;
1184-
ReactCurrentBatchConfig.suspense = config === undefined ? null : config;
1185-
try {
1186-
setPending(false);
1187-
callback();
1188-
} finally {
1189-
ReactCurrentBatchConfig.suspense = previousConfig;
1190-
}
1191-
});
1192-
},
1193-
[config, isPending],
1194-
);
1195-
return [startTransition, isPending];
1189+
const hook = mountWorkInProgressHook();
1190+
const start = (hook.memoizedState = startTransition.bind(null, setPending));
1191+
return [start, isPending];
11961192
}
11971193

1198-
function updateTransition(
1199-
config: SuspenseConfig | void | null,
1200-
): [(() => void) => void, boolean] {
1201-
const [isPending, setPending] = updateState(false);
1202-
const startTransition = updateCallback(
1203-
callback => {
1204-
setPending(true);
1205-
Scheduler.unstable_next(() => {
1206-
const previousConfig = ReactCurrentBatchConfig.suspense;
1207-
ReactCurrentBatchConfig.suspense = config === undefined ? null : config;
1208-
try {
1209-
setPending(false);
1210-
callback();
1211-
} finally {
1212-
ReactCurrentBatchConfig.suspense = previousConfig;
1213-
}
1214-
});
1215-
},
1216-
[config, isPending],
1217-
);
1218-
return [startTransition, isPending];
1194+
function updateTransition(): [(() => void) => void, boolean] {
1195+
const [isPending] = updateState(false);
1196+
const hook = updateWorkInProgressHook();
1197+
const start = hook.memoizedState;
1198+
return [start, isPending];
12191199
}
12201200

12211201
function dispatchAction<S, A>(
@@ -1553,12 +1533,10 @@ if (__DEV__) {
15531533
mountHookTypesDev();
15541534
return mountDeferredValue(value, config);
15551535
},
1556-
useTransition(
1557-
config: SuspenseConfig | void | null,
1558-
): [(() => void) => void, boolean] {
1536+
useTransition(): [(() => void) => void, boolean] {
15591537
currentHookNameInDev = 'useTransition';
15601538
mountHookTypesDev();
1561-
return mountTransition(config);
1539+
return mountTransition();
15621540
},
15631541
};
15641542

@@ -1670,12 +1648,10 @@ if (__DEV__) {
16701648
updateHookTypesDev();
16711649
return mountDeferredValue(value, config);
16721650
},
1673-
useTransition(
1674-
config: SuspenseConfig | void | null,
1675-
): [(() => void) => void, boolean] {
1651+
useTransition(): [(() => void) => void, boolean] {
16761652
currentHookNameInDev = 'useTransition';
16771653
updateHookTypesDev();
1678-
return mountTransition(config);
1654+
return mountTransition();
16791655
},
16801656
};
16811657

@@ -1787,12 +1763,10 @@ if (__DEV__) {
17871763
updateHookTypesDev();
17881764
return updateDeferredValue(value, config);
17891765
},
1790-
useTransition(
1791-
config: SuspenseConfig | void | null,
1792-
): [(() => void) => void, boolean] {
1766+
useTransition(): [(() => void) => void, boolean] {
17931767
currentHookNameInDev = 'useTransition';
17941768
updateHookTypesDev();
1795-
return updateTransition(config);
1769+
return updateTransition();
17961770
},
17971771
};
17981772

@@ -1917,13 +1891,11 @@ if (__DEV__) {
19171891
mountHookTypesDev();
19181892
return mountDeferredValue(value, config);
19191893
},
1920-
useTransition(
1921-
config: SuspenseConfig | void | null,
1922-
): [(() => void) => void, boolean] {
1894+
useTransition(): [(() => void) => void, boolean] {
19231895
currentHookNameInDev = 'useTransition';
19241896
warnInvalidHookAccess();
19251897
mountHookTypesDev();
1926-
return mountTransition(config);
1898+
return mountTransition();
19271899
},
19281900
};
19291901

@@ -2048,13 +2020,11 @@ if (__DEV__) {
20482020
updateHookTypesDev();
20492021
return updateDeferredValue(value, config);
20502022
},
2051-
useTransition(
2052-
config: SuspenseConfig | void | null,
2053-
): [(() => void) => void, boolean] {
2023+
useTransition(): [(() => void) => void, boolean] {
20542024
currentHookNameInDev = 'useTransition';
20552025
warnInvalidHookAccess();
20562026
updateHookTypesDev();
2057-
return updateTransition(config);
2027+
return updateTransition();
20582028
},
20592029
};
20602030
}

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

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1977,16 +1977,18 @@ describe('ReactHooksWithNoopRenderer', () => {
19771977
it.experimental(
19781978
'delays showing loading state until after timeout',
19791979
async () => {
1980+
const SUSPENSE_CONFIG = {
1981+
timeoutMs: 1000,
1982+
};
1983+
19801984
let transition;
19811985
function App() {
19821986
const [show, setShow] = useState(false);
1983-
const [startTransition, isPending] = useTransition({
1984-
timeoutMs: 1000,
1985-
});
1987+
const [startTransition, isPending] = useTransition();
19861988
transition = () => {
19871989
startTransition(() => {
19881990
setShow(true);
1989-
});
1991+
}, SUSPENSE_CONFIG);
19901992
};
19911993
return (
19921994
<Suspense
@@ -2043,17 +2045,19 @@ describe('ReactHooksWithNoopRenderer', () => {
20432045
it.experimental(
20442046
'delays showing loading state until after busyDelayMs + busyMinDurationMs',
20452047
async () => {
2048+
const SUSPENSE_CONFIG = {
2049+
busyDelayMs: 1000,
2050+
busyMinDurationMs: 2000,
2051+
};
2052+
20462053
let transition;
20472054
function App() {
20482055
const [show, setShow] = useState(false);
2049-
const [startTransition, isPending] = useTransition({
2050-
busyDelayMs: 1000,
2051-
busyMinDurationMs: 2000,
2052-
});
2056+
const [startTransition, isPending] = useTransition();
20532057
transition = () => {
20542058
startTransition(() => {
20552059
setShow(true);
2056-
});
2060+
}, SUSPENSE_CONFIG);
20572061
};
20582062
return (
20592063
<Suspense
@@ -2113,13 +2117,15 @@ describe('ReactHooksWithNoopRenderer', () => {
21132117
);
21142118

21152119
it.experimental('always returns the same startTransition', async () => {
2120+
const SUSPENSE_CONFIG = {
2121+
busyDelayMs: 1000,
2122+
busyMinDurationMs: 2000,
2123+
};
2124+
21162125
let transition;
21172126
function App() {
21182127
const [step, setStep] = useState(0);
2119-
const [startTransition, isPending] = useTransition({
2120-
busyDelayMs: 1000,
2121-
busyMinDurationMs: 2000,
2122-
});
2128+
const [startTransition, isPending] = useTransition();
21232129
// Log whenever startTransition changes
21242130
useEffect(
21252131
() => {
@@ -2130,7 +2136,7 @@ describe('ReactHooksWithNoopRenderer', () => {
21302136
transition = () => {
21312137
startTransition(() => {
21322138
setStep(n => n + 1);
2133-
});
2139+
}, SUSPENSE_CONFIG);
21342140
};
21352141
return (
21362142
<Suspense fallback={<Text text="Loading..." />}>
@@ -2175,12 +2181,12 @@ describe('ReactHooksWithNoopRenderer', () => {
21752181
});
21762182

21772183
it.experimental(
2178-
'can update suspense config (without changing startTransition)',
2184+
'can pass different suspense configs per transition',
21792185
async () => {
21802186
let transition;
21812187
function App({timeoutMs}) {
21822188
const [step, setStep] = useState(0);
2183-
const [startTransition, isPending] = useTransition({timeoutMs});
2189+
const [startTransition, isPending] = useTransition();
21842190
// Log whenever startTransition changes
21852191
useEffect(
21862192
() => {
@@ -2189,9 +2195,12 @@ describe('ReactHooksWithNoopRenderer', () => {
21892195
[startTransition],
21902196
);
21912197
transition = () => {
2192-
startTransition(() => {
2193-
setStep(n => n + 1);
2194-
});
2198+
startTransition(
2199+
() => {
2200+
setStep(n => n + 1);
2201+
},
2202+
{timeoutMs},
2203+
);
21952204
};
21962205
return (
21972206
<Suspense fallback={<Text text="Loading..." />}>

packages/react/src/ReactHooks.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,9 @@ export function useResponder(
161161
return dispatcher.useResponder(responder, listenerProps || emptyObject);
162162
}
163163

164-
export function useTransition(
165-
config: ?Object,
166-
): [(() => void) => void, boolean] {
164+
export function useTransition(): [(() => void) => void, boolean] {
167165
const dispatcher = resolveDispatcher();
168-
return dispatcher.useTransition(config);
166+
return dispatcher.useTransition();
169167
}
170168

171169
export function useDeferredValue<T>(value: T, config: ?Object): T {

0 commit comments

Comments
 (0)