Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Functions which return different data for the same input aren’t functions. It’s a bad interface.


That's a very general statement. I wouldn't accuse you of implying that it would also apply to e.g. Date.now(), but I firmly disagree that it applies to hooks.

I get that hooks are declared in the form of functions, and that pure functions are the desired pattern in many cases. But I like to think of hooks as hooks, not functions (emphasized by the naming convention of prefixing with "use"). We are in the context of React, after all, and I think it is comparable to critique of JSX because of "separation of concerns". Yes, valid critique in many cases, but not necessarily applicable to React.

Having been a React developer for over 5 years now, I think hooks solve a lot of problems that before would typically have been solved with Redux, Redux Saga and other libraries. Or with home made solutions, leading to various degrees of mess. From what I've seen, hooks have enabled React devs to write code that is even easier to understand, while also keeping things simpler and external libraries fewer.


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


Hooks are not something you instantiate and then just call later, the whole point is that they need to be able to chain/compose with each other, and that has to happen during every render. I'd be really curious to see what your proposal is in concrete code, as on the surface it sounds like it either misses the point of hooks entirely, else it's just a syntactical change that is more a surface-level critique than an actual criticism of the concept itself.

Also your subroutine comment makes little sense. A function that returns different values over time is just that, it need not output anything. In UI programming I guess you'd call everything a subroutine, as state is constantly changing and you're always in need of outputting changing dates, inputs, etc.

The whole point of frontend is you have to accept that fact and find the best paradigm to deal with that constant change of state, and I'll claim hooks do that better than anything before it. If you did have a better solution to hooks, that'd be a big deal, and many people would love to see a good example.


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.

I guess what we disagree on is whether that is an issue or not. What problems do you feel the current hooks API is causing, that could be solved with a different API?


Another API improvement case I neglected is just to make hooks HOCs and use existing patterns. They can easily wrap props and return a component with roughly the same API for instantiation.


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


It is how hooks are explained to the public, for good reason.

This API, which is one I came up with many years ago before hooks as well, is not hooks, and doesn’t have the same properties you’d want. It doesn’t at all allow composition. Honestly, it feels like you haven’t spent much time with hooks if this is your improvement. It's a great case of "making it look better but removing the valuable properties in the name of purity".

I already explained fairly well why you want hooks to be inline in render, as they need to handle side effects, they need to compose with each other and react to changing values, and therefore they need to constantly run to check for updates. It’s 100% how they are used by everyone.

I hope you find time to actually look at what hooks do more closely and find the power behind them.

Again - frontend always had highly stateful and state-changing components, they were just abstracts in ways that suited your tastes better before. But it is just taste you’re talking about, not some inherent superiority. You have to deal with state on the frontend that’s changing often during renders, and you want to handle it locally, and you need to compose and chain various units of logic. You can try and hide that fact behind more syntax, but it doesn’t change the fact that your inner function you defined is depending on an outer closure that is stateful, breaking your law of no functions that act differently when called with the same values.

Anyway, to each his own, if you don’t care enough to actually understand them and contend with what they solve then I won’t try and force you to!


> It doesn’t at all allow composition.

Of course it does. The most basic programming primitives of composition is functions. Want to compose hooks with these APIs? Call one hook from another. They’re just functions.

Edit: or call one hook-using factory/HOC from another.

> Honestly, it feels like you haven’t spent much time with hooks if this is your improvement

It’s true, I do avoid using them. But I spend a lot of time reading the docs, as well as working with a large variety of different JSX APIs.

> It's a great case of "making it look better but removing the valuable properties in the name of purity".

I don’t know why that’s what you took from my pseudocode, since one of the examples maintained the exact same interface. Why would it be less valuable if hooks-consuming components are factories/HOCs?

> I already explained fairly well why you want hooks to be inline in render, as they need to handle side effects, they need to compose with each other and react to changing values, and therefore they need to constantly run to check for updates. It’s 100% how they are used by everyone.

And all of that is possible with the APIs I suggested.

> I hope you find time to actually look at what hooks do more closely and find the power behind them.

Please don’t condescend. I’m plenty familiar with the material.

> You can try and hide that fact behind more syntax, but it doesn’t change the fact that your inner function you defined is depending on an outer closure that is stateful, breaking your law of no functions that act differently when called with the same values.

It’s not about hiding statefulness, it’s about:

1. Isolating it: this is every letter in the SOLID principles.

2. Maintaining clearly identifiable interfaces that are clear in their boundaries and expected behavior.

In Clojure, a language that's pure FP by default with clearly defined APIs for mutable escape hatches, this is achieved by convention with @ prefixes for state and ! suffixes for dereference. In JS/TS, this is achieved by convention by separating state init from post-init behavior (my factory/HOC example), or by passing/wrapping state (see common redux patterns). Both have corresponding types that make clear what’s going on.

Shoving state and return values into the same space makes that impossible.


You really don't understand composition as it related to hooks, sorry, it's not condescension, you are arm-chairing something and it's obvious. I could provide a complex example for you and then re-write it in your example, and you'd see how hooks is far more elegant, and maybe I will, but to be honest there's a million examples on the web.

And algebraic effects are a FP concept, go look at OCaml which has been pioneering them, so your appeal to FP purity doesn't even hold.


> You really don't understand composition as it related to hooks, sorry, it's not condescension, you are arm-chairing something and it's obvious.

You’re being a jerk. You’re not in the least being open to the possibility that I understand the subject matter but have omitted exhaustive detail because I’m typing on a phone in comments on HN. But if there’s something you think I haven’t considered feel free to show a use case, and I’m happy to work with it to show an API I think improves on hooks but accounts for it. Let’s collaborate.

> I could provide a complex example for you and then re-write it in your example, and you'd see how hooks is far more elegant, and maybe I will, but to be honest there's a million examples on the web.

Please provide an example. Let’s have a conversation, not... this.

> And algebraic effects are a FP concept, go look at OCaml which has been pioneering them, so your appeal to FP purity doesn't even hold.

I didn’t appeal to FP purity at all. I explicitly appealed to impurity! I appealed to interfaces that embrace it and clearly delineate it.


You admitted yourself you've never really used them in practice.

I would never having simply read some docs on Rust's borrow checker having never used it, go to some discussion of experts online and nerdsnipe with a pithy "It's a bad interface" comment without having ever seriously tried it.

You're not being a jerk, just a jerk-off. You're talking out of your ass for fun and you admit you've never actually used it, and your poor example show it clearly.

I mean really, think about it. I was honestly interested in seeing if you had some unique insight but your example has no relation at all to hooks, it would only solve useState but has nothing to do with useEffect which is equally if not more important. You replaced the old React `this.setState` basically and not at all hooks, it's a syntax many have done before, that I had personally invented ~7 years ago well before hooks and used it on a small app, and which solves none of the problems they solve.

And now you want me to personally educate you?

At first I was going to just leave it at this conversation, but you know what, fine, here's your example:

  function Component(props: { isActive: boolean }) {
    const [query, setQuery] = useState('')
    const queryDebounced = useDebouncedValue(query, 200)
    const searchResults = useSearch(queryDebounced)
    const results = useLastValueWhen(searchResults, !props.isActive)

    return <>
      <input onChange={e => setQuery(e.target.value)} />
      {results.map(result => <ResultItem {...result} />)}
    </>
  }

  // these two are easy to write out so leaving out
  const useDebouncedValue = () => // debounce hook
  const useLastValueWhen = (a, b) => // hook that retains last a when b == true

  const useSearch = (query: string) => { // can be used elsewhere
    return useFetch(`http://localhost/search?query=${query}`)
  }
  const useFetch = (url: string, args?: any) => {
    const [state, setState] = useState([])

    useEffect(() => {
      let alive = true
      fetch(url, args).then(res => {
        if (alive) setState(res)
      })
      return () => {
        alive = false
      }
    }, [url, args])

    return state
  }
Please do show your example of this.


Okay I looked and... I'm not sure why you think this example can't be accomplished with my alternate API. You can compose hooks the exact same way and use them in the outer constructor/HOC function. You can nest said functions to receive incoming props. I honestly don't want to spend the time explaining functions as values and scopes, unless you sincerely think that I'm missing a use case here.


I even looked again at my examples and I’m a little annoyed that I even bothered putting a string around my finger for this condescending exchange. I accounted for this case in my second example! Outer function takes props, hooks are a function that expand them. There’s your composition bud.


Do your part and come back with an example that does the same thing in your syntax.

Don't be lazy and fool yourself into thinking you have a point because you didn't write it out.

I know what problems will come up and I'd like to see how you try and solve them. In the best case you end up with a more verbose syntax for doing the same thing, and in the likely case it will be brittle because you'd have to manually link the instantiations and re-runs, manually ensure you're calling things in order, and may even have to manually dispose things.


> You admitted yourself you've never really used them in practice.

No, I said I actively avoid them in practice. That said, I’m acknowledging there’s more here and I’ll come back tomorrow.


Yea, it's definitely a workaround for not having algebraic effects, you can basically think of it as a yield.

Of course ideally the language would have it first class, it doesn't change the fact that they are immensely useful. Having composable stateful logic that always handles side-effects is incredibly useful, and it works really well within the immutable data / top down data flow model of React.

I'd love to hear the alternatives, as I spent years researching and working on many (Redux and FRP/reactions) and they're simply not as nice.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: