-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix race condition in useReactiveVar (#10072)
Co-authored-by: Ben Newman <[email protected]> Co-authored-by: Jerel Miller <[email protected]>
- Loading branch information
1 parent
d414de7
commit 51045c3
Showing
3 changed files
with
78 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@apollo/client": patch | ||
--- | ||
|
||
Fixes race conditions in useReactiveVar that may prevent updates to the reactive variable from propagating through the hook. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,24 @@ | ||
import * as React from "react"; | ||
import type { ReactiveVar } from "../../core/index.js"; | ||
import { useSyncExternalStore } from "./useSyncExternalStore.js"; | ||
|
||
export function useReactiveVar<T>(rv: ReactiveVar<T>): T { | ||
const value = rv(); | ||
|
||
// We don't actually care what useState thinks the value of the variable | ||
// is, so we take only the update function from the returned array. | ||
const setValue = React.useState(value)[1]; | ||
|
||
// We subscribe to variable updates on initial mount and when the value has | ||
// changed. This avoids a subtle bug in React.StrictMode where multiple | ||
// listeners are added, leading to inconsistent updates. | ||
React.useEffect(() => { | ||
const probablySameValue = rv(); | ||
if (value !== probablySameValue) { | ||
// If the value of rv has already changed, we don't need to listen for the | ||
// next change, because we can report this change immediately. | ||
setValue(probablySameValue); | ||
} else { | ||
return rv.onNextChange(setValue); | ||
} | ||
}, [value]); | ||
|
||
return value; | ||
return useSyncExternalStore( | ||
React.useCallback( | ||
(update) => { | ||
// By reusing the same onNext function in the nested call to | ||
// rv.onNextChange(onNext), we can keep using the initial clean-up function | ||
// returned by rv.onNextChange(function onNext(v){...}), without having to | ||
// register the new clean-up function (returned by the nested | ||
// rv.onNextChange(onNext)) with yet another callback. | ||
return rv.onNextChange(function onNext() { | ||
update(); | ||
rv.onNextChange(onNext); | ||
}); | ||
}, | ||
[rv] | ||
), | ||
rv, | ||
rv | ||
); | ||
} |