7 ms·
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
by idreyn 4y ago
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.
- recursive 4y agoI'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?
- idreyn 4y agoHooks 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.
- theteapot 4y ago> 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.
- mattgperry 4y agoInteresting you assume they didn't consider this. They probably did. How do this two behaviours compose together/interact with each other?
- theteapot 4y agoI 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?
- codeptualize 4y agoDeparture 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 https://legacy.reactjs.org/docs/hooks-intro.html#motivation for details. Agree or not, but it is a very intentional and well motivated direction.
- 4y ago
- mind-blight 4y agoI 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
- mejutoco 4y agoYou 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.
- codeptualize 4y agoEvery 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).
- mejutoco 4y agoContext 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.
- codeptualize 4y agoI 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!
- SebastianKra 4y agoThis 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.