Why Do React Hooks Rely on Call Order?
overreacted.io
overreacted.io
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.
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.
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.
Custom widgets? Amount of work to get them running in one of these is a nightmare.
Compare that to game engines such as Unity or UDK and their editors, it's so bad it's laughable.
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.
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.
If you haven’t tried Mobx it is definitely worth saving yourself thousands of lines of boilerplate in your next project.
( 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.
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.
It's the same implicit call order you have when a language runtime allocate local variable on stack.
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.
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.
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.
[0] https://redux.js.org/faq/organizing-state#do-i-have-to-put-a...
[1] https://redux.js.org/introduction/three-principles
[2] https://github.com/reduxjs/redux/issues/2590
[3] https://docs.google.com/forms/d/e/1FAIpQLSfzIkY3fXZ8PrQKScYM...
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
There's nothing wrong with implicit call order when you only call it once.
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.
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.
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.
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.
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.
(Don't forget effects would also need keys.)
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.
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.
Edit: Flaw #5, not #7
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.
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.
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).
Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
the example would become something like:
const nameNamespace = [Symbol()];
const surnameNamespace = [Symbol()];
const name = useFormInput(nameNamespace);
const surname = useFormInput(surnameNamespace);
const valueKey = Symbol();
function useFormInput(namespace) {
const [value, setValue] = useState(extendNamespace(namespace, valueKey));
return {
value,
onChange(e) {
setValue(e.target.value);
},
};
}
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. (defn my-component []
(let [user-data (data/pull! :user "{ user { firstName } }")]
(fn []
(if (:loading @user-data)
[:div "Loading..."]
[:div "Hello, "
(get-in @user-data [:data :user firstName]) "!"])))
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
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.
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.
Example:
function useCount() {
const [count, setState] = useState(0)
return { count, increment: () => setState(count + 1)};
}
function useCountPlusOne() {
const {count: baseCount, increment} = useCount()
return {count: baseCount + 1, increment}
}
function MyHookComponent() {
const { count, increment } = useCountPlusOne()
return ...
}
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*' conventionIt'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).
Thanks for the article :)
It's probably not a good idea for Production code.
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
In practice we haven't seen this to cause confusion from people who actually tried this proposal for more than a few hours.
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.
function MyReactComponent() {
const [
[width, setWidth],
[name, setName],
] = React.use(
[useWidth],
[useName, 'alice'],
);
return <div>{width} {name}</div>
}
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. class Form extends ReactishComponent {
name = this.useState('Mary')
surname = this.useState('Poppins');
width = this.useState(window.innerWidth);
constructor () {
this.useEffect(() => {
const handleResize = () => this.width.set(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
})
}
handleNameChange = e => this.name.set(e.target.value)
handleSurnameChange = e => this.surname.set(e.target.value)
render () {
return (
<>
<input value={this.name.get()} onChange={this.handleNameChange} />
<input value={this.surname.get()} onChange={this.handleSurnameChange} />
<p>Hello, {this.name.get()} {this.surname.get()}</p>
<p>Window width: {this.width.get()}</p>
</>
)
}
}Thanks for feedback though, we’re listening.
https://gist.github.com/sebastiaanvisser/72e6bc54baa14abc08e...
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...
Note they need to be always up-to-date and not just execute once.
Note, I don't really know what these springs are, but derived hooks could work like this:
class MyComp extends ReactishComponent {
fast = this.use(new Spring({ pos: [0, 0], config: "fast" })) // prim hook
slow = this.use(this.fast.map(spring => ({ pos: spring.pos, config: "slow" }))) // functor map
mid = this.use(combineTwoHooks((fast, slow) => computeMiddle(fast, slow), this.fast, this.slow)) // applicative
render() {
return (
<>
<Point pos={this.fast.get().pos} />
<Point pos={this.slow.get().pos} />
<Point pos={this.mid.get().pos} />
</>
)
}
}
The trick is functions that convert hooks into new hooks. 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).
function useWindowWidth() {
const [[width, setWidth], stateId] = debug(useState);
assert(stateId === "useWindowWidth/state/:0");
useEffect(() => { ... });
const [_, effId] = debug(useEffect, () => { ... });
assert(effId === 'useWindowWidth/effect/:1");
return width;
}
This will definitely help for nested custom hooks..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?
https://github.com/reactjs/rfcs/pull/68#issuecomment-4393148...
Thanks!
Sure strings would have collisions, but symbols wouldn't.
Maybe a typo, thanks for the heads up :)
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.
[0]: https://news.ycombinator.com/item?id=18640612 (and the blogpost in the answer didn't really answer it)
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.
Passing a Symbol to custom Hook from outside also doesn't work because a custom Hook may have more than one state.
Try to convert the `useSubscription` example to your proposed API (and don't forget effects would also need "IDs") and you'll see what I mean.
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.
And while I trust the React teams judgement, I teach people React and they often question the "why".
Hope you’ll enjoy reading it.
Thank you for the time and effort you're putting into this.
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.
A tough nut to crack.
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.
https://gist.github.com/politician/5f03c169a4119a63abb785b1c...
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.
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.
The best I can come up with is Sweet.js (hygenic macros), but can you imagine what outrage that would provoke?
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.
Basically, lint, but at transpile time.
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?
React has always been about taking useful ideas from functional programming and bringing it to mainstream through pragmatic choices in JS.
Your concern about the "globalness" is addressed in Sebastian's comment which is linked five times throughout the post — you should definitely check it out! https://github.com/reactjs/rfcs/pull/68#issuecomment-4393148...
Finally, don't forget you're comparing Hooks to classes. Those are hardly functional either.
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.
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.
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.
We’re very open to good technical arguments but this isn’t one.
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’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:
https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamo...
https://reactjs.org/blog/2016/07/13/mixins-considered-harmfu...
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.
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.
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.
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.
https://github.com/facebook/react/blob/master/packages/react...
React.memo() is all the more useful of a signal in the Hooks world.