-
Notifications
You must be signed in to change notification settings - Fork 42
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 incorrect type definitions in useCallback #97
Conversation
@cknitt is there a better way than using all these versions? |
@mununki Ooops! Thanks for the fix! Maybe something like external useCallback6: ('callback, ('a, 'b, 'c, 'd, 'e, 'f)) => 'callback = "useCallback" would be nicer than external useCallback6: ('f, ('a, 'b, 'c, 'd, 'e, 'f0)) => 'f = "useCallback" though? |
This reminds me of something: do we really need to use useCallback0-7 to make the number of dependent arrays type safe? |
I was toying with the idea of introducing an abstract type for dependency arrays so that you would write React.useCallback(() => myCallback(a, b, c, d), React.deps4(a, b, c, d))
React.useEffect(() => myEffect(a), React.dep(a))
React.useMemo(() => myFunction(a, b), React.deps2(a, b)) Not sure if that would be an improvement though... |
React.useCallback(_ => f, [a,b,c]) Ah, I'd like to draw my idea. We have to use the tuple, not array for different types of items. |
Well if you have deps, then it can as well take a generic |
This will only work if a, b, c have the same type. |
The array forces you to inject everything into a single type, which complicates things. |
The bindings are trying to enforce that the dependency array is in fact an array. If we just type the dependency array as React.useCallback(() => myCallback(a, b, c, d), (a, b, c, d))
React.useEffect(() => myEffect(a), [a])
React.useMemo(() => myFunction(a, b), (a, b)) |
But that would be less safe and allow people to pass React.useEffect(() => myEffect(a), a) instead of React.useEffect(() => myEffect(a), [a]) for example. |
Is there any serious risk of passing something that is not allowed i.e. is not picked up, or is it pretty clear this is low risk in practice? |
Is there any change to improve using unboxed variant instead |
React team actually working on some compiler for it (codename: Forget) So.. we may end up needing PPX or something to build equivalents. |
E.g. one possible mistake would be to pass a list, but I'm not sure we want to worry about that. |
Maybe heterogeneous array for deps. |
Ok, what happens if I pass for example a number instead of an array is that I get this warning:
|
Currently, in addition to the useCallback1, 2, ... we have this binding @module("react")
external useCallback: 'f => 'f = "useCallback" which does not really make sense, as without a dependency array nothing is memoized. So we could just change that one to @module("react")
external useCallback: ('f, 'deps) => 'f = "useCallback" so that we have a less safe, but more convenient option in addition to the useCallback1, 2, ... |
How about removing the useCallback? It seems to me that useCallbacks0-7 are the ones that guarantee there are deps. |
I've tried to define a heterogeneous list something like: type rec dep<'b, 'c> =
| Nil: dep<'b, 'b>
| Cons('a, dep<'b, 'c>): dep<'a, 'b>
let x = Cons(1, Cons("a", Cons(1.5, Nil))) But it doesn't compile with |
|
Same goes to |
Same thing for all three, we have two options:
I understood @cristianoc that he would like something like option 2 as we are not too worried about errors here:
Usage would be like this: React.useCallback(() => myCallback(a, b, c, d), (a, b, c, d))
React.useEffect(() => myEffect(a), [a])
React.useMemo(() => myFunction(a, b), (a, b)) |
Ah, sorry, I see you implemented that already, but removed the existing 0-7. I would suggest to keep them for compatibility with existing projects and maybe deprecate them later on. |
This reverts commit f1422b4.
Then I'd rather have two separate bindings. In any case, we need to keep in mind that I think we should just have external useEffect: (@uncurry (unit => option<unit => unit>), 'deps) => unit = "useEffect" as suggested and not even document case 1 (how to pass undefined) at all. |
I know this is mostly unused, but I think it's still worth mentioning in the documentation, especially compared to cases like useCallback or useMemo where the second argument is absolutely necessary. |
Perhaps keeping the binding as simple as possible and put all the required info in the doc comment, could be the way. |
We can also define something like type runEffectOnEveryRender
@val
external runEffectOnEveryRender: runEffectOnEveryRender = "undefined" giving us the following options to document:
|
Since we've already adopted simplicity and flexibility, what about the documentation explains to use |
That's certainly an option, I am just afraid that people might start to confuse the |
I think the tradeoffs are clear. It looks like the case of omitting the second arg cannot be left out without making things worse (People reading the React docs will follow what is recommended). So the only question is how to represent that case out of the ways considered. |
"If you want to skip the effect on initial render and run only on updates, you could use a ref to track if it's the first render" I'm not sure I know what to say about this |
I've tried a rough implementation of heterogeneous array for deps expression: |
Oh, there is v11 for playground. I've tried an implementation using untagged variant: |
To summarize, we seem to be at 3 options.
|
Choosing one of the three options you provided would depend on several criteria from the user's perspective. Here are some important factors to consider:
In conclusion, all these options have their advantages and trade-offs. The ideal choice would depend on the specific needs, preferences, and the level of experience of your team. |
Is 2 the same as 1 with one extra function that one might or might not use? |
Indeed. We can rewrite as below:
|
I'd say let's prioritize "Knowledge Transfer from React" and "Learning Curve" and go with 1 or 2. If you prefer 1, I am fine with it as long as we make it clear in the documentation what the difference between |
Let's pick 2! In my opinion, option 2 is most balanced 😄 . If so, does the name make sense to you? |
Sounds like it's settled |
@@ -62,7 +62,8 @@ let arrayToList = a => { | |||
let pathParse = str => | |||
switch str { | |||
| "" | |||
| "/" => list{} | |||
| "/" => | |||
list{} |
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.
Why this format change?
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.
In my editor, vsc, it formats 🤔 The version of extension is v1.18.0
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.
Need to revert it?
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.
Ah, right, same for me. Not necessary to revert it, but this formatting is a bit weird, I will report it in the compiler repo.
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.
Ah no, sorry, it is correct that way, as the line above does not have a =>
. 🤦
Maybe better to rewrite in one line as | "" | "/" => list{}
.
Co-authored-by: Christoph Knittel <christoph@knittel.cc>
This fixes the incorrect type definitions of
React.useCallback7, 8
.The duplicated polymorphic type
'f
between the callback function and item in deps causes the type error.