Hacker Read top | best | new | newcomments | leaders | about | bookmarklet login

> 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.



sort by: page size:

> 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.


> 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.


> 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'.


> 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.


> 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.


> 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).


> 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.


> 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.


> 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.

edit: the footnote broke formatting


> As you can see it becomes not that clean quickly.

This is my biggest gripe with my fellow developers. Why they see the need of abstracting everything?

I don't see any problem in this code:

``` const [count, setCount] = useState(0) const [name, setName] = useState('') const [school, setSchool] = useState('') const [weight, setWeight] = useState(0) const [height, setHeight] = useState(0) ```

but i see them in the proposed alternative:

``` 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.


> 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.


> Hooks allow you to bundle code together by functionality, and consequently allow you to easily extract and share said functionality in a very composable way.

You’re using React. The mechanism that enables code reusability is through composing components.

There’s nothing wrong with class components even for the most complex logic. The only downside is the community has moved on and mostly adopted hooks and functional components.


> React knows which component is rendering at any point in time — so it knows which component useState() call corresponds to.

Personally, I think that may be the piece that makes it feel a bit magical. With `this.setState()`, usage of `this` makes me feel like I know how the component and the state are linked.

With hooks, however, there's no obvious link to the component in the code. I'm grabbing `useState` off of the shared react module, `useState` is not passed into the component, and I don't have to reference the component itself at any point while using hooks. The new sets of rules you have to follow to make things line up correctly play a part, as well.

Of course that may be how `this.setState()` does it anyways - you know better than I do, obviously. Maybe the usage of `this.setState()` made me feel confident in something I didn't actually understand under the hood. But at least for me, that's why `useState()` feels a bit magical at first glance compared to `this.setState()`. Not _too_ magical, and not enough to scare me away from using hooks, but still a bit.


> That's a very general statement. I wouldn't accuse you of implying that it would also apply to e.g. Date.now()

You don’t need to, I’ll gladly go on record saying Date.now() is not a function. It’s a subroutine. The clearest signal isn’t even its return value, it’s the fact that it takes no input.

> [... everything else you said ...]

I don’t have a problem with the expressiveness of hooks, nor the general way they solve a problem by defining clear APIs for interacting with reactivity and state. I have a problem with the context in which they’re called, and the way they infect their context by changing the semantics to be sometimes else.

Hooks are something you instantiate and use in your component’s lifecycle. If it were that simple, you could call them in an outer function and return a component. But because you call them while defining the component’s behavior, your component is no longer semantically a function of props, it’s a constructor.

A better API would be hooks called in a component factory, returning components which use those hooks.


Are you referring to hooks in general, or the specific useState hook they showed?

I love the idea of hooks - the more I can isolate my logic, the better. The useState example I'm pretty ambivalent about, for the exact reason you state.


> 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.


> 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:

        import React, { useState } from 'react';

        function Example() {
          const [count, setCount] = useState(0);

    ---  return (
    +++  return () => (
            <div>
              <p>You clicked {count} times</p>
              <button onClick={() => setCount(count + 1)}>
                Click me
              </button>
            </div>
          );
        }
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:

    import React, { useState } from 'react';

    function Example(props) ({
      const setCount = useState(props, (props, state) => {
        ...props,
        count: state,
      }));

      return (props) => (
        <div>
          <p>You clicked {props.count} times</p>
          <button onClick={() => setCount(props.count + 1)}>
            Click me
          </button>
        </div>
      );
    }
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").

> Hooks definitely build a complex data structure

But you don't. It is react that builds the datastructure in the background, not you that builds and controls it. Hooks might be declarative, but this is actually orthogonal to being pfp or not.

All of that is totally fine btw., there is no need for everything to follow a pfp style. It's a tradeoff. But we shouldn't call something pfp that isn't, even if it is closely related.


> 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.

next

Legal | privacy