-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix CM tearing #1455
fix CM tearing #1455
Changes from all commits
b986030
9c0520f
3d52f3a
647195d
d248d0a
eb6f8cf
6647836
5ed652c
16a6253
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import { useReducer, useRef, useMemo, useContext } from 'react' | ||
import { useRef, useMemo, useContext, useState } from 'react' | ||
import invariant from 'invariant' | ||
import { useReduxContext as useDefaultReduxContext } from './useReduxContext' | ||
import Subscription from '../utils/Subscription' | ||
|
@@ -13,7 +13,7 @@ function useSelectorWithStoreAndSubscription( | |
store, | ||
contextSub | ||
) { | ||
const [, forceRender] = useReducer(s => s + 1, 0) | ||
const [reduxState, setReduxState] = useState({ value: store.getState() }) | ||
|
||
const subscription = useMemo(() => new Subscription(store, contextSub), [ | ||
store, | ||
|
@@ -31,7 +31,7 @@ function useSelectorWithStoreAndSubscription( | |
selector !== latestSelector.current || | ||
latestSubscriptionCallbackError.current | ||
) { | ||
selectedState = selector(store.getState()) | ||
selectedState = selector(reduxState.value) | ||
} else { | ||
selectedState = latestSelectedState.current | ||
} | ||
|
@@ -53,10 +53,15 @@ function useSelectorWithStoreAndSubscription( | |
|
||
useIsomorphicLayoutEffect(() => { | ||
function checkForUpdates() { | ||
const newReduxState = store.getState() | ||
try { | ||
const newSelectedState = latestSelector.current(store.getState()) | ||
const newSelectedState = latestSelector.current(newReduxState) | ||
|
||
if (equalityFn(newSelectedState, latestSelectedState.current)) { | ||
setReduxState(prev => { | ||
prev.value = newReduxState | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @salvoravida As I said in other thread, I'm not confident this works for the future. Do you ever feel like just trying my previous snippet??? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For what i have understood, useRef is not CM safe. its a shared mem between branches. we need an useStateRef, a way to save data to current branch states queue. i dont' think that reading value from the future is ok in CM and event streaming, but i will try your idea asap. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line is mutating the existing state value, though, which is definitely not CM-safe. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no, because it is executed only on next update, before next render. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. on every branch, so every branch has it's own reduxState queue changes. this is why there is NO more tearing, even if with last test, that simulate this edge case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hm, so it's more or less like...: const [refState, forceUpdateRef] = React.useState({ current: null })
React.useImperativeHandle(refState, () => 'some value', [ ...optional_deps ]) Isn't this just like a short cut?, I mean, if keeping state in ref and just using const ref = useRef()
const forceUpdate = useReducer(c => c + 1, 0)[1]
somewhere {
ref.current = someValue
forceUpdate()
}
...
const [ref, forceUpdate] = useState({ current: null })
somewhere {
ref.current = someValue
forceUpdate({ ...ref })
} Both There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes but using ref, another rendering brahch could update the same value before or after, like shared mem between 3d. Instead states ref are duplicated on every branch and every state ref updater will work on its own branch rendering. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Idk, this is how I see it:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not following the discussion in deep, but:
My understanding is a bit different. We can cause interrupt with heavy computation without throwing promise. That's the whole point of my tests. P.S. Did I ever test using transition? Just today, I made new checks using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Never mind, ignore the above 🙈 |
||
return prev | ||
}) | ||
return | ||
} | ||
|
||
|
@@ -68,8 +73,7 @@ function useSelectorWithStoreAndSubscription( | |
// changed | ||
latestSubscriptionCallbackError.current = err | ||
} | ||
|
||
forceRender({}) | ||
setReduxState(() => ({ value: newReduxState })) | ||
} | ||
|
||
subscription.onStateChange = checkForUpdates | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
{ | ||
"plugins": [ | ||
"react-hooks" | ||
], | ||
"extends": [ | ||
"airbnb" | ||
], | ||
"env": { | ||
"browser": true | ||
}, | ||
"settings": { | ||
"import/resolver": { | ||
"node": { | ||
"extensions": [".js", ".ts", ".tsx"] | ||
} | ||
} | ||
}, | ||
"rules": { | ||
"react-hooks/rules-of-hooks": "error", | ||
"react-hooks/exhaustive-deps": "error", | ||
"react/jsx-filename-extension": ["error", { "extensions": [".js"] }], | ||
"react/prop-types": "off", | ||
"react/jsx-one-expression-per-line": "off", | ||
"import/prefer-default-export": "off", | ||
"no-param-reassign": "off", | ||
"default-case": "off", | ||
"arrow-body-style": "off", | ||
"no-plusplus": "off", | ||
"import/no-useless-path-segments": 0, | ||
"no-console": "off", | ||
"no-await-in-loop": "off", | ||
"arrow-parens": ["error", "as-needed", { "requireForBlockBody": true }], | ||
"react/jsx-fragments": "off" | ||
}, | ||
"overrides": [{ | ||
"files": ["__tests__/**/*"], | ||
"env": { | ||
"jest": true | ||
}, | ||
"rules": { | ||
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }] | ||
} | ||
}] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
*~ | ||
*.swp | ||
node_modules | ||
/dist |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
language: node_js | ||
node_js: | ||
- 8 | ||
- 10 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be CM friendly, could we do something like:
So we could do this, for example:
In React docs, it's using something like:
const value = resource.read()
where
read()
either returns the value or throws so it can bail out before the selector is set. Not sure if here is the best place to do it, but yeah, with CM not everything "thrown" is an error.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry i think that thow promise to comunicate with Suspense does NOT matter with useSelector, that has to select a state without tearing on render. that's another problem.