Beg to differ, yes, implicit call order will result in huge clusterfucks. React+Redux is already causing Frankenstein apps (which is not implicitly caused by those frameworks (ok, maybe except redux) but when you throw in react-redux, react-router, redux-thunk, etc. in the mix it just deteriorates quickly).
Well the NPM report showed us the trends. Every major fad peaks around 5 years in the making in JS land and then it fades away. We are halfway through. We just have to sit through the next 3-5 years.
You can write really messy confusing apps with lots of magic and indirection with anything, redux included, but redux by design enforces a single, mono directional and easily traceable chain of events whenever you fire an action. Whether you have 1 action or 100, this doesn't change. It's not, in my view, a pattern which works in the simple case but doesn't scale up.
Actually the complexity of understanding redux actions and their effects doesn't seem to change much at all from small to very large apps. This may come down to a well designed state tree (data model), or designing simple actions that don't try to do too much. There are actually big parallels with API design. It might even be the same problem.
So does a badly designed REST API mean REST as a concept is the cause? Of course not. In most cases, REST isn't even a limiting factor. It's just been badly designed and badly implemented. I think it's the same with blaming redux for a poorly designed, poorly implemented web app.
In actual use I've seen that Redux apps often wind up with a lot of accidental data dependencies that probably wouldn't have happened without a centralized store. For example devs will lazily use a "currentUser" key in the store for all kinds of unrelated stuff and subtle bugs creep in. Another common problem with Redux is memory issues because various components don't clear their data out of the store when they're unmounted.
Redux has some nice features but at this point I recommend people avoid it until they start to suffer from some of the problems it was designed to solve. You can get pretty far with a lot less complexity by use using local component state in vanilla React.
I absolutely agree with you and that's been my experience too, I've seen exactly those problems in both my own code and others' code. It took me personally many iterations over various projects to learn the boundaries of where redux makes more sense and where component state makes more sense.
That's perhaps the biggest actual inherent problem with redux, that it may implicitly encourage everything to be in the one single centralised store, for newcomers. That's a hard problem to solve, though I'm fairly certain Dan Abramov and the other maintainers have tried to make it clear that this, and using redux at all in simple apps, is usually a mistake.
The Redux FAQ specifically has an entry with rules of thumb to help decide when it makes sense to keep a given piece of state in Redux [0].
I do agree that the "Single Source of Truth" principle [1] is probably over-interpreted, and maybe needs some caveats somehow. That said, it's tough to simultaneously say "here's the basics of how Redux works", "here's the ideas behind why you _should_ use Redux", and also try to tell people when to _not_ use Redux.
We're currently planning a revamp of the Redux docs content [2]. I'd appreciate it if you could fill out this survey on how we can improve the docs structure [3], or leave a comment in that issue thread with some suggestions.
Yeah my approach now is to use Redux only for state that's shared among a bunch of components. Local state is fine and a lot simpler for things that aren't globally interesting.
You are implying that the causation goes like this: redux good -> so the app will be good too. And to make a case in point you are trying to make an argument that because many apps are poorly designed, everyone should use redux.
You really can't see how fundamentally broken is your argument? Really???
And you are not the only one who makes this HUGE fallacy.
All the redux/react fanboys are like you. At this point I already consider those people who make these arguments that they are just following/using redux because it's a religion for them.
Not to mention the fact that some people considers the whole uni-directional message flow a HUGE antipattern. See:
https://youtu.be/QM1iUe6IofM?t=1306
I'd love to debate the part about persistent call order but the rest of your comment has nothing to do with the content of my article and appears to be a generic rant.
Could you help me see how it would result in "huge clusterfucks" with an example?
Note React doesn't rely on particular call order. You can move your calls around any way you like. Just that it's persistent between re-renders.
Dan, I've seen this a few times already. I believe people are misunderstanding "persistent call order" to mean that they cannot change call order between versions of their app. I wonder if another name would help people understand this, maybe: "Non-conditional call order."?
That does better highlight that you are worried about conditionals. (It doesn't address the loop issuie though.) Hmm, here is another one. "Static call order"?
I guess what I'm saying and what I'm reading from the parent comment is that they are misunderstanding the name.
On the contrary: Redux's insistence on serializable events and side effect-free reducers makes debugging these pernicious "state update" ordering issues, which exist with or without Redux, a far easier job.
I don’t agree that state update ordering has ever been an issue, it’s a straw man to justify redux with. The correct solution here is to not design UI updates that can fail because of ordering! Mobx removes all the boilerplate and is far simpler to reason about and test in complex systems. Also async actions/side effects are built in, not third party.
If you haven’t tried Mobx it is definitely worth saving yourself thousands of lines of boilerplate in your next project.
if someone doesn't want to they don't need to write most of the Redux boilerplate, my last project a colleague wrote out all the boilerplate you normally see in a reducer - 100s of lines, and then I had to go write a new reducer of the same level of complexity and managed to write it with maybe 20 lines of Object.Assign and some values lookup.
For him he found my code harder to reason about because it was not explicit everywhere, but for me his was harder to reason about because I had to read the same thing over and over again with just different names.
But I wonder which one devtools would have handled easier, probably his.
Please check out our new `redux-starter-kit` package, which includes utilities to simplify common use cases like store setup, defining reducers, immutable update logic, and even creating entire "slices" of state automatically:
You have to concede though that once someone sets up the call order, it's really unlikely to change unless there are bugs or someone's coming to do some yak-shaving.
People just don't write productive programs where the instruction sequencing at the function level is non-deterministic or chaotic. The UI is already insane enough with CSS and browser differences fighting you every step of the way to make it worse with clever preambles.
When we deal with state full objects (classes instances/ database objects/ in memory caches) -- we have to segregate the operations on those objects as:
( 1 )
Operation can be applied multiple times -- result will not change (idempotant op) -- eg delete, select
( 2 )
Operation order is important (eg balance cannot increment before payment is processed )
( 3 ) operation order is not important, or ordering conflicts can be mitigated by the solution/underlying platform completely.
So what React team has realized, is that updating a state variable ( a change operation), often falls into ( 2 ).
As react getting more and more use, it tries to find more and more areas to optimize. Which is good.
In both time and ram-space dimensions...
For the React's run-time system, to perform global optimizations, it is really important to know what order the developer had implied in his/her design for case ( 2 ).
Very similar, to what an optmizing compiler, would like to know about a flow of calls/etc.
this is very difficult to do for a user level application in JS.
Without some help from the developer using the library, those optimizations cannot be done, or they will cause user's state to get corrupted.
Their solution is to keep the existing machinery as is, but allow a user to 'tell' the framework about the state variables a bit more than usual.
To me this this a fair tradeoff/ask.
So I will have to learn a bit more, and may be even restructure my code, to let React (and React Native) to do better global optimizations....
Why not ?!
Most programming languages syntax is poor as specifying the intended order of function invocation or value change rules, as 'compile-time' directive.
(well, I at least, do not know of any language that let's me do that -- so I resort to forcing some order though function arguments and return type, so that a call to nxt function, must have a certain type provided by a return of a previous function...
I think explicit state management, is absolutely the correct problem to tackle. it is difficult and error prone.
It is not fair, in my view, to judge React added complexity in this area, as 'deterioration'.
Ideally these types of things should have been solved by the compilers .. but javascript (and browser) ecosystem is probably 10-15 years away from even thinking about this.
That's a loaded question. What about comparing to the current day alternatives and upcoming ones? People have learned from react/redux and are pursuing alternatives.
Dont think anything was superior but that doesnt mean its perfect. It still amaze me how much time we have to spent(engineer) to get a frontend working. It really should be a completely or semi completely visual process.
It's always strange when people act indignant about the fact that engineering a large frontend is difficult, like the world owes it to them for it to be easy. Frontend code is by nature highly repetitive, but each different unit has its own arbitrary tweaks because of the fact that people have to use it. This makes it very hard to abstract.
There are a million visual frontend builders out there, and they are all terrible, because you can never get them to do what you want. They are only usable to build example apps, and if used for anything real, require a huge amount of hacking around which results in code that is worse than what it would have been otherwise.
Yep. Underestimating frontend complexity is how we got into this mess. Say what you will about framework churn, React is the first one where I really feel like it's easy to apply basic coding hygiene (e.g. treating abstraction and composition as first-order concerns).
>There are a million visual frontend builders out there, and they are all terrible, because you can never get them to do what you want.
I disagree. I had worked with Visual Basic and it had a great balance of ease of use and flexibility. If you are not obsessed with the feeling that you need to see the underlying code of the UI, you can have a great visual builder. Of course you need to code the event handlers, that's pretty much should be the coding required. I am talking about something like webflow + code for event handling.
>require a huge amount of hacking around which results in code that is worse than what it would have been otherwise.
that is because you haven't seen a great implementation yet. and as another commenter mentioned below, mobile UI builders are better because there is a uniformity in mobile design, while desktop (web) design lacks uniformity. It's possible.
While I agree with you, I think it's important to maybe specify that visual frontend builders generally suck -- for web frontends. In general, the visual UI builders for mobile apps tend to be very good.
The reason I think it's important to mention this is because I believe that the amount of variability you have to deal with in the web is far greater, which is why I believe these visual frontend builder systems tend to fall apart.
There's two layers to front end development. There is the visual layer and the arrangement of UI elements on the screen...and then there's application state management, routing, http request handling, data caching, inlining assets, etc that all come into play as well. UI builders might help you arrange elements on the screen but they do little to help with the latter except for minor cases like click through handlers.
When you declare a variable in your code, the variable is actually allocated a slot on the stack, which means there are implicit call orders when you declare a variable.
There's nothing wrong with implicit call order when you only call it once.
I think this post has actually pushed me back towards Symbol keys perhaps being a good idea. The useFormInput() example under Flaw 3 seems rather contrived – wouldn’t you just pass a Symbol key to useFormInput and it would then pass the key to useState, solving the supposed flaw? If you had to use useState several times in useFormInput, just use a WeakMap (they’re not that scary) with the keys being the Symbols passed to useFormInput. Or am I missing something in the explanation?
It's not really well explained in the article, but the argument against Symbols is that you (client code) have to store them somewhere for reuse in different calls.
That is that, you could do...
useState("someID")
...and somewhere else (* or in the same place but on a different call) again...
useState("someID")
...and this indeed refers to the same item. But using a Symbol, you need to first create it, store it somewhere and then use it. That is, you can't do this...
useState(Symbol("someID"))
...because this will fail through different repeated calls. Instead you'd need to first...
let someSymbol = Symbol("someID");
...and then...
useState(someSymbol)
Or, alternatively, use Symbol.for("someID"), which then has both problems: creating the symbol first and clashing of identifiers.
While the article does not explain this clearly, the example used alludes to this in an indirect way.
Personally I do think that this would be a more desirable sacrifice to make than restricting call order, but the React team thinks otherwise, it seems.
That’s not really all that different to everyone moving their CSS-in-JS and GraphQL queries out of a functional component body though, is it? This is kind of exactly the scenario Symbols seem to have been intended for.
Also... a nitpick, but `new Symbol` always throws a TypeError. And Symbol.for() is a useful escape hatch.
>That’s not really all that different to everyone moving their CSS-in-JS and GraphQL queries out of a functional component body though, is it?
I suggest you to take the `useSubscription` example from the "diamond problem" section and try to convert it to your proposed API. I think you'll see why it falls apart.
> Also... a nitpick, but `new Symbol` always throws a TypeError. And Symbol.for() is a useful escape hatch.
Yes, you're right. I was distracted with other stuff and meant just Symbol, without new. I'll fix it. Thanks.
As for the rest... Well, I don't really care much for React and many of the decisions they make. I don't like CSS-in-JS at all, and GraphQL... well, that one's nothing new.
>wouldn’t you just pass a Symbol key to useFormInput and it would then pass the key to useState, solving the supposed flaw?
Then `useFormInput()` wants to add another state (e.g. `isHovered`). How are you going to compose Symbols? We get to the next flaw (manual composition is annoying and error-prone).
I would’ve used a WeakMap with Symbol keys that mapped to a list or object containing Symbols.
Sorry if I’m just overlooking something obvious, but most of the examples that I’ve seen to explain why Symbol keys aren’t better than the current proposal seem to be just be examples of badly implemented custom hooks, not necessarily flaws with Symbol keys per se.
Can you convert the `useSubscription` example from the "diamond problem" section so we can compare "before" and "after"?
A key design goal is that creating a custom Hook is easy. You should be able to literally copy paste part of your component (e.g. a bunch of useState calls and some event handlers) and call it a day.
I'm struggling to see how what you're suggesting could be easy for the end user but maybe I'm missing something.
> A key design goal is that creating a custom Hook is easy. You should be able to literally copy paste part of your component (e.g. a bunch of useState calls and some event handlers) and call it a day.
I'd totally understand that reasoning, because the keyed Hooks are more verbose and would generally require two or three parts of a component to be copy-pasted – but the examples under Flaws #3 and #5 didn't make this clear (to me at least), and I hadn’t seen ‘ease of custom Hook implementation’ cited as an argument against keyed hooks before.
I’m really just playing devil’s advocate here, because in my playing around with Hooks I haven’t yet found a case where keyed Hooks are necessary, but I have accidentally put calls to useState() inside a conditional a heap of times.
It is definitely possible but there’s so many more places you could make a mistake if each custom Hook has to do bookkeeping like this. I don't know if I could write or extend the code you wrote without making quite a few mistakes in the process. By comparison, I find following a rule like "calls should be static" much simpler.
My post does mention that we care about copy paste experience:
>Code passing non-unique or badly composed keys would accidentally work until a Hook is called multiple times or clashes with another Hook. Worse, if it’s meant to be conditional (we’re trying to “fix” the unconditional call requirement, right?), we might not even encounter the clashes until later.
>Remembering to pass keys through all layers of custom Hooks seems fragile enough that we’d want to lint for that. They would add extra work at runtime (don’t forget they’d need to serve as keys), and each of them is a paper cut for bundle size. But if we have to lint anyway, what problem did we solve?
I later go into why allowing conditional declarations of state or effects isn’t even particularly useful or desirable because the semantics are too confusing. So I do think I kind of addressed that.
> It is definitely possible but there’s so many more places you could make a mistake if each custom Hook has to do bookkeeping like this. I don't know if I could write or extend the code you wrote without making quite a few mistakes in the process. By comparison, I find following a rule like "calls should be static" much simpler.
The book keeping could be moved into a couple of utility functions - it’d be largely the same for most custom hooks.
I’m also not sure relying on a linter is necessarily going to make static call Hooks simpler. Poorly written hooks are going to be buggy whether they’re Symbol keyed or not. I think that one of the big disadvantages of static call Hooks would seem to be that incorrect conditional usage could still accidentally work.
> My post does mention that we care about copy paste experience
I think I misunderstood that section when I first read it - it makes sense now. I’m not convinced it’s a huge win though.
> I later go into why allowing conditional declarations of state or effects isn’t even particularly useful or desirable because the semantics are too confusing.
We’ll have to agree to disagree - while I’m not eagerly wanting to use Hooks in conditionals, I dont think the semantics are that confusing.
Small, late technical digression that's been bugging me, but Symbols won't provide the behavior you'd expect from a WeakMap.
A) Symbols often get interned as a part of the modules that own them and may never be garbage collected in the lifetime of an app. Thus the items in the WeakMap may never expire (because modules themselves are currently rarely unloaded/garbage-collected).
B) Primitive data types are actually expressly prohibited from being WeakMap keys in the spec (to avoid issues like [A]), and proper implementations are expected to throw errors if you try. MDN expressly makes this clear that this means that Symbols are not allowed to be WeakMap keys:
> Primitive data types as keys are not allowed (e.g. a Symbol can't be a WeakMap key).
i think the problem with the naive symbol approach is you need to know all the keys the user hook wants to use and pass them all in which is not really scalable. however, instead of using a single symbol you could use an array of symbols which would solve that problem. a user hook would take in an array of symbols from its caller which would define its namespace then it would add a symbol to this namespace to create a new array for each nested call it wants to make.
there are a lot of drawbacks with doing it like this tho. like it is much less performant because you are creating all these arrays and having to do these comparisons across possibly big chains of symbols. also, there doesn't seem to be any way to easily store these chains in a map like structure for fast lookup except for using nested maps which is a bit weird.
I'm a bit surprised this pattern isn't more popular; having side-effectful functions return ratoms in a form-2 component seems almost as flexible as Hooks. It seems that most CLJS people try and keep their views more pure, which I think is honestly to their overall detriment. You miss out on the encapsulation and composition that React is espousing.
That being said, I intend to replace reagent soon with just raw React + a few helpers :P
> That being said, I intend to replace reagent soon with just raw React + a few helpers
Do you intend to keep using ClojureScript or use JavaScript/TypeScript? I read pure React with ClojureScript is rather painful (mostly due to the props conversion but maybe that's what your helpers are for?)
I am asking since I am still considering between React (JS/TS) and Reagent/Rum (CLJS) for a side project of mine.
Re: 'Flaw #6: We Still Need a Linter' (first example):
Could the 'primitive' hooks (useState, useEffect, etc) walk up `arguments.callee.caller.arguments.callee.caller...` grabbing function names until you hit a React function? Then use the names to create a 'composed' key automatically? It still doesn't solve the problem of a function using the same hook twice in one function, but it might solve the problem of collision across custom hooks.
Would give you a key of
`useState(useCount(useCountPlusOne(MyHookComponent)))` without the end user having to futz around composing the key manually. At this point you could probably even forego the 'use*' convention
It's still pretty magical, but the magic seems more abstracted. In general I've really liked hooks, and I'm willing to put up with the wackiness (although testing them with enzyme is a big PITA right now).
There are some big performance implications of using `arguments` (most current JITs heavily deoptimize functions that use `arguments`), and arrow functions in the spec are supposed to throw errors for any attempts to access their `arguments`.
It's probably not a good idea for Production code.
relying on call order does seem kind of scary but without knowing how people structure their code using hooks its hard to know how bad it would be. for example the situation with hooks is basically:
1) any function that calls a hook function has a red colour
2) any function that calls a red coloured function has a red colour
3) if you ever have ever call a red coloured function in a conditional branch then bad things are going to happen
if you have nested functions calling hooks then you can change the order of hooks without even realising hooks are being called which is dangerous. this 'nesting transparency' where callers aren't forced to know about the hook behaviour of their sub-functions is also used as defence in the blog for relying on call order. heh
The "color" you're talking about is the "use" naming convention that's enforced by the linter. So if you call a Hook, you're supposed to call your function `useSomething()`, and we consider it a Hook too.
In practice we haven't seen this to cause confusion from people who actually tried this proposal for more than a few hours.
My gut feeling tells me that introducing hooks is going to end badly.
If there's no patently obvious advantage but you have to rely either on convention or an additional pool of knowledge then most junior (or generally less skilled) developers cannot be trusted to use the given thing properly.
I've seen this happen with observables - sure you can do a lot of new stuff with them but they are only clearly more useful than say promises in a handful of cases.
Thid trend of producing tools which are powerful in the hands of the best but hard to use for beginners worries me. In the long run this makes development more expensive, not less.
admittedly, this is much less clean looking than the current proposal, but, in my mind at least, it makes it a bit more clear that you have to pass the functions in with a specific order at the top of the component.
I don't understand what exactly you're trying to solve by this (nothing prevents a user from making it conditional) but it definitely has flaw #7 (can't pass values between Hooks).
It all seems to boil down to packing and silently composing lifecycle methods. Either with classes, records of functions, or effectful functions, dictionaries.
I can see how the ergonomics of current react hooks are actually great, but I still think they’re weird :) I’ll probably get over it some day.
edit: note how hooks are now parametrized datatypes, probably allowing you to do all kinds of first class composition. Read only hooks are probably a functor and a monad...
With passing values between hooks do you mean "Make one hook depend on the value of another hook?". Because that seems like the functor 'map', or applicative 'apply'.
Note, I don't really know what these springs are, but derived hooks could work like this:
Dan, thanks for your work on React and all the new features from you and the team. I love your stewardship of this project. React is fantastic and I’ve loved your choices of features to add. If you could just make docs less verbose...
Hooks reminds me of tensorflow scopes a little bit, in python:
with tf.variable_scope("foo"):
with tf.variable_scope("bar"):
v = tf.get_variable("v", [1])
assert v.name == "foo/bar/v:0"
`v` tensor here will have a generated human readable unique name here: "foo/bar/v:0"
It seems with hooks, react uses call order to derive the unique "leaf" hook id for runtime resolving its implementations. However, it would be nice if react hooks can automatically provide a similar human readable "hook id" (even if only for dev/debug build).
Could something like this be used to move the "linting" that the react team recommends directly into react itself?
If you can abuse error stack traces to get the call stack, and some trickery to get the string representation of the component function that called the hook (following it through all intermediate custom hooks), you could then have the full text of the function body and know for sure that it's calling a Hook, and from there could run some linting on that internally and scream to the console if a hook is being used incorrectly.
It may have a pretty significant performance and possibly size overhead depending on how much code is needed to inspect the function body and actually do the parsing/linting, but removing the need for a linter ("need" might be too strong of a word?) would make it easier to get started with, and safer to use for developers who, like it or not, don't read docs fully, don't setup or use linters, or just want to throw something together with very little tooling. And obviously it would all be stripped from production builds.
Has the react team explored this idea? and are there reasons that I'm missing that it won't work or isn't ideal?
Sebastian’s comment (which I linked to throughout the post quite a few times) mentions we will probably do some DEV time validation with similar techniques. I really suggest to read it all — my post wasn’t intended to answer all questions.
My apologies! I was on mobile when I read it last night and the RFC links don't seem to go directly to the comment on my android device for some reason! (it loads the full list of comments, but never takes me to the correct one, and searching for sebastian didn't show any results, i guess because his username is the only thing that shows up)
> Flaw 2: One common suggestion is to let useState() accept a key argument (e.g. a string) that uniquely identifies a particular state variable within a component.
I tried asking about that earlier here on HN[0], nice to finally at least get an explanation. The tldr is name clashes when reusing hooks. A valid concern, I think they should be more upfront about the reasoning instead of "hooks are magic, don't use them in these ways". That would make it easier to accept.
Yes, and to some extent it is addressed in the link too (though not really well explained).
Symbols do not clash, but they need to be managed/stored by client code. i.e. you need to keep the original Symbol to use it in different calls, while you can use "different equal strings".
This is the argument in the article. Whether this is indeed more or less desirable than having order restrictions on calls, that's a different thing. I personally think it is indeed a better solution, but the React team seems to think it's not.
Couldn't a custom hook have a map of maps that keep track of this?
The first map gets the symbol that was passed to the custom hook as keys and the maps inside that map would use symbols only the custom hook knows about.
I think your solution is superior in that is more concise and I at least think I understand why you went that way. I'm just trying to understand why the symbol approach wouldn't work.
Yeah you could do this but this “breaks copy paste” (one of the flaws). We want creating a custom Hook to feel exactly like extracting a function. It should feel easy and you shouldn’t need to mess with maps and symbols.
Just want to say that I've been loving your posts so far. Well written and I love the perspective of exploring _why_ an API is designed the way it is instead of trying to focus on explaining why <insert library> is the best for <insert language>. There are only a handful of blogs I've read over my career that I feel like focus on solving real application development problems, exploring trade-offs, or digging into why an approach was taken and these posts are of that caliber. Those topics help me in the problems I encounter frequently in my day job and make me a better engineer.
Thank you for the time and effort you're putting into this.
I just briefly looked at the examples (and of course followed the twitter boom about it), but what I am not getting is that to me it looks like since useState() is against the principles of pure functional programming.
I understand that it implements some kind of inversion-of-control, but just looking at the code it feels like using some global object's method which is a big no-no. Also this importance of ordering reminds me the unmaintainable magic hell of Angular.
Maybe I'm just missing the "explicit-over-implicit" concept here. What's your opinion on this?
I suggest you to read about algebraic effects. We don't have them in JavaScript, but conceptually that's what Hooks represent. (This is as functional as it gets.)
React has always been about taking useful ideas from functional programming and bringing it to mainstream through pragmatic choices in JS.
It's not a global object—it's an instance of the object for the component you're using. The whole thing's a terrible OOP system in a language that already has a built-in mediocre OOP system, which terrible OOP system is, in the end, just a(n inefficient) pass-through to same built-in mediocre OOP system.
The posters here wondering why you can't name them are on to something. I assume it's because then it'd be too obvious that's what they're doing, and whoever's paying people on that team (I really hope it's not more than one person, it's not hard work, but it probably is) might notice and make them stop, and maybe the React team at FB would even shrink in size, and we can't have that.
I'm not sure what other explanation there could be for such comically-wasteful sandcastle building. I assume it's a combination of individual incentives to work on something not-difficult but flashy and prominent, with project incentives to never need fewer people than they currently have.
>The posters here wondering why you can't name them are on to something
Can’t name what? Not sure I follow.
>I really hope it's not more than one person, it's not hard work, but it probably is
We had from 5 to 8 people on the team at different times. Maybe maintaining one of the most popular open source projects isn’t “hard work” for you but we find it challenging.
>I'm not sure what other explanation there could be for such comically-wasteful sandcastle building.
We try to solve problems that product engineers run into. If you have better ideas we’d love to hear them.
>The posters here wondering why you can't name them are on to something
Imprecise phrasing on my part, the discussion here has been around naming hooks with Symbols.
> We had from 5 to 8 people on the team at different times. Maybe maintaining one of the most popular open source projects isn’t “hard work” for you but we find it challenging.
I meant React Hooks specifically, not Redux, which I assume is what you mean here.
> We try to solve problems that product engineers run into. If you have better ideas we’d love to hear them.
Use the built-in OOP system instead of writing a worse new one? Reinventing methods and properties with poor, misleading syntax as a thin layer over the OOP system of the host language is... well, it's helping bloggers, I guess.
I’d love to hear about your solutions but maybe don’t be so quick to dismiss other people’s hard work as something to “help bloggers”. If we were chatting face to face would you also behave like this?
We’re very open to good technical arguments but this isn’t one.
> If we were chatting face to face would you also behave like this?
Maybe? I usually very much would not, of course, but JS "culture" creates significant irritation and wasted time for me daily, and has for years, especially in React-land, since that's where the money is lately so it's hard to avoid. The only other software that gets me this exasperated is anything Poettering thinks up, but at least I don't personally have to work closely with any of that daily (any more, and for now).
I'm sure I'll have to deal with hooks when people around me start using them. It's less that I'm upset that they exist than I feel like I'm being gaslighted. I've read the docs, and the source, because, again, I have to know this stuff. After the initial disbelief wore off I had a good laugh, like, actual LOL. This thread is the first of many I at that moment predicted I'd see as people who really, really in their hearts and in their actual technical needs and in the language they're using, just needed OOP, but are on the JS-must-be-functional-at-all-costs hype train, expressing frustration and confusion over this feature and burning lots of time trying to sort out how OOP works when you're trying so hard to not type "this" and pretending it's something else. It's a less-useful and confusing replacement for OO that re-implements just enough of it that people will 100% for sure hang themselves with it, and omits enough that people will complain about it. It's a perfect device for generating confusion. It's going to be used for no benefit, or misused harmfully, a ton, and meanwhile everyone's gonna be very confused about it.
Ditto Redux—which is at least not funny in itself the way React Hooks are, though the flailing and consternation around it are—which is dead simple if you don't use the wrong words to describe things, yet how many person-hours have been lost trying to decipher what's going on there? So, so many. On the one hand it's funny, and I do legitimately enjoy all the accidental humor in React land. On the other I have to console, counsel, and train the folks who run into difficulty with this stuff, and live with or fix software in which it's been misunderstood and misused.
I empathize with your frustration of having to work with something you don’t like (or feel is unnecessary). I would much prefer that people who don’t like React aren’t forced to use it but that’s not how job market works. Sorry about this.
I’d love to see what OOP solution you envision for these problems. It’s not like we’re unfamiliar with OOP. In fact the OOP version of Hooks is what we had before. It’s called mixins. Mixins, like other forms of multiple inheritance, suffer from the “diamond problem” described in the post, and many others:
If you have an OOP solution that solves the same problems Hooks solve, but without the downsides of mixins, I’d love to hear it. You’re being vague in your proposed fix which makes it difficult to discuss.
I also want to emphasize we’re not “FP purists”. (In my opinion some codebases take FP way too far making the code very difficult to follow.) In many ways, Hooks help replace those heavy-handed patterns. So I think you might actually like them if you spend some time using them.
I entirely share your "ew, gross, no" re: mixins generally. That their being added to React in the first place is part of this history is definitely interesting.
Thing is, I don't even consider Hooks not OO. They're just a really limited in-JS partial re-implementation with bizarre syntax. React tracks your "this" for you so it can dispatch the calls correctly. Your constructor gets mushed around in your render function for some reason. But it's attaching properties and methods to an instance. It's going to require care and discipline to use it correctly, given its quirks, so just direct that same discipline toward composition-over-inheritance instead, is my thought, which you can do without yet another way to write things. The fix is quit hitting yourself, in short, but if you don't I guess we'll hand you another way to hit yourself, but differently? This is just one more layer of complication that everyone's now got to understand (or, more likely, not, but use anyway) to even read other people's React codebases.
Sorry but this is still pretty hand-wavy :-) I’d be happy to discuss a specific “before” and “after” code example.
There’s neither methods nor properties in Hooks code. I think you might be doing the same thing you think we are doing — you’re projecting the API you see onto the metaphors that feel more familiar to you. But these metaphors don’t really match what the API is doing or what it represents.
But again, this discussion is fruitless without specific code examples to anchor it.
> There’s neither methods nor properties in Hooks code. I think you might be doing the same thing you think we are doing — you’re projecting the API you see onto the metaphors that feel more familiar to you. But these metaphors don’t really match what the API is doing or what it represents.
I mean that Hooks implementation re-implements key elements of a typical implementation of methods and properties, and end up mimicking them in important ways, differing mainly in the parts of that it doesn't include. Not that it actually uses methods and properties (though it does, of course—the "current component" that React tracks is an object, and the Hooks code leans on that to determine its calling context).
> you’re projecting the API you see onto the metaphors that feel more familiar to you.
Place a typical OO implementation next to what Hooks are doing, and you don't even have to squint to see that they're quite close. The porcelain's super-weird, yes—magical in all the "wrong" places, explicit in all the "wrong" places, missing a ton of mostly-inheritance-related stuff—but even with all that a Render function using Hooks manages to look an awful lot like a class declaration, as if someone had implemented classes in a language but forgotten to add any of the syntax to support it.
EDIT: I mean, seriously. The source is public. It's a really fun read.
Looking at the design, it's clear that the team is trying really hard to make functions look and work like classes look. Variable declarations at the top, followed by helper methods, followed by lifecycle methods. Given that classes are themselves sugar to make prototypes palatable to a broad range of developers, it's certainly a tough design task; I'm not convinced that reliance on call-index is wrong per se, but it is magical (and magic in JavaScript is obnoxious).
That said, the presentation of the third and forth flaws seem a bit weak. useState accepts a key as the first parameter, but in both of the composite functions that input parameter disappears. That looks like a refactoring error.
From a design standpoint, if `useState(symbol)` is acceptable (and I'm not saying it is), then `useWindowWith(symbol)` would likewise be acceptable in that it's not adding any more requirements to the interface than the vanilla version.
Proper bookkeeping of symbols resolves the issues here. However, if the design objective is to reduce the effort on the developer to do their own bookkeeping, then call-order indexing does make sense.
(Not to mention that Symbols are not supported by IE11 which remains an important target for bigger companies.)
I'm not sure there's a good way out. When you look at solutions like Protocol Buffers, they just bite the bullet and require the developer to supply the indexing. If JavaScript had something like Go's iota, then you could imagine using an enumeration to supply the indexing without requiring everyone to type 1,2,3,4... etc. But it doesn't so, that's wishful thinking. Nevertheless, the react codebase itself does contain a giant list of assignments of Symbol() || number to constants, so it's a pattern you're already aware of.
>From a design standpoint, if `useState(symbol)` is acceptable (and I'm not saying it is), then `useWindowWith(symbol)` would likewise be acceptable in that it's not adding any more requirements to the interface than the vanilla version.
I think you're missing that `useWindowWidth()` could have more than one `useState()` and thus you'd need to somehow compose Symbols. Which is what the next section is about.
In particular, I would suggest to take the `useSubscription` example from "diamond problem" section and try to convert that whole snippet. You'll see where it falls apart.
I must be missing something because this didn't fall apart when I refactored it to work with a `useState(symbol, value)` interface. I'm not saying this is a good or ideal solution, indeed if the useWindowWidth hooks were to both useState and also useSubscription, then multiple keys would have to be injected -- it would work correctly, but the interface would have the AMD feel that perhaps is undesirable (per flaw #8).
EDIT: I still think it's a hard design problem and the concerns in flaw #8 are _very real_ to a broad range of developers. When you're just trying to get it done, proper bookkeeping of Symbols and injecting them into hooks composed of other hooks might cross the line. It's a shame that relying on call-order indexing is the solution because it's magical. But at the end of the day, engineering is about trade-offs. Time will tell whether deeply nested composition of automatically-managed hooks was a good feature to expose.
> It's a shame that relying on call-order indexing is the solution because it's magical.
I've been thinking about this a lot since Hooks were introduced, and I'm increasingly of the opinion that it isn't that magical. Order of operations is incredibly important in the functions we write, especially in a language like JS that makes no effort for strict "pure" side-effect free functions. The order of a console.log or a return versus an increment matters in JS. We write a lot of procedural code in JS where order matters (a lot) already.
In that matter, Hooks can just melt into the "procedural" background of JS.
That said, I still feel like I want a better solution than "lint errors" for things like accidental branches of a Hook. I don't have any more of a proposal for how that would work or what that would mean than the article here, though, unfortunately.
Yeah, macros/DSLs might be a bridge too far for a lot of users.
I keep trying to figure out if there is a way to push it into a type safety problem in Typescript, at least. A compile error would be preferable to a lint error, even if not everyone uses a typing compiler like Typescript.
If there were some way that you could get use* functions to narrow to `never` in any branch or branch-like position, that would be cool. Unfortunately, I can't think of a natural way in the type system to do that that would work in even a plurality of cases. Which drops back to it needing to be a context-sensitive linter rule.
You might be able to use ES6 annotations to mark use* functions such that a bespoke Babel plugin can assert that the the control flow AST only contains functions so annotated in non-branching positions.
Similarly, tslint operates best as a Typescript plugin and so lint rules in Typescript still can happen at transpile time and aren't necessarily that much different in practice from compiler errors. Still bugs me a little that there's not a higher-kinded type or pattern that I can come up with to do it "for real" in the compilation.
Enjoying your posts, keep them up! Hooks look like a very powerful API for sharing pieces of functionality between components. You've probably heard this one a lot, but what bothers me a bit about hooks (especially useState) is that before, when you saw a component defined as a function, you could assume that it was just a function that renders something based on its props. However, I think it's a fair trade-off for such a powerful API.
Recent versions of React have encouraged truly pure props-only functions to wrap them with React.memo(), which also adds the benefit of giving them the equivalent of a shallow-check `shouldComponentUpdate()` avoiding re-renders when the props are the same.
React.memo() is all the more useful of a signal in the Hooks world.
Well the NPM report showed us the trends. Every major fad peaks around 5 years in the making in JS land and then it fades away. We are halfway through. We just have to sit through the next 3-5 years.