Hacker Newsnew | past | comments | ask | show | jobs | submit | more idreyn's commentslogin


This is excellent. Thank you.


I have a hook that I copy around between projects called `useViewport()`. All it does is watch the current viewport size and updates its value when that changes. Because it's a hook, I can call this function from any component body:

  const { width, height}  = useViewport();
  return <div>Viewport: {width} {height}</div>
Zooming in, this is just built from a useEffect() to bind event listeners, and and a useState() to hold the current value. Zooming out, it would be easy to write a useBreakpoint() hook that mostly just calls useViewport(). Hooks compose.

Before hooks, there weren't great patterns for isolating units of stateful logic from the presentational side of React. A really common pattern was to use "render props" where a component keeps some internal state and passes it into a function-as-child:

  <ViewportWatcher>
    {({ width, height}) => <div>Viewport: {width} {height}</div>}
  </ViewportWatcher>
You'd see these provider-components nested three or four layers deep, and it got ugly. Hooks solved this.


I'm not a react expert, obviously, but what would be wrong with this?

    class MyComponent {
        constructor() {
            watchViewPort((x, y) => {
                this.something += x + y;
            });
        }

        render() { /* ... */ }
    }

    watchViewPort(callback) {
        addEventListener("resize", (event) => {
            // get x and y
            callback(x, y);
        });
    }
This is obviously not production code, and I haven't tried it, so maybe something doesn't work? What is this missing that hooks provide?


Hooks let you colocate logic that, in class components, would need to be split across multiple lifecycle methods. In practice you need to remove the event listener when the component unmounts. You can get reasonably close to a hooksy API for doing this here:

    class MyComponent {
        componentWillMount() {
            this.stopWatchingViewport = watchViewPort((x, y) => {
                this.setState({ something: x + y });
            });
        }

        componentWillUnmount() {
            this.stopWatchingViewport();
        }
    }

    watchViewPort(callback) {
        const onResize = (event) => {
            // get x and y
            callback(x, y);
        };
        addEventListener("resize", onResize);
        return () => removeEventListener("resize", onResize);
    }
But being able to slice up logic by functionality, rather than by lifecycle event, gets gradually nicer as you have more of it.


Thats purely a React API limitation.

The hook API could be class based:

  class ViewportHook {
    // API on use
    constructor(component) {
      this.viewportState = component.addState(this, initialValue)
      const unsubscribe = watchViewport((x, y) => this.viewportState.set({something: x + y}))
      component.addOnUnmount(unsubscribe);

      // you can also use another hook - hook composition works
      this.otherHook = component.use(SubHook);
      // use the other hook's api
    }

    // API to expose (in render)
    value() {
      return this.viewportState.get()
      // use this.otherHook too if you like
    }
  }
You would be able to use it in a component like this

  class MyComponent
    constructor() { 
      this.viewport = this.use(ViewportHook);
    }

    render() {
      const viewportSize = this.viewport.value();
      // use in render
    }
  }
Boring, and a bit less weird.


How would passing a value from one hook to another look like, for example, Subhook requiring viewport value to set up some subscription?


Good question.

  component.use(SubHook, param)
could be used, that would pass the param as a second argument to the constructor.

The main reason why this isn't the case, I think, is concurrent mode. Hooks force certain values to be retreived and stay stable during render (i.e. you can only get a component state value during render function) and this is important if there are multiple setup and teardowns going on.

(Concurrent mode is IMO a bit of unfortunate React complexity that a lot of users of React don't really need, and many others can avoid)


This is a hook version of the same code as near as I could guess it.

  function useViewport(initialValue) {
    const [state, setState] = useState(initialValue);
    useEffect(() => {
      return watchViewport((x, y) => setState(x + y))
    }, [])
    return state.get();
  }
  
  // usage
  
  function MyComponent() {
    const viewportSize = useViewport()
    // use in render
  }
I made a couple of assumptions here. From usage, I assume watchViewport is supposed to both subscribe and return a teardown function. I also assume that the viewportState.set/get are functions for getting and setting the tracked value in the component's state.

In my opinion, there are many advantages to the hook version and several major disadvantages to the class version:

1. There's no need for hooks to have a value method or React.Components to grow the use or addState methods because the hook is just a function call which returns a value. How it produces that value is up to the code inside the function--which does call out to React--but the fact the function will always be called when MyComponent is called (absent something throwing earlier in the function body of course). The value you see being returned by the hook will always match the value you get when you call useViewport() in your component. These two things are guaranteed to have the exact same behavior as any other JavaScript function call and thus you can reason about the "registration" and value passing without needing to learn any framework-specific APIs.

2. There's no need for an addOnUnmount method on the component object passed to the ViewportHook constructor, because the hook function can use the function component's equivalent to componentWillUnmount (the return value of a useEffect) in the exact same way as a function component can, but without need for external registration.

3. In order to implement the class API, you'd need to either (A) pass the component's actual instance to the hook, or (B) create a new type of value to represent the instance of the component the hook is registering against. Option (B) is yet another API to learn, as you have to learn a new type of object to deal with in a React application. Option (A) would mean figuring out a way to prevent people from calling those methods after construction, OR introducing the possibility of registering a new slice of state partway through. The latter might be possible, and maybe that's even what you intend, but I'd want to know what the expected impact on methods like shouldComponentUpdate or getDerivedStateFromProps would be. Speaking of those...

4. I can't think of any obvious way you could pass previous versions of hook-related instance properties to lifecycle methods in the same way that you can pass prevProps and prevState.

5. Concision: the hook version has a dramatic reduction in the amount of code you have to read

6. Bundle size: because the hook version relies on functions rather than class properties, it can be minified trivially and thus reduce bundle size even more than the obvious reduction in character count would imply


The class version is

  class ViewportHook {
    constructor(component) {
      this.viewportState = component.addState(this, initialValue)
      component.addEffect(() => {
        return watchViewport((x, y) => this.viewportState.set({something: x + y})
      })
    }

    value() { return this.viewportState.get() }
  }

The hook version is

  function useViewport() {
    const [state, setState] = useState(initialValue);
    useEffect(() => {
      return watchViewport((x, y) => setState(x + y))
    }, [])
    return state;
  }
I changed the API to be more similar to the existing useEffect where you can return the unsubscribe function. That makes the difference in the size of code non-significant.

Again, its more about API design rather than classes or functions.

Another crucial benefit of the class version: you can use hooks in conditions or loops, in any order. This is because they're not called on every render. I also used a unique `key` to pass to `addState`, but its not really a requirement, since the hook constructors only get called once.

I don't understand points 3 and 4, can you elaborate? I'm assuming that when i call `component.use(HookClass)` the component creates a new instance of that hookclass. Regarding learning, I don't think the whole concept of (functional) hooks and their idiosyncracies are any easier to learn than learning one new type of class.

> Bundle size: because the hook version relies on functions rather than class properties, it can be minified trivially and thus reduce bundle size even more than the obvious reduction in character count would imply

I don't think this will make any meaningful difference. The full component methods API (addState, addEffect) is likely to be in use in any nontrivial project, so that can't be minified away. For 3rd party hook classes, they would be small and independend through `use` and easy to minify / dead-code-eliminate if not in use.


> I don't understand points 3 and 4, can you elaborate? I'm assuming that when i call `component.use(HookClass)` the component creates a new instance of that hookclass.

Sure! You show ViewportHook's constructor receiving a "component" prop. Since, in the calling component, you call this.use(ViewportHook), the `use` method is supposed to pass some sort of reference of the calling component into ViewportHook's constructor. My question was about what the type of that parameter is. Internally, is `use` something like `use(Hook) { return new Hook(this); }`? If so, you're passing a direct reference to the class instance. I had thought that maybe the framework could pass a more limited delegate for the instance to the constructor to prevent people from doing something silly with it, but that would prevent you from assigning a property safely anyway.

> I don't think this will make any meaningful difference. The full component methods API (addState, addEffect) is likely to be in use in any nontrivial project, so that can't be minified away. For 3rd party hook classes, they would be small and independend through `use` and easy to minify / dead-code-eliminate if not in use.

Object properties can't be safely minified. Any user-defined component using lifecycles will still need to have "constructor", "render", etc in the generated source, whereas identifiers (like "MyComponent" in `const MyComponent = ...`) aren't accessed by being looked up on an object, and thus can safely be minified to one or two character names. This was one of the motivations of the design of hooks. I believe it's called out in the original talk from React Conf 2018.

[Edit]

And regarding point 4: componentDidUpdate receives prevProps and prevState, and shouldComponentUpdate receives nextProps and nextState. How would you provide information about the previous or next version of that hook-based state if it's being tracked as an instance property rather than in the component's state object?


> I had thought that maybe the framework could pass a more limited delegate for the instance to the constructor to prevent people from doing something silly with it, but that would prevent you from assigning a property safely anyway.

That seems like a good idea. I'll admit my illustration is not a fully fleshed out design, it was meant more to be a sketch of how it would be possible to make a class based design much more capable than the original one was.

> Any user-defined component using lifecycles will still need to have "constructor", "render", etc in the generated source

Ahh I got it - this is not about DCE but about minified names. Not sure why my mind went with dead code elimination.

My first gut feeling is that this wouldn't be as important for everyday users if they already have code splitting / DCE / gzip. I'll look up the video, would be interesting to see some numbers - I'm hoping thats part of the presentation.

> componentDidUpdate receives prevProps and prevState, and shouldComponentUpdate receives nextProps and nextState. How would you provide information about the previous or next version of that hook-based state if it's being tracked as an instance property rather than in the component's state object?

I'm giving up having a single component state with the new API - this class based hook API also allows you to have multiple states. So I'm going to focus on the props part.

In the hook constructor, you would be able to use a listener for componentUpdate:

  component.onUpdate((prevProps, props) => { run code });
You could also do something like

  component.watch(() => [otherHook.someValue(), this.someState.get()], () => {
    // callback
  })
to get render-time change detection (equivalent to `useEffect(fn, [otherHookValue, someStateValue])`).

Its all a bit wordier, no doubt, but I feel that most of the capabilities can be implemented.


I imagine most of them could be, but this is an API which would require a lot of additions to the existing React.Component API, and would end up duplicating a bunch of already-existing functionality for something which in the end would probably need to conform to the same rules hooks do, but with what certainly feel to me like clunkier ergonomics. You'd also still be left with the problems of class components like poor minifiability and unreliability for hot module replacement.

I have to go to bed, but if you'd like to see a much better explanation about why hooks were adopted, I highly recommend the [checks notes] 1,401 comment-long discussion[1] on the PR for adopting the hooks RFC back in 2018, as this sort of design was brought up frequently. Especially worthwhile is Sebastian Markbåge's ending summary about why the team was going with hooks[2].

[1] https://github.com/reactjs/rfcs/pull/68 [2] https://github.com/reactjs/rfcs/pull/68#issuecomment-4393148...


I was trying to discuss the merits or demerits of hooks overall - just wanted to point out that a class based design doesn't need to be significantly less powerful or HoC-level awkward to use.

(I can't resist commenting I suppose - I did follow that thread as much as time permitted back then, and I couldn't agree with the conclusion from the arguments presented. Specifically, I was unconvinced that dispatch performance and file size were that dramatically different. In my experience, classes are very efficient in modern engines and code-splitting means much more than minification, especially after gzip. Even if they are the right trade-offs for Facebook, its unclear whether they're the right trade-offs for the rest of React users and the community as a whole - from my experience it has errected a bit of a barrier to front end work for backend engineers because there is a steep and weird learning curve at the start)


> Hooks let you colocate logic that, in class components, would need to be split across multiple lifecycle methods.

I think the obvious OO alternative to hooks would have been this:

    class MyComponent {
        constructor() {
            this.attachBehaviour(new MyBehaviour(this));
            this.attachBehaviour(new MyOtherBehaviour(this));
        }

        render() { /* ... */ }
    }
    
    class MyBehaviour implements ComponentLifeCycleHooks {
        componentDidMount() { /* ... */ }
        componentWillUnmount() { /* ... */ }
        componentDidUpdate() { /* ... */ }
    }
Weird React didn't even seem to consider when they went to hooks. Would be possible to implement yourself though.

I'm not saying this is better than hooks / "composable" / "functional" API (I quite like Vue's composable API) but it's less of a departure from class based components.


Departure from class based components was one of the motivations behind hooks, those are:

1. It’s hard to reuse stateful logic between components

2. Complex components become hard to understand

3. Classes confuse both people and machines

See https://legacy.reactjs.org/docs/hooks-intro.html#motivation for details. Agree or not, but it is a very intentional and well motivated direction.


Above was OO an based solution to #1, and arguably #2. Many would argue hooks did nothing to solve #2 and may have made things worse.

> Agree or not, but it is a very intentional and well motivated direction.

Agree with problem exists (roughly), disagree on solution.


Interesting you assume they didn't consider this. They probably did.

How do this two behaviours compose together/interact with each other?


I made no such assumption. I've just never seen it discussed, where as, for example, I've seen "mixins" discussed and dismissed (justifiably) as an alt.

> How do this two behaviours compose together/interact with each other?

What?


I think the disconnect is reusability. You should not use inheritance class-based react components. Therefore, if you wanted this logic in another component, you either need to copy/paste it or use an HOC. Other commenters have shown why HOCs are a pain, so I won't give into it there.

Copy/paste gets even worse when you fix the bugs in your code: - use `setState` rather than update a prop - handle remove event listener on dismount

That extra code makes copy/paste even worse, so HOCs become tbe better option. Hooks are even better since they don't require passing callbacks into a component to get the data


You are also copy-pasting the hook line

> const { width, height} = useViewport();

This could be a library call from a component, using normal language constructs. In some languages it would be a service.

React is great, but I wished they stopped changing things every week.


Every week? Hooks are from 4 years ago..

Only new things now are suspense and server components, both completely optional.

React is amazingly stable and largely backward compatible (where it’s not there are usually codemods available to do most of the work).


Context is also relatively new, and capturing errors I believe was only possible with class components, or it changed recently.

Redux, mobx, etc also were all options to fix the basic state management in React.

I concede it is not every week. I should have said the preferred approach changes too often for my taste.


I believe context predate hooks. You are right about error boundaries, probably should be revisited.

This might be controversial but imo the lack of state management in React is a pro not a con. It means the community can innovate instead of being stuck with whatever the framework provides as is standard with most other frameworks. A lean focussed but flexible API.

You can choose the flavor that works for your, patterns like React Query, SWR, Zustand, the Atom based stuff, it's all a big #win imo. I like having options.

Also I'd say changes in preferences are not exclusive to React. React did make the hook change (while still supporting the old!), but otherwise the preferred approaches are not per se exclusive to React.

I would add that there is nothing wrong with sticking to what you are using atm. You don't have to use the shiny new thing if the old works well!


This is actually a perfect example for why classes are insufficient.

You've forgotten the teardown logic. If the user navigates from & to this component often. You'll add more and more listeners. over time.

The solution would be to store the teardown logic somewhere in a class field and then call it from a destructor.

Now this unit of logic is in three different places of the class. If you need to integrate multiple units, they interleave and the class becomes convoluted. Also it's easy to forget about the teardown.

Hooks can achieve this lifecycle awareness by default. They aren't the only solution though: Vues' refs and Sveltes' stores achieve similar results.


I think that's basically right. They've been weaving "high-status outdoor adventuring" into their brand for a long time — think about the codenames High Sierra and Mavericks, or going to an Apple store and seeing a ski trip under planning in the sample iMessages.

A big part of the way Apple signals their products as clean, healthy, and high-status is by depicting themselves as exactly that sort of people.

(Not to imply that this is particularly disingenuous, but they know exactly what they're doing)


> there should be roving vans throughout the city moving and balancing the slots based on usage

There are, and the system does rely in part on this rebalancing act to be functional. (Also to bring the E-bikes in for charging — sadly the stations won't be able to charge those for some time yet.)


To be fair, there are also occasionally staff at the busiest drop off locations who manually accept bikes and then just pile them off to the side.


There was a recent article on a software-mediated version of (A) that seems to be happening all over the US: https://www.propublica.org/article/yieldstar-rent-increase-r...


Roads are free at the point of use, so this analogy doesn't hold (and I think you'd be hard-pressed to find empirical evidence of what you're describing)


There's more housing in NY now than there was in the 1900s and house prices have only increased.

There's no more choice in using roads than there is to live somewhere.


You might be interested in the S-process: https://m.youtube.com/watch?v=jWivz6KidkI


That is very interesting, thank you!


Increasingly I think the screenreader/ARIA proposition of navigating a (linearized ordering of) elements on a 2D page is fundamentally limiting for blind users. The web needs a more robust model of application state from which text, audio, and GUI representations can be derived. Our applications clearly structure "objects" and their "methods" under the hood, and it should be possible to present these more directly to users in whatever format makes sense to them.


It has to be linearized because hearing, and time itself, are one-dimensional.

Screen readers can represent nesting of content, going into one element and coming back out, Apple's VoiceOver in particular uses this concept. It may be conceptually helpful in some instances or to some people but often it just makes things more cumbersome to navigate.


Maybe I don't get what you want to express, but screen readers allow for alternative presentations of an HTML document, allowing for alternative modes of content discovery, e.g. hierarchical outlines of headlines and "landmarks".

Several ARIA attributes further allow for communicating the current application state, e.g. whether content has changed (aria-live), a button is in a pressed state (aria-pressed), or a menu is opened (aria-expanded).


That would require some regulation, since commercial web design deliberately obfuscates the underlying structure.


I agree. For visually impaired users, HTML/sceenreaders are simply wasteful layers between them and the core behaviour/data of an application.

In an ideal world, we could have entirely different clients that focus on audio-touch interfaces like a some sort of keyboard driven Alexa skill.


Is that not the DOM?


Check the Graphics folder! (It crashed immediately for me in Firefox, though)


FWIW: The emulator dynamically loads chunks of the hard disk over the network, and will usually crash if that fails -- which can happen if the site is busy.


I tried it again and was able to use it this time!


Glad to see they never exceed 20 mph in this video. If AVs can stick to city speeds that prioritize pedestrian safety over travel time, they'll have a huge head start over human drivers in safety and "human decency" metrics. Hopefully have a traffic calming effect on the rest of us, too.


Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

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

Search: