-
-
Notifications
You must be signed in to change notification settings - Fork 11
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
perf: replace useState with useReducer #5
Conversation
follow up to quisido#3 (perf optimization) this avoids creating a new function on each render, the identity value for the dispatch function stays stable. [passing dispatcher down is encouraged](https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down) because of "ref stability". added some tests to guarantee void returns from function, as I had to do some coercion because dispatcher function is specific.
ced46c6
to
e24be04
Compare
src/use-force-update.ts
Outdated
return forceUpdate; | ||
}; | ||
const useForceUpdate: VoidFunctionCreator = () => ( | ||
useReducer(reducer, true)[1] as VoidFunction |
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.
It's not as short or memory efficient, but I think destructuring first to [ , dispatch ]
will make the repo more readable and inviting to contributors. f(x, y)[z]
looks like a lot of magic that may not be intuitive to learners.
Why is dispatch being overridden as a VoidFunction
? What is it's default return type?
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.
It's not as short or memory efficient, but I think destructuring first to
[ , dispatch ]
will make the repo more readable and inviting to contributors.f(x, y)[z]
looks like a lot of magic that may not be intuitive to learners.
💯% agreed.
Why is dispatch being overridden as a
VoidFunction
? What is it's default return type?
yeah, I saw that the assignment was a bit of a hack.
I'm fairly new to TypeScript, and don't know how to solve that.
It doesn't compile without overriding it.
the type is React.Dispatch<void>
(aka: (value: void) => void
).
Type 'Dispatch<void>' is not assignable to type 'VoidFunction'.
I can make the hook return this type, but that, I think, is exposing implementation details, and it's incompatible with VoidFunction
because it receives an argument.
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.
EDIT: We do want this changed if it receives an argument. I'll keep looking into the definition. The fact that action is defined as void should be enough for it to realize there is no argument.
We can replace VoidFunction
with Dispatch<void>
.
I think
import { Dispatch, useReducer } from 'react';
deleteVoidFunctionCreator
changeuseForceUpdate
's definition to be(): Dispatch<void>
This is subtle. Took me a moment to see the change. I really like it.
As much as I like unnecessarily short code, this is precisely why I didn't implement it. React behavior in the future isn't predictable enough to say that calling setState will always trigger a re-render. I wasn't super happy about toggling back and forth between booleans, because I feared a race condition for a shallow comparison, where it toggled twice before a re-render/animation frame, and React decides not to re-render because the state is now the same again. That isn't the case for how it's treated right now, but I didn't want to have to refactor for future releases. Oh well. People didn't like the integer thing. C'est la vie. |
e24be04
to
276c4fc
Compare
it was news to me too. I came about it, when I was writing a turn's out it's stable (I quick tested with a ref), then looked at the It's very subtle indeed. 🤓 |
return forceUpdate; | ||
const useForceUpdate: VoidFunctionCreator = () => { | ||
const [, dispatch] = useReducer(reducer, true); | ||
return dispatch as VoidFunction; |
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.
will wait on feedback regarding the as VoidFunction
, maybe there's a way around it.
no hurries to merge, it's not even a direct dependency of mine :P
I stumbled upon it while reading your use-react-router
.
Thanks for the PR. After merging, I did a little tampering to see what I could find. I had to use I really appreciate the extra eyes on the project. If you have any other ideas or concerns, feel free to share them. Thanks again! |
follow up to #3 (perf optimization)
also: bundle size will be even smaller (not that is a concern 🤣)
this avoids creating a new function on each render,
the identity value for the dispatch function stays stable.
passing dispatcher down is encouraged because of "ref stability".
added some tests to guarantee void returns from function,
as I had to do some coercion. (dispatcher function is too specific)
this is not a breaking change, and compatible with both alpha and stable react.
(the alpha lacks the third argument to
useReducer
, this doesn't use it.)update: regarding the other, even shorter, proposal:
the reducer proposed here seems more "reliable" as the
setState
will always returnundefined
.React may bail out in the future, if it doesn't already.
update 2: yeps, react broke the
() => useState()[1]
: