> You basically stick to writing pure functional components focused only on how to render
If you’re using hooks like useState or useEffect I wouldn’t consider those functional components pure.
Hooks provide some nice syntactic sugar, which is not nothing, and make it easier to share logic between components. In my experience hooks have made my code easier to read, but they haven’t simplified it. The side effects and state might not be inlined but they’re still there. If anything they’ve made it less explicit because some of the details are hidden behind the magic that the React runtime uses to add state to functions.
> It makes purely functional frameworks like modern react much easier to deal with.
But hooks aren't pure functions. They aren't referentially transparent and have an implicit dependency on call order. Likewise, React components aren't "functional" the second you use any of these APIs which subscribe to effects, state, context, whatever coming from elsewhere. React is simply not a "pure functional" enterprise and it's strange to represent it as simply a "violation of some theoretical principles." Those principles are the entire basis of what constitutes functional programming, which is why the original parent described this as not-functional not-oo.
> Hooks are the place to segment out effectful code from otherwise pure code
I know what hooks are. Neither of those things is true. The hooks are called/instantiated in the same function body that returns the render. This performs some kind of stateful magic which makes that function itself inherently impure. Then that function returns a render which calls into that magic to perform further side effects, so that’s not pure either.
> The existence of a hook is what should alert you to the fact that there may be some side effect thing happening here.
That is true, and is for sure a benefit. But a class sends the same signal, but with the benefit of actually being fairly straightforward to reason about (notwithstanding the complexity of lifecycle, but lifecycle can be complex with hooks too).
> Not sure if this applies to you but I've found that most people who criticize hooks haven't made the leap to using custom hooks and have instead only used ones provided to them (useState, useEffect, etc).
I try not to use them at all, to minimize state and side effects as much as possible, and to encapsulate stateful code from rendering code.
I find them hard to reason about, whether in the core interface or as abstractions around the core. They fundamentally change the behavior of a function component from “this is a function which receives input (props) and returns a rendered data structure (JSX)” to “this sets up some initial state and returns a reference type of some kind that’s used to track that state over time”, but with the same syntax and semantics. The former is what attracted me to React and JSX, the latter is what I wanted to get away from. But making the same semantics do both is even worse than just having different semantics for both.
> Hooks are functions
Well not really, they’re routines expressed as functions, but they cause side effects on their enclosing context. That’s why, for example, you can’t conditionally call a hook. If you do, React won’t know to treat this component differently from components which don’t call hooks.
> Just because it's using functions doesn't mean it's functional.
I never said it was. I said that hooks are a way of abstracting functionality that can't be easily represented as pure functions.
> they make it difficult to reason about which functions are pure and which are impure.
Hooks have to have 'use' at the start of their name to be recognized by React. It should be instantly obvious which functions are pure and which aren't as long as you don't wrongly namespace your pure functions with 'use'.
> Most importantly: hooks make functions that look pure actually impure.
What makes the functions look pure? Functions in JavaScript have no guarantees about purity. TypeScript won't let you make such guarantees either, and I think Flow won't either. The only way I could imagine something _looks_ pure is if you have prior expectations of what a function component should be from how they were discussed in very old versions of React before APIs like createRef existed. The preferred terminology from the core team these days is "function components" and has been for a long time. I've been using React since 2014 and they stopped talking about "stateless functional components" well before hooks were announced in 2018.
> Thank you for elaborating! I remember I was really curious when they introduced them, since there had been a lot of focus in the community on stateless functional components before, and the benefits of their purity. Now suddenly they were renamed to function components instead, and as you say, are no longer guaranteed to simply be a function of their props.
Thank you for engaging thoughtfully and with some curiosity! I'm sorry it took me a while to come back to this, but I do want to answer because it's worth exploring.
> What problems do you feel the current hooks API is causing, that could be solved with a different API?
Frankly, once you introduce hooks, you can no longer trust that a function is a function. You can't call it without knowing whether it'll cause side effects. You have to know the hooks internals to know what those side effects actually do, and you have to know the internals of every component before you consider calling a component. It's a breaking change of huge consequence, with no type-level (whether you're using TypeScript or JSDoc or just web docs) indication of what a thing is. So now, allllll functions are not functions.
A way a different API could solve this would be to make the construction and render explicitly separate (just like all of the pre-hooks state APIs for react). Modifying the example from the hooks intro:
This isn't where I'd dust off my hands and call it a day. A more ideal hooks API, with chaining and composition in mind, would not just produce free-floating values and state update functions. You could instead wrap props and essentially have a component-local API for a props/state combination where the component still is just a function of props:
A comment adjacent to yours (which I don't have time or patience to respond to right now) asked how another API would deal with hooks which are instantiated per-render (which... I guess exists but it's mind-boggling because it's not how hooks are explained to the public), take either of these examples and nest them for each state/props case you'd encounter. This is the Python principle of explicit is better than implicit (which means, yeah, writing a bit more code might be a pain in the butt but you'll understand what's happening), and in FP is referred to as referential transparency ("If I call f(x) -> y, y will always be the same for the same x").
> You should have a core (pure) functional component, that doesn't perform network requests, and an outer component for side effects
IMO, this is the biggest thing we lost with hooks. Before there were two different types of component, and you couldn't make a pure functional component stateful or effectful without completely rewriting it.
I love hooks, but I'm constantly asking in code reviews "are you sure this is where you want this state to live?".
The other big problem with hooks is that there are no safeguards (and little documentation) about using them wrong. The post you're responding to mentions using `useEffect` to toggle a loading spinner. This might make sense if the loading spinner is outside the React component tree, but if you're using useEffect to toggle some piece of React state, yer doin it wrong. Once you start slipping these kinds of hacks into a codebase, it's a slippery slope.
> The main thing being the meaning of having dependencies for useEffect and other hooks.
This is 100% a result of how react (ab)uses the host language semantics to implement their DSL to describe components and has nothing to do with the conceptual nature of functional components themselves.
It relatively easy to imagine a design that doesn’t require the dependency lists when capturing a callback closing over props or state.
Not saying that design would be better or worse, just that it could exist and still leverage concepts like FCs, hooks, effects etc.
Oh, this I can tell. For one thing, it's very easy to get stale closures if one isn't careful. For another, hooks are a reactivity mechanism that is tied to the re-rendering of the whole component (what if you don't want to re-render the component? what if you only want to perform a side effect when a particular value changes?). Third, the docs are sowing confusion by discouraging the very natural, and often inevitable, concept of side effects, as well as by removing the concept of component's lifecycle (there is no idiomatic way of telling react to please run certain logic only once when the component mounts)[0].
> After having to use [hooks] in a large React codebase for years now, I still can't stand them.
useEffect forcing you to clean up after yourself reminds me of RAII from C++. IMO, any "ugly" functional component with hooks is even worse when written as a class component.
> Hooks are the antithesis of this - they create code them seems pure
I disagree. The presence of a hook is the indicator that something impure is happening. Seeing a hook should be equivalent to seeing a promise, option, IO type etc.
Hooks also compose beautifully together. You can make so many great new hooks by combining just useState and useEffect together, bundling up that functionality into a new hook that you can then use in any UI.
> This is probably the only React hook nuance you really have to understand
Absolutely not. There is the dependencies array, how it only does a strict equals check (no shallow comparison), how you need to memoize other dependencies so they don't change on every render, why it is a problem to leave it empty, why useEffect cannot strictly replace componentDidMount, why you can't put one hook inside the other, how (and which) hooks can trigger a re-render or block updates, how state is captured by the closure and what to do if you need current state, how to persist things across renders using useRef.. and probably more.
> I would go even further to argue that proposing a very unpopular control flow technique as a method of state management sounds like the #1 way to cause a library to have a steep learning curve and result in unexpected interactions, especially when being contrasted to algebraic effects.
Putting aside the fact that hooks are not the same as algebraic effects, I think it's funny that you believe algebraic effects are simpler to learn and reason about than generators. I'll grant you that it's pretty easy to get up and running using hooks, but I've met few frontend engineers who could actually explain to me the entire lifecycle of a complex functional component in detail. You can argue that those are not very good engineers, but I've seen this far too often not to blame the tool.
> Enough about this transferable business, writing a reusable pure function is as transferable as it gets.
But they're not pure! As soon as you call `useState` or any other hook, you lose all semblance of purity in your components, because the functions are now tightly bound to the React life cycle and can't be called or tested in any way without that context. Debugging and testing hooks means using React-specific tools that aren't applicable anywhere else, and stack traces become significantly less meaningful than they were with the class-based lifecycle methods.
> I really don't know what mental burden hooks are putting on developers either.
The "rules of hooks" are weird and counter-intuitive, and necessitate a lot more care with what would otherwise be simple refactors. That said, I could grant that it's not a big deal if that was the only gotcha, but hooks are full of them. For instance, I can't tell you how many bugs I've encountered related to the dependency array passed into `useEffect` and `useCallback`—it's very easy to have your component render too often or not often enough in ways that are not immediately obvious in a code review. Finding bugs related to these dependencies can be really painful, whereas class-based components let you easily debug updates by plopping a breakpoint into `componentDidUpdate`.
> I don't see how using unrelated solutions that require learning entirely new concepts not popular in the domain already will improve the situation.
Yeah, I think we can agree to disagree here, because this is exactly what I think about hooks: they are React-specific magic functions that require learning entirely new concepts that are meaningless outside of React. To each either own.
> Things like the `useEffect` dependency arrays and the general naming of hooks seems to lack any thought.
In React, everything rerenders newly in response to state changes. All functions are called on each render. Side effects need to be limited so that they aren't called on every render. The dependency array is where you say "only perform the side effect in response to changes to these specific values".
The problem is that most people aren't used to React's functionalish programming model. It's really common to have way too much state in a React app, and then rely on useEffect to manage that state. If your useEffect isn't reaching outside your React app in some way, it probably represents a flaw in the way you've structured everything.
> React hooks are an interesting and tricky thing to put in a box, because they’re almost monads, almost thunks, almost algebraic effects. The interplay with fibers and components makes them hard to pin down, but they are much closer to functional than any other paradigm.
They're almost classes (/objects). The implementation is what it'd look like if you wanted to make a weird half-assed OO... thing. Property and method declarations, getting attached (ultimately) to an object. But it's FIFO access/calling, rather than using named properties and methods w/ a lookup step. And the declaration syntax is bizarre and hard to read. And they're slower than regular JS OO (because they add an extra layer of JS on top of it).
> They need to rewrite it to use hooks at some point
What is the reason that not using hooks is so frowned upon? When I took my first steps with React, I didn't know hooks, so I write component classes and it immediately "clicked" with me because it behaves like most other GUI frameworks do, from Win32-based (1) to X-based to Android to iOS to macOS to Java, and tons of higher-level frameworks in between. It also feels obvious to write a component class because you are describing the behavior of a stateful thing, that is, exactly what classes are made for.
With hooks, OTOH, we have a stateful "function", but all state is implicit, which describes behavior other than its main behavior (rendering) but that is implicit too... I can see why developers outside the React world don't "get" hooks, because I can't even remember seeing this idiom outside the React world. (BTW, I've seen the claim that hooks are easier to reason about, for which I found the opposite to be true, and the claim that this makes the main function stateless, which is absurd since the state just becomes implicit).
(1) the raw system-level APIs are often not class-based, but nearly all frameworks above it are.
See [1] where they say "if you’re a functional programming purist and feel uneasy about React relying on mutable state as an implementation detail, you might find it satisfactory that handling Hooks could be implemented in a pure way using algebraic effects".
> Is there anyone who would argue that React Hooks are "intuitive", or at least become that way after you've used them for long enough?
Yes. I've been using React for a while, started with class and functional components very close in time, and almost immediately found functional components with hooks far more intuitive than class components and lifecycle methods.
Of course, I also started programming before the mid-1990s OOP hegemony, and while I did use OOP early on my programming career, I also used lots of other paradigms early on, too. I think for people whose programming knowledge is entirely OOP, Class components and their lifecycle methods are probably more intuitive. Though given that React imposes unusual restrictions on state management in class components, that mean using usual OOP class design doesn't work for class components, I find even that a bit odd. React’s rules attached to hooks seem a lot less uncanny valley to me than React’s rules applied to classes.
IME, hooks get awkward mostly when a component is managing too much unrelated local state—which in a class component (or, really, even a functional one, though one associates this more with OOP) would mean you are flagrantly flouting the SRP. I actually find the friction that hooks provide in that case—which does seem to bite sooner than with class components—a welcome nudge to reassess responsibilities and refactor before the component becomes unmanageable. (And usually that involves moving more state management out of components and into, for the apps I work on, redux.)
```
function useFormState(initial) {
const [fields, setFields] = useState(initial)
const _setFields = (name, value) => {
if (name in fields) {
setFields({ ...fields, [name]: value })
}
}
return [ fields, _setFields ]
}
```
this kind of hooks likely are going to be extended in the future, supporting edge-cases, new features and so. But are these kind of abstraction necessary? Userland pushes them to the extreme with big packages.
All because the developer initially was seeing a "repeating pattern" in the code that was readable and testable.
> Is there anyone who would argue that React Hooks are "intuitive", or at least become that way after you've used them for long enough?
In my experience, they make life much, much easier than using class components as soon as you have any complicated amount of lifecycle management. A lot of componentDidMount, componentDidUpdate, componentWillUnmount, etc stuff collapses down into single useEffect statements, which you can then turn into your own hook wrapper if you're reusing functionality.
If you’re using hooks like useState or useEffect I wouldn’t consider those functional components pure.
Hooks provide some nice syntactic sugar, which is not nothing, and make it easier to share logic between components. In my experience hooks have made my code easier to read, but they haven’t simplified it. The side effects and state might not be inlined but they’re still there. If anything they’ve made it less explicit because some of the details are hidden behind the magic that the React runtime uses to add state to functions.
reply