With the recent internal and open-source releases of Recoil we have received lots of useful feedback (thank you!). From these discussions we have prepared a terminology and hook name cleanup to help simplify and clarify concepts.
For this simplification the core state concept in Recoil would become just “Recoil State.” The Flow/TypeScript types and React hooks interface would be renamed to consolidate on RecoilState. Instead of using RecoilState vs RecoilValue to distinguish writeability, all types would just be RecoilState, with special read-only or writable variants.
Atoms and selectors continue to both be types of RecoilState, and the use of them from components should be indistinguishable. Atoms are built with the atom() factory and represent state managed by Recoil while selectors are built with the selector() factory and represent state derived from other Recoil state or external requests.
We do recognize that API change can be disruptive, so we want proceed carefully. For the most part we can support this change by simply supporting both the new and old hook names before later removing the deprecated hooks. We are also planning to explore a code-mod script to aid in migration.
The TypeScript and Flow types would be updated to reflect the unified “Recoil State” terminology. RecoilState<T> represents either a read-only or writeable state. Note that the read-only type has a covariant type parameter, which can be very helpful for interfaces. The writeable type could also be extended to support different read and write types for selector abstraction.
RecoilState<T> ← RecoilValue<T>RecoilStateReadOnly<+T> ← RecoilValueReadOnly<+T>RecoilStateWriteable<R, W = R> ← RecoilState<T>By unifying on the “Recoil State” terminology, we can then simplify the hook names as well:
useRecoilState()useState().RecoilStateWriteable<R,W> will return a tuple with the value and a setter callback.RecoilState<T> or RecoilStateReadOnly<T>, in which case it will return a tuple with just the read-only value and no setter callback.useRecoilValue() - No changeuseRecoilSetter() ← useSetRecoilState()RecoilStateWriteable<R,W> type.RecoilStateWriteable<R,W> state to set.useRecoilResetter() ← useResetRecoilState()RecoilStateWriteable<R,W> .useRecoilLoadable() ← useRecoilValueLoadable()Loadable without using Suspense.useRecoilStateLoadable() - DeprecateduseRecoilGetInfo_UNSTABLE() ← useGetRecoilValueInfo_UNSTABLE()useRecoilSnapshot()useGotoRecoilSnapshot()useRecoilTransactionObserver_UNSTABLE() - This hook may be deprecated in favor of just using useRecoilSnapshot().useRecoilCallback() - No changeuseRecoilBridgeAcrossReactRoots() - No changeisRecoilState() ← isRecoilValue() - Helper to determine if a variable holds a Recoil atom or selector type.Hey @drarmstr it's great to see this! This was actually one of the issues I was going to bring up, so it's nice to see that you're open to changing the naming system. I think the proposed names are better than the current ones (the current "value" vs. "state" is very confusing), but I think they could be improved further.
I think the issues are:
useRecoilState it takes a long time before you get to a unique aspect of the hook name to filter on.I was going to suggest a core set of hooks that looked like:
// The most basic, likely most often used.
[value, setValue, resetValue] = useRecoil(...)
// For just reading a value.
value = useRecoilValue(...)
// For just writing a value.
setValue = useRecoilSet(...)
// For just reseting a value.
resetValue = useRecoilReset(...)
This makes them easier to read quickly, is better for autocomplete, and I think it pretty clear as far as intuitively understanding what the more specific hooks help you with.
You could also do:
useRecoilRead
useRecoilWrite
useRecoilReset
And either way it would be easy to extend for the extended set of hooks you mentioned:
// Get info about an atom/selector.
info = useRecoilInfo(...)
// Get a loadable without suspending.
loadable = useRecoilLoadable(...)
useRecoilSnapshot(...)
useRecoilCallback(...)
...
I think given that Recoil is a library that is completely built around managing state, having to write "State" everywhere becomes really tedious for no real gain in specificity. Similarly you could even simplify:
isRecoil(...)
Looking at how nice and terse the built-in React hooks are (eg. useMemo, useCallback, useState, useReducer), it would be a shame not to have a similarly terse setup for something used as often as Recoil.
Great initiative!
I think given that Recoil is a library that is completely built around managing state, having to write "State" everywhere becomes really tedious for no real gain in specificity.
I agree, I don't think anyone is going to install Recoil and not know that it is about state. Of course there's the case where a team member installs Recoil in a project to manage state, and the other team members do not know of Recoil, but I'd argue that it befalls the person who installed it to explain to the others what it is and link to the documentation.
RecoilStateWriteable<R, W = R>←RecoilState<T>
Amazing! This means we'll be able to make a useReducer-like api. I think can will greatly simplify a lot of complex code wrapping existing selectors.
[value, setValue, resetValue] = useRecoil(...)
value = useRecoilValue(...)
setValue = useRecoilSet(...)
resetValue = useRecoilReset(...)
info = useRecoilInfo(...)
loadable = useRecoilLoadable(...)
useRecoilSnapshot(...)
useRecoilCallback(...)
These look the most comfortable to use.
curiously this would make recoil to name things closer to how Jotai named its hooks https://github.com/pmndrs/jotai/blob/0bfb19a0b2cc52242c7a9183021773c66f1b107a/docs/core.md https://github.com/pmndrs/jotai/blob/0bfb19a0b2cc52242c7a9183021773c66f1b107a/docs/utils.md
Hey @drarmstr it's great to see this! This was actually one of the issues I was going to bring up, so it's nice to see that you're open to changing the naming system. I think the proposed names are better than the current ones (the current "value" vs. "state" is very confusing), but I think they could be improved further.
I think the issues are:
- They are very verbose compared to the average hook. Which is not very nice when you consider that multiple of these could be used per component. It looks like the new names are actually more verbose overall too.
- They're not great in terms of autocomplete, since most of them start with
useRecoilStateit takes a long time before you get to a unique aspect of the hook name to filter on.- They're still not as clear to me as I think they could be for common concepts like "read", "write", "reset".
I was going to suggest a core set of hooks that looked like:
// The most basic, likely most often used. [value, setValue, resetValue] = useRecoil(...) // For just reading a value. value = useRecoilValue(...) // For just writing a value. setValue = useRecoilSet(...) // For just reseting a value. resetValue = useRecoilReset(...)This makes them easier to read quickly, is better for autocomplete, and I think it pretty clear as far as intuitively understanding what the more specific hooks help you with.
You could also do:
useRecoilRead useRecoilWrite useRecoilResetAnd either way it would be easy to extend for the extended set of hooks you mentioned:
// Get info about an atom/selector. info = useRecoilInfo(...) // Get a loadable without suspending. loadable = useRecoilLoadable(...) useRecoilSnapshot(...) useRecoilCallback(...) ...I think given that Recoil is a library that is completely built around managing state, having to write "State" everywhere becomes really tedious for no real gain in specificity. Similarly you could even simplify:
isRecoil(...)Looking at how nice and terse the built-in React hooks are (eg.
useMemo,useCallback,useState,useReducer), it would be a shame not to have a similarly terse setup for something used as often as Recoil.
This syntax is much cleaner imho.
Thank you for the feedback. Just using useRecoil() isn't very explicit on what the hook would do. We also toyed with the idea of the primary hook returning a tuple of the value, setter, and resetter, however we opted against this in order to maintain stronger parity with the React useState() API and reserve the flexibility to maintain symmetry in case React adds a third return value in that hooks return tuple. Using terms like Set vs Setter are misleading because the hook doesn't set anything itself, but instead provides a callback which can set. The intention for including RecoilState in the hook names was to be clear and explicit that those were the hooks that operate on RecoilState objects. But, feedback on the verbosity is noted and will be considered during the RFC period, thanks.
@drarmstr thanks for the response.
We also toyed with the idea of the primary hook returning a tuple of the value, setter, and resetter, however we opted against this in order to maintain stronger parity with the React
useState()API and reserve the flexibility to maintain symmetry in case React adds a third return value in that hooks return tuple.
Makes total sense, and resetting is likely uncommon enough that this won't really impact the current DX at all, so sounds good to me.
Using terms like
SetvsSetterare misleading because the hook doesn't set anything itself, but instead provides a callback which can set.
Fair enough, and not a huge difference in writing/reading, so also sounds good to me.
Just using
useRecoil()isn't very explicit on what the hook would do.
The intention for including
RecoilStatein the hook names was to be clear and explicit that those were the hooks that operate onRecoilStateobjects. But, feedback on the verbosity is noted and will be considered during the RFC period, thanks.
This I disagree with. If Recoil is a library for managing little atoms of state in a React application, it's _very_ sensible that the useRecoil hook allows you to get one of those atom states.
You'll find many hooks in the React ecosystem that are extra terse precisely to make them nice to use very often. For an example straight from React's core... we don't call it useMemoizedFunction, or even useMemoized, we call it useMemo.
We don't need to repeat "State" everywhere to remind us. Someone who knows nothing about Recoil is not going to immediately understand what useRecoilState is any faster than what useRecoil is. The term that needs learning is "recoil" either way, and this is a one-time learning experience for the user for a hook that could be used many hundreds of times throughout a codebase.
Not only that, but in the current API you never type RecoilState except when using the hook, so the parallelism isn't particularly strong anyways. If anything, why is it called RecoilState instead of RecoilAtom if it's returned by the atom() factory?
If parallelism is that important why not call it useAtom? That would surely be more immediately clear to a user.
What's the point of the "state" term at all? We already have "recoil" and "atom" which are made up terms that can mean whatever we want, and the vague "value" which makes sense here, why add the vague "state" to the mix which doesn't really make things any clearer? It just makes it easier to confuse the "value" and "state" terms throughout the API because there's not much difference between those two terms in people's minds.
The only thing I can think of is that "state" gives you the same term as useState from React's core. But the community has already internalized this hook and uses it all over the place now without needing to append State to every hook. Look at useBoolean from react-use for example.
Zooming out, on verbosity in general...
My experience with Facebook libraries is that because of Facebook's internal import/module requirements in their codebase (and maybe other reasons), Facebook APIs tend to be much more verbose than average—they start to feel Java-like. And because of this they often aren't as nice to learn or use as other competitor libraries, with the unfortunate result of them becoming less popular.
You can already see this happening with Recoil, and "cuter" alternatives like Jotai. Note that I'm not advocating for "cute", there are many places where Jotai's DX is subpar because it opts for cuteness over understandability. But extreme verbosity like useRecoilStateValue or useRecoilStateSetter for the most basic of use cases is going to really turn people off.
I've seen this happen with other Facebook libraries in the past: Relay had so much verbosity in its initial versions and was out-popularized by Apollo. Draft.js had an even more frustratingly verbose API which caused me to write Slate as an alternative—I'd have loved to never have needed to write Slate.
Please don't mistake verbosity for clarity! I really think good documentation + terse APIs (but not cute ones) is a better solution here.
On the point about resetting being rarely used. I think jotai does it
better that you have to opt-in into resettable atoms, otherwise you have to
always check if you got DefaultValue in every single one of your writable
selectors even if you don't care about it. It's not immediately obvious
(thankfully it's going to be caught by typescript) and I've seen even docs
forgetting to check for it.
On Mon, Dec 28, 2020, 19:41 Ian Storm Taylor notifications@github.com
wrote:
@drarmstr https://github.com/drarmstr thanks for the response.
We also toyed with the idea of the primary hook returning a tuple of the
value, setter, and resetter, however we opted against this in order to
maintain stronger parity with the React useState() API and reserve the
flexibility to maintain symmetry in case React adds a third return value in
that hooks return tuple.Makes total sense, and resetting is likely uncommon enough that this won't
really impact the current DX at all, so sounds good to me.Using terms like Set vs Setter are misleading because the hook doesn't
set anything itself, but instead provides a callback which can set.Fair enough, and not a huge difference in writing/reading, so also sounds
good to me.Just using useRecoil() isn't very explicit on what the hook would do.
The intention for including RecoilState in the hook names was to be clear
and explicit that those were the hooks that operate on RecoilState
objects. But, feedback on the verbosity is noted and will be considered
during the RFC period, thanks.This I disagree with. If Recoil is a library for managing little atoms of
state in a React application, it's very sensible that the useRecoil
hook allows you to get one of those atom states.You'll find many hooks in the React ecosystem that are extra terse
precisely to make them nice to use very often. For an example straight from
React's core... we don't call it useMemoizedFunction, or even useMemoized,
we call it useMemo.We don't need to repeat "State" everywhere to remind us. Someone who knows
nothing about Recoil is not going to immediately understand what
useRecoilState is any faster than what useRecoil is. The term that needs
learning is "recoil" either way, and this is a one-time learning experience
for the user for a hook that could be used many hundreds of times
throughout a codebase.Not only that, but in the current API you never type RecoilState except
when using the hook, so the parallelism isn't particularly strong anyways.
If anything, why is it called RecoilState instead of RecoilAtom if it's
returned by the atom() factory?If parallelism is that important why not call it useAtom? That would
surely be more immediately clear to a user.What's the point of the "state" term at all? We already have "recoil" and
"atom" which are made up terms that can mean whatever we want, and the
vague "value" which makes sense here, why add the vague "state" to the mix
which doesn't really make things any clearer? It just makes it easier to
confuse the "value" and "state" terms throughout the API because there's
not much difference between those two terms in people's minds.The only thing I can think of is that "state" gives you the same term as
useState from React's core. But the community has already internalized
this hook and uses it all over the place now without needing to appendState to every hook. Look at useBoolean from react-use for example.
Zooming out, on verbosity in general...
My experience with Facebook libraries is that because of Facebook's
internal import/module requirements in their codebase (and maybe other
reasons), Facebook APIs tend to be much more verbose than average—they
start to feel Java-like. And because of this they often aren't as nice to
learn or use as other competitor libraries, with the unfortunate result of
them becoming less popular.You can already see this happening with Recoil, and "cuter" alternatives
like Jotai. Note that I'm not advocating for "cute", there are many places
where Jotai's DX is subpar because it opts for cuteness over
understandability. But extreme verbosity like useRecoilStateValue or
useRecoilStateSetter for the most basic of use cases is going to really
turn people off.I've seen this happen with other Facebook libraries in the past: Relay had
so much verbosity in its initial versions and was out-popularized by
Apollo. Draft.js had an even more frustratingly verbose API which caused me
to write Slate as an alternative—I'd have loved to never have needed to
write Slate.Please don't mistake verbosity for clarity! I really think good
documentation + terse APIs (but not cute ones) is a better solution here.—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/facebookexperimental/Recoil/issues/804#issuecomment-751935616,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AAABW4Q72H2Z6UDOGPQYQALSXFFWVANCNFSM4VANRL4A
.
Not only that, but in the current API you never type RecoilState except when using the hook, so the parallelism isn't particularly strong anyways. If anything, why is it called RecoilState instead of RecoilAtom if it's returned by the atom() factory?
If parallelism is that important why not call it useAtom? That would surely be more immediately clear to a user.
We would have liked to just use the term Atom<> for the type as well, instead of RecoilState<>, and even worked through several proposals with this option. However, the state may either be an "atom" or a "selector". These are both nice established terms in the state management ecosystem, so we didn't want to deviate and use something like "stateAtom" and "derivedAtom," when the existing terms have better clarity: they are terser and better understood. That leaves us needing an umbrella term for the abstract type, which may be either. So far that is RecoilState<>.
On the point about resetting being rarely used. I think jotai does it
better that you have to opt-in into resettable atoms, otherwise you have to
always check if you gotDefaultValuein every single one of your writable
selectors even if you don't care about it. It's not immediately obvious
(thankfully it's going to be caught by typescript) and I've seen even docs
forgetting to check for it.
We also toyed with this, but didn't want to make a piece of state being either re-settable or not as a distinct option from writeable. That would have required more variants of the RecoilState type to fully enforce static type safety of their usage with hooks. Splitting the setter and resetter handler in the selector definition is an option, but ended up being more verbose in our code base usage and more cumbersome to pass-through to upstream state. And anyway, I think this situation of guarding against DefaultValue may become cleaner with another proposal for handling async atoms that's still in the works.
Thank you for the feedback on the length of the hooks during this RFC.
I think right way of looking at this is that there are no resettable atoms
in jotai, just resettable selectors. So if you want to store resettable
state for temperature, you have base atom that stores numbers and writable
selector that accepts numbers and resettable value that causes it to set
base atom to initial value. And you can only reset selectors that accept
reset value (new DefaultValue ()) and you can make any selector
resettable if it has a type of DefaultValue | something. So inefficiency
of this approach is that you create two states for the same thing when you
want your atom to be resettable (it's more or less common approach in
jotai, even analog of useSetRecoilState creates new selector). But in
exchange you don't have to think about resettable state if you are not
using it :)
On Mon, Dec 28, 2020, 21:34 Douglas Armstrong notifications@github.com
wrote:
Not only that, but in the current API you never type RecoilState except
when using the hook, so the parallelism isn't particularly strong anyways.
If anything, why is it called RecoilState instead of RecoilAtom if it's
returned by the atom() factory?If parallelism is that important why not call it useAtom? That would
surely be more immediately clear to a user.We would have liked to just use the term Atom<> for the type as well,
instead of RecoilState<>, and even worked through several proposals with
this option. However, the state may either be an "atom" or a "selector".
These are both nice established terms in the state management ecosystem, so
we didn't want to deviate and use something like "stateAtom" and
"derivedAtom," when the existing terms have better clarity: they are terser
and better understood. That leaves us needing an umbrella term for the
abstract type, which may be either. So far that is RecoilState<>.On the point about resetting being rarely used. I think jotai does it
better that you have to opt-in into resettable atoms, otherwise you have to
always check if you got DefaultValue in every single one of your writable
selectors even if you don't care about it. It's not immediately obvious
(thankfully it's going to be caught by typescript) and I've seen even docs
forgetting to check for it.We also toyed with this, but didn't want to make a piece of state being
either re-settable or not as a distinct option from writeable. That would
have required more variants of the RecoilState type to fully enforce
static type safety of their usage with hooks. Splitting the setter and
resetter handler in the selector definition is an option, but ended up
being more verbose in our code base usage and more cumbersome to
pass-through to upstream state. And anyway, I think this situation of
guarding against DefaultValue may become cleaner with another proposal
for handling async atoms that's still in the works.Thank you for the feedback on the length of the hooks during this RFC.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/facebookexperimental/Recoil/issues/804#issuecomment-751952753,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AAABW4XIOBLFPMY6NYI26TTSXFS7VANCNFSM4VANRL4A
.
You'll find many hooks in the React ecosystem that are extra terse precisely to make them nice to use very often. For an example straight from React's core... we don't call it useMemoizedFunction, or even useMemoized, we call it useMemo.
We don't need to repeat "State" everywhere to remind us. Someone who knows nothing about Recoil is not going to immediately understand what useRecoilState is any faster than what useRecoil is. The term that needs learning is "recoil" either way, and this is a one-time learning experience for the user for a hook that could be used many hundreds of times throughout a codebase.
I strongly agree with all of these statements.
But extreme verbosity like useRecoilStateValue or useRecoilStateSetter for the most basic of use cases is going to really turn people off.
Especially this one and the following. Currently this is the best implementation of a global state library I've seen. It's by far the most simple. If it starts to be too verbose in naming conventions, I think it will not grow in popularity as quickly as it could.
My experience with Facebook libraries is that because of Facebook's internal import/module requirements in their codebase (and maybe other reasons), Facebook APIs tend to be much more verbose than average—they start to feel Java-like. And because of this they often aren't as nice to learn or use as other competitor libraries, with the unfortunate result of them becoming less popular.
I worked at Facebook and internally, all the code was SO verbose it was insane. Now, that was necessary because of their single codebase auto import functionality, but in this case we don't need the extra verbosity. The library is already simple enough to understand.
Please note the RFC has been updated to remove the State based on feedback.
Most helpful comment
Hey @drarmstr it's great to see this! This was actually one of the issues I was going to bring up, so it's nice to see that you're open to changing the naming system. I think the proposed names are better than the current ones (the current "value" vs. "state" is very confusing), but I think they could be improved further.
I think the issues are:
useRecoilStateit takes a long time before you get to a unique aspect of the hook name to filter on.I was going to suggest a core set of hooks that looked like:
This makes them easier to read quickly, is better for autocomplete, and I think it pretty clear as far as intuitively understanding what the more specific hooks help you with.
You could also do:
And either way it would be easy to extend for the extended set of hooks you mentioned:
I think given that Recoil is a library that is completely built around managing state, having to write "State" everywhere becomes really tedious for no real gain in specificity. Similarly you could even simplify:
Looking at how nice and terse the built-in React hooks are (eg.
useMemo,useCallback,useState,useReducer), it would be a shame not to have a similarly terse setup for something used as often as Recoil.