18 ms·
Thinking in React Hooks
- pgt 7y ago(Edit: it is worth noting that Hooks are based on Reagent's reactive atoms.) Compare the complexity of Hooks with the simplicity of state management in [Reagent](http://reagent-project.github.io/ http://reagent-project.github.io/) or [Rum](https://github.com/tonsky/rum https://github.com/tonsky/rum). Complete ClojureScript example using Reagent: (ns reagent-example.core (:require [reagent.core :as r]) (def !count (atom 0)) ;; ! prefix is convention for state (defn counter-component [] [:div [:p "You have clicked the button " @!count " times."] [:button {:on-click #(swap! !count inc)} "Increment"]]) (r/render [counter-component] (js/document.getElementById "app")) The same example using Rum: (ns rum-example.core (:require [rum.core :as rum]) (def !count (atom 0)) (rum/defc counter-component < rum/reactive [] [:div [:p "You have clicked the button " (rum/react !count) " times."] [:button {:on-click #(swap! !count inc)} "Increment"]]) (rum/mount (counter-component) (js/document.getElementById "app"))
- pixelbreaker 7y agoWhat's your point? This is about Hooks vs Class based components.
- pgt 7y agoIf you need to manage state and want to focus on functions instead of objects (classes), try ClojureScript - especially in combination with [DataScript](https://github.com/tonsky/datascript https://github.com/tonsky/datascript), an immutable database and Datalog query engine. Clojure/ClojureScript's persistent, immutable memory model makes it much nicer for reasoning about state. Maybe I'm just a dinosaur, but I can't keep up with all the changes to JavaScript, whereas ClojureScript compiles to ECMAScript 3 and the syntax hasn't changed since its release 12 years ago.
- drbojingle 7y agoI think you're missing a ) for counter-component
- pgt 7y ago<p>Thanks, fixed! :)
- drbojingle 7y agoNo sweat! For what it's worth I'm quite interested in ClojureScript/React and I appreciate the example. It's nice to see how it compares to JS/React
- xwdv 7y agoDon’t know about others but React Hooks look way easier than whatever this is.
- ng12 7y agoI don't think this is a compelling example. React would implement this almost identically: const Counter = () => { const [count, setCount] = React.useState(0) return ( <div> <p>You have clicked the button {count} times.</p> <button onClick={() => setCount(count => count + 1)}>Increment</button> </div> ) } Replace the atom/swap with a useState hook and it's almost identical.
- mondaygreens 7y agoYeah that's way more readable and intuitive than anything that came before (that I'm aware of)!
- kgwxd 7y ago1 language vs 2 intertwined languages is compelling enough for me.
- ng12 7y agoDo you mean JSX? Here you go: import {createElement as h} from "react" const Counter = () => { const [count, setCount] = React.useState(0) return h("div", {}, h("p", {}, `You have clicked the button ${count} times`), h("button", {onClick: () => setCount(count => count + 1)}, "Increment") ) } There are additionally libs like hyperscript (https://github.com/mlmorg/react-hyperscript https://github.com/mlmorg/react-hyperscript) that take the non-JSX approach further. Although honestly, I don't recommend it. JSX is really great even it seems a little unfamiliar.
- ArchReaper 7y agoWhat? JSX allows you to write HTML as though you are actually writing HTML. The '1 language' version requires you to learn language-specific syntax for creating HTML elements. How is that more compelling?
- pgt 7y ago(not= syntax semantics)
- lilactown 7y agoI think that hooks and Reagent developed somewhat independently; or at least, they are not as related as one might think at first glance. The primary difference is where the data _flows from_. Like another commenter pointed out, your examples can be done similarly as trivially with Hooks. The difference is that the state is stored in Reagent in a global mutable atom, while the state in React is stored in the React tree, and is tied to the render lifecycle. This doesn't seem like a huge difference on its face, but in practice it makes for very different experiences navigating a code base. In Reagent, your data is flowing down via props but also from the side via any number of atoms/subscriptions/etc. In React, you know that your data is always either state local to the component or passed via props. This isn't even to mention the other various hooks like useEffect, which allows you to coordinate side-effects with changes in data in a composable, locally-scoped way that Reagent does not allow you to do. In order to coordinate side effects, it requires you to keep the description of side effects completely separate from your components, which makes them less composable. For people who enjoy the way that React Hooks makes you think, and are also interested in ClojureScript (immutable data structures + amazing standard library + hot reloading + macros built into the language), check out https://github.com/Lokeh/hx https://github.com/Lokeh/hx or its WIP successor, https://github.com/Lokeh/helix https://github.com/Lokeh/helix (I am the author of these libraries).
- filoeleven 7y agoI have a tangential question for you about helix. You switched from hiccup-style lists in hx to functions in helix.dom. I was just looking at fulcro and they made a similar choice. What drove that decision for you? One of the things that first drew me to check out reagent and hx was that the components are declared as data. I haven’t tried any of these libs out yet, so while hiccup syntax looks to me to be more idiomatic clojure, there must be solid, practical reasons for implementing them as functions that I haven’t understood yet, and I’m curious to hear more about what they are.
- lilactown 7y agoPerformance, primarily. Hiccup that is generated _at runtime_ is incredibly wasteful; you allocate a bunch of immutable vectors and maps, then parse them into React elements and throw them away, every time your component is rendered. This is a constant performance tax on your application and makes Reacts tradeoffs re: virtual DOM worse. React Elements are already data (they are POJOs), so you lose nothing in terms of declarative-ness using hiccup vs. functions/macros. If you're very attracted to hiccup syntax, you can still use hx's hiccup parser with helix (docs incoming) or try out a library I wrote called thump[0], which uses reader tags to parse the hiccup at compile time into React Elements. 0: https://github.com/Lokeh/thump/ https://github.com/Lokeh/thump/
- drcode 7y agoFWIW, here is the same example in clojurescript qlkit. It doesn't use "reactive atoms", just the usual state map approach. Of course qlkit's selling point (like for Om) is when global state needs to be synchronized to components, which isn't an issue in this example. (ns qlkit-example.core (:require [qlkit.core :as ql])) (defcomponent Counter (state {:count 0}) (render [atts {:keys [count] :as state}] [:div [:p "You have clicked the button" count " times."] [:button {:on-click #(ql/update-state! update :count inc)} "Increment"]])) (ql/mount {:component Counter :dom-element (js/document.getElementById "app")})
- dhucerbin 7y agoReagent atom is only useState part of hooks. useEffect is all about syncing effects with state/props changes. VDOM is this one specific, privileged thing so it can use return value of the function. Less common effects must use something different. I'm not aware of such solution in reagent or rum. Something like re-frame comes to mind, but there, all effects are global.
- istoica 7y agoIs there a way to trigger something after state changes ? In the class based components, setState has a second argument that gets triggered after state has updated. This is nowhere to be found in the hooks approach, without using flags and other strange constructs. Where is it gone ?
- deleted 7y ago[deleted]
- sombremesa 7y agoYou can use useEffect for this. If you only care about certain state/props, put them in the second argument in an array.
- gherkinnn 7y agoUse useEffect. const [thing, setThing] = useState(true) useEffect(() => { console.log({ thing }) }, [thing])
- rpastuszak 7y agoNot that it matters too much given the question asked, but am I right to think that the useEffect callback would get fired on the next render (function call) here?
- lilactown 7y agoThe function given to useEffect will be fired after a successful _commit_ (aka React actually manipulates the DOM after a render). Here's a helpful diagram that shows the difference between render and commit phases in terms of the class lifecycle methods: http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/ http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram... So when a render executes useEffect with a callback (and values in its dependency array has changed), it will schedule the callback to be run once React has committed the DOM changes based on the current render.
- Klathmon 7y ago
- stephen 7y agoWhat an amazingly well-written, well-illustrated blog post. I really like how the post showed the contrast between "imperative anytime thing x changes remember to poke thing y" vs. "declarative thing y is calc'd on thing x". That said, I'm a little disappointed with hooks b/c, while React is not at all claiming they invented this declarative approach, it seems like other "state-of-the-art" approaches (like mobx? not picking that one as the best/only, but it's observer-/push-based) can do the same thing without the ugly duplication between "here's my calc on x/y/z" + "oh and my calc's array of dependencies is [x,y,z] which btw also doesn't use deep equality". Like I get that it works, and it's built into React, so it's a for-free declarative/derived value/derived state approach vs. pulling in yet another framework, but I'm disappointed they couldn't (yet?) solve the "you also have to specify your dependencies/shallow equality" problem.
- JMTQp8lwXL 7y agoNot using deep equality allows for huge performance gains. You can use an immutability library like https://github.com/guigrpa/timm https://github.com/guigrpa/timm to handle mutations, if you want to edit a nested property on an object and return a new object.
- Ineentho 7y agoI haven't heard about timm before, but it looks very similar to immer https://github.com/immerjs/immer https://github.com/immerjs/immer, which is even recommended in the official React docs now
- acemarke 7y agoWe also recommend using Immer with Redux [0], and it's used internally in our new Redux Toolkit package [1]. [0] https://redux.js.org/style-guide/style-guide#use-immer-for-writing-immutable-updates https://redux.js.org/style-guide/style-guide#use-immer-for-w... [1] https://redux-toolkit.js.org/ https://redux-toolkit.js.org/
- 7y ago
- omarhaneef 7y agoMy favorite things about this post: 1. The comparison of the lengths of the function using the two styles (class/function) 2. The comparison of the length of the changes required in the two styles 3. The clarity achieved by maintaining the two columns consistently distinct. If you are interested in how to demonstrate the efficiency of a new coding technique -- even if you're not interested in React Hooks -- this is worth a read.
- smrq 7y agoObligatory HN post not about the contents of the post: On my MacBook, fullscreen, I get horizontal scrolling on this page. Honestly, what kind of monitor was this site designed for? The content is excellent, though.
- Waterluvian 7y agoIt worked beautifully on my stupid loaner 1080p screen.
- chmln 7y agoThe class-based examples given are a bit disingenuous. Not only the author is not using anything like redux, the class examples look deliberately made longer than they should be. For example, one of them has 4 setState() calls after each other instead of combining them. Another manually performs deep equality checks for each key instead of using some library/helper. The conciseness of React hooks also relies on the knowledge of implicit ordering of when certain functions are called, while lifecycle hooks are obvious and straightforward. Optimizing for writability is not the best approach for anything beyond prototype-level codebases.
- epidemian 7y agoI don't think the class-based examples look deliberately inflated. > or example, one of them has 4 setState() calls after each other instead of combining them. You would still need one line for each state property set: this.setState({ data: newData, dimensions: getDimensions(), xScale: getXScale(), yScale: getYScale(), }) (Unless you were to compress all of that into one line...) In fact that makes things longer in terms of LoC. A bit less noisy though, that'd be true. > Another manually performs deep equality checks for each key instead of using some library/helper. Yeah well, I think this is completely fair if you want to "compare oranges with oranges". If the author were to include external libraries, then they would no longer be comparing "Reach with hooks" with "classic React", but with "classic React + some X library" instead.
- mharroun 7y agoWhy not put the function calls into the render? And only decide to rerender on state change. You could push nearly all that logic back into extends Purecomponent
- Klathmon 7y agoThey point out that the get* functions are supposed to be "computationally expensive", and you don't want something like that in your render method. But also in either the current or near future versions of react, with Suspense and concurrent rendering, the render method may be called multiple times over the course of a single render. So if any of those functions aren't entirely pure, there is a good chance there will be bugs from that.
- mharroun 7y agoThe this.setstate spamming and lack of using something like the PureComponent class show that the author either is bad at coding in class based react or intentionally making it look worse. Disclaimer: got nothing against hooks but hate false examples.
- daok 7y agoThe class example is misleading in terms of the line of code. The first example would have the componentDidMount and componentDidUpdate to call the same function componentDidMount() { const newData = getDataWithinRange(this.props.dateRange) this.setState({data: newData}) } componentDidUpdate(prevProps) { if (prevProps.dateRange != this.props.dateRange) { const newData = getDataWithinRange(this.props.dateRange) this.setState({data: newData}) } } Could be: componentDidMount() { get(this.props.dateRange); } componentDidUpdate(prevProps) { if (prevProps.dateRange != this.props.dateRange) { get(this.props.dateRange); } } get(dateRange){ const newData = getDataWithinRange(dateRange) this.setState({data: newData}) } It removes the duplication.
- TheTrotters 7y agoThe original example has 10 LOC, yours has 12.
- crtlaltdel 7y agoAnd the 12 LOC example allows you to change the logic in one place and not two. While more verbose, this does make it harder to introduce bugs by forgetting to update the logic everywhere it is used (copy/pasted)
- stickfigure 7y agoYes, it's a better pattern, but then the hookophobes would complain that the example artificially inflates the line count by adding a not-strictly-necessary function. I don't think the author is being disingenuous, just trying to keep the examples straightforward. It doesn't matter anyway. With or without the function, the class example compares unfavorably to the hooks example.
- daok 7y agoThe hook functions need the knowledge that the mount and update are doing the same thing. However, the example would be more complete if the mount was doing something different. In that case, not only the class example would be more clear to read with explicit function for mount and update but also would be similar in terms of the line of code. With Hooks, if you want something different you need to do a "trick" which is less obvious than defining `componentDidMount`. I am not against Hooks, but I do not believe the example portrait the situation properly.
- oaxacaoaxaca 7y agoI have not been a fan of hooks thus far, but these are some intriguing examples. Especially the useMemo ones. My complaints about hooks are that with hooks we lose useful tools in our react toolkit: class methods, separate componentDidMount/componentDidUpdate lifecycle events, shouldComponentUpdate, PureComponent, etc. And my biggest complaint is that we're losing simplicity. React spread like wildfire when it first came out because it was so beautifully simple: a class, with something called `props`, and this.state and this.setState(). That's it. You can build SO MUCH with just that. To me, with hooks, React's simplicity is being replaced by cloudiness. There's so much "magic" now. The order of hooks in the code is the most important thing for a hook component to work right. New linter rules had to be built to remind people where hooks need to be written. I do appreciate the work that's been put into hooks. And like any other open source project, I'm incredibly grateful to the authors and maintainers. I guess I just wonder if anyone shares the same sentiments as mine, because it seems like the only thing I read about this topic is that classes suck, class components are full of pitfalls, etc.
- lilactown 7y agoYou use the word "simple," but it sounds like you mean something more like "familiar." That's not a knock; familiarity is important when designing an API that thousands of developers are going to use every day. But as you can see by the blog post, there are tradeoffs between doing something the way people are familiar with, versus a different way that's less familiar, but has different capabilities that might fit the problems better. Classes map to certain use cases very well; for instance, there's no Hook for "componentDidCatch" or any other error handling. That being said, you are not losing anything. The tools are simply different (although the old tools are still there, too!). As the blog post goes into, you can still implement the same _behavior_ as you did with componentDidMount/componentDidUpdate, but with Hooks you can formalize the different patterns you would find when using those methods into a custom hook that you can reuse. This wasn't possible before. shouldComponentUpdate can be easily replaced with React.memo, a higher-order component that takes a function that compares the previous props to the new props and returns whether they are the same. Wrapping it in React.memo without a custom comparator is the same as PureComponent.
- pier25 7y agoGreat article, but to me hooks solve problems that I rarely have. Anyway I left React/Preact/Inferno/Vue for Mithril and my life is a lot simpler now.
- c-smile 7y ago> In a function component, we need to think about what values stay in-sync. So we are removing Classes in favor of Functions. Something tells me that on the next iteration we will see something like this: "In a declarative component, we just need to declare what UI elements shall stay in-sync with what items in data model." And that is precisely what Angular is based on (declarative two way binding). And the cycle of evolution spiral will be complete. Life goes on ...
- gombosg 7y agoWhy? This is just slippery slope arguing. Two-way bound data is a huge leap in framework complexity. They require Zone.js stuff in Angular and use of ES2015 Proxies in Vue.js. React has neither of these. Hooks is basically a different syntax describing the same framework. I personally love them since I no longer need to care about lifecycle methods, but can describe state and state change logic.
- imtringued 7y agoTwo way binding is absolutely awful and what React hooks appear to do is one way data binding which has always been considered superior. Of course one way data binding isn't exactly new but your comment is making some fundamental mistakes.
- bg0 7y agoThis is off topic but can anyone tell me what color theme this code is in? I need this for VSCode
- bbx 7y agoWhenever I like a syntax highlighter theme on a website, I search on Github a few of the hex codes. Like this: https://github.com/search?q=65ddd9+ff767b+ffc312&type=Code https://github.com/search?q=65ddd9+ff767b+ffc312&type=Code In this case, there's only 1 result… the author's public repo. So I guess the search worked! But the theme seems to be a personalised one the author created themselves. But the primary colors remind of Solarized Dark, if that helps: https://www.google.com/search?q=solarized+dark&tbm=isch https://www.google.com/search?q=solarized+dark&tbm=isch
- bg0 7y agoVery clever. Thanks man!
- throwaway286 7y agoReasons I dislike hooks: The dependency array feels like a hack. It's way too easy to accidentally make an infinite loop. It's no longer safe to pass around functions; they have to be wrapped in useCallback(), which also has a dependency array. Yes, I'm aware of the eslint rules and the autofixer. It just feels that I'm so far away from writing javascript. Hooks are great when you're doing a simple setState or something, but with complex logic they get confusing quickly.
- jbaudanza 7y agoYes, I have had the same frustrations. The dependency array pattern feels very brittle. I have found two ways to deal with this: 1) Instead of taking a callback, can your hook return some value instead? I've found that in most cases, it can. That value can then be used to in some other (hopefully shorter) dependency array for a useEffect() hook that is a peer to your original hook. 2) In cases where your hook must take a callback, your hook should store the latest version of the callback in a local useRef. This add a lot more boilerplate code, but it feels safer to me than requiring the caller to manage the identity of the callback. I've just had to make peace with the fact that I'm going to have to write a lot of boilerplate glue code around hooks. I try not to worry about it and I just hope the next version of hooks is better.
- Aeolun 7y agoOr you can just use classes? Where writing these dependencies is actually formalized?
- tekkk 7y agoYour comment reminds me, I never found a perfect explanation what a negation inside the dependency array does. Does it simply negate the checked variable or just omits it from ever triggering eg useEffect? For example: const useClickOutside = (ref, onClickOutside, isActive) => { ... useEffect(() => { if (!isListening && isActive) { addHandlers(handler) setListening(true) } else if (isListening && !isActive) { removeHandlers(handler) setListening(false) } return () => { removeHandlers(handler) } }, [ref, !onClickOutside, isActive]) } I remember adding the negation here as the useEffect would fire needlessly (or wouldn't, can't remember), but wasn't entirely sure what it did. The whole hook is just for checking clicks outside an element. Anyway, I agree with you that at times they can a bit too abstract for the fellow programmer. Without prior knowledge how hooks work it can be quite tedious to read (without proper documentation at least)
- rebotfc 7y agoThe thing I like about hooks is that components can be truly modular in terms of both behaviour and state. They can so be relatively easy to reason about in isolation and therefore it's easier to reuse them with less plumbing needed. For instance a simple custom hook use_graphql_list(url,query) can encapsulate the entirety of fetching json via a graphql query keeping an array up to date and in sync and present data in a format that is easy to use in a list component. For instance in rust: let graphql_query = "{countries {name currency emoji}}"; let graphql_url = "https://countries.trevorblades.com/"; let container_name = "countries"; let (list, graphql_control) = use_graphql_list::<Country>( graphql_query, graphql_url, container_name);
- mmcnl 7y agoI like React Hooks, I like the simplicity. I dislike that it makes reading the code much more difficult. The code required to do a simple "componentDidMount()" is very abstract. It is difficult to reason about what a component does at first glance, with the class methods this is much easier.
- city41 7y agoclass components have a huge downside in that `this` is mutable. Hooks solve this problem, and this problem seems to have motivated their creation to some extent. This blog post goes into more detail: https://overreacted.io/how-are-function-components-different-from-classes/ https://overreacted.io/how-are-function-components-different...
- ArchReaper 7y agoTo do 'componentDidMount' you simply need a 'useEffect' with an empty (non-null) dependency array. It makes sense to me - maybe the issue is more familiarity than simplicity? Edit: useEffect performs a side effect every time one of it's dependencies has changed value. Passing an empty array means the effect is only run once (on mount) because there are no dependencies.
- jpincheira 7y agoWow what a sick animated page! Amazingly done.
- sbr464 7y agoOne thing to watch out for currently are memory leaks that retain previous state even if unmounted and/or unrelated. I’ve seen several bugs especially with useRef/memo, and animation related hooks, or hooks that combine several together to expose a simpler api. I didn’t notice them until working with larger file uploads and local content. They can be hard to spot with small changes, Since they are usually deep in the memory trace stack. The component with the bug doesn’t need to even be in the tree to accidentally retain state that you’ve cleared/unmounted. Since then I’ve made a simple test that saves a large test blob to local state, and makes sure it gets garbage collected etc, not stuck in another components previous state memory snapshot. — An example could be a loading animation above/outside the tree, that uses a hook somewhere inside its library that tracks browser size changes. It wouldn’t seem to affect you, but it could inadvertently cache some state in the window. So you’d only detect it if you’re working with very large content.
- valgaze 7y agoWhat an excellent & incisive writeup Also worth flagging this great touch-- scroll-aware "scribble"/cross-out animation: https://imgur.com/a/mhPCjNy https://imgur.com/a/mhPCjNy
- _hardwaregeek 7y agoHmm, how do you allow for more complicated updating schemes? Like if I want to update on something other than a shallow comparison? Correct me if I'm wrong, but the dependency array seems to use shallow comparisons. Also, and this is a totally minor quibble, but I hate it when websites have that tiny bit of horizontal scroll. The animations for code size were very pretty though.
- blairanderson 7y agoThe formatting and UX of this article were jawdropping. good job!
- fpgaminer 7y agoIn the few cases I've used them, I've liked Hooks. I just have one big pet peeve with them. Everyone seems to write hooks in Least Significant Function First coding style. This is where you define dependent functions before the code/functions that call them. A short example: function Hello() { // other stuff const callback1 = () => { // do stuff } // other callbacks, effects, etc return <button onClick={callback1}>{label}</button>; } See how the callback functions, and everything else, is defined before the "rendering" part of the function? Compared to, say: function Hello() { const render = () => <button onClick={callback1}>{label}</button>; const callback1 = () => { // do stuff } // other callbacks, effects, etc return render(); } That uses the Most Significant Function First approach, where the higher-order function (render) is defined first, and the functions it depends on are defined after them. That latter example, AFAIK, compiles fine (filling in the elided etcs, of course). To me, MSFF reads better. The higher order function being defined first gives the reader a lay of the land. They provide the highest overview of what the module as a whole does. The functions that follow drill down into increasingly finer details. LSFF is backwards, forcing the reader to understand the finer details of a module without having any clue about their context or the overarching goal until the very end of the code. React specifically, as a reader, I want to know first "What are we rendering?" Only then do I want to then know where the data that goes into that render comes from, the processing of that data, and what user interactions there may be. I see LSFF a lot, especially in React code. Maybe it's just a personal preference, but I just feel like most coders are jumping straight to the "render" part of the code anyway, before looking at the rest of the module. So why write it at the bottom? Why not write the code the way it should be understood?
- ArchReaper 7y agoI'm not sure I follow your question. It's Javascript. Code goes from top to bottom. You can't write: function doThing(target) { console.log(myVar); const myVar = target.prop; } Because that's invalid Javascript. React Hooks are just functions that return markup. If your goal is simply to see what it renders first, that's fine, you simply need to look at what the function returns. I disagree with your assertion that LSFF is 'backwards' - it is simply not what you are used to. MSFF could definitely be argued for class-based components, but it's simply not an option for hook-based components.
- lewisflude 7y agoFrom this thread it seems a lot of people aren't very familiar with React Hooks yet. I highly recommend diving into it. useEffect can replicate lifecycle methods in a way that's much more sensible and easy to read if done right (in my experience of course).
- the_gipsy 7y agoI prefer it when render functions are pure and just render. Given some input, produce html that can dispatch some messages. Deal with the state somewhere else. I don't want to have to reason about when a component mounts, how many ways it could trigger things, that will cause itself to render again and trigger more unexpected things. Why did a component mount? Maybe the route changed, well I will just fetch data where I handle that event. Maybe I got an API response, well guess what I can do stuff right there. All I had to do was let go of the idea of "component". I know, we need to make layers, and isolate modules, separation of concerns. But "components" are unsatisfactory for me, because all I ever got was implicit dependencies. Sure this component deals with his own data, and its children do the same, but somewhere, they DO have to talk to each other at same point, always. The component depends on the user, or user's list of thingies, and bam I've smeared it all across all of them. Because I'm building an application, not a component catalog. Also testing is a PITA. I think most of the time it's useless to test a component without the whole app, and then it's an integration test, but you really want an e2e test by then.
- erikpukinskis 7y ago> I think most of the time it's useless to test a component without the whole app, and then it's an integration test, but you really want an e2e test by then. I tend to agree. With a couple of caveats: On my team we have visual tests that use factory data to just make sure different states of each widget look right. I think it’s pretty valuable as a safety net for CSS refactors. We use Chromatic for this, but they charge per state, so even a medium sized app will get into the $500+ range per month. I also think that, while in practice every front end I’ve worked on is tightly coupled to the backend (making front-end only testing pretty useless), if you build an “optimistic UI”, you can do it. We got close to this at my last company, where we used Vuex to implement a kind of a simple dB on the client, and allow server syncs to kind of build up in the background as needed. You still need to stub some initial requests, but you could test a UI by doing that and then kind of “coasting” in the front end. Eventually you’d hit an interaction that needed the server, but in this way you can test some short sequences of interaction, which is probably enough to test the basic interactions, and leave the E2E tests for actually making sure the front end is wired to the server, db, work queues, etc correctly.
- mikebannister 7y agoExcellent tutorial for person familiar with React lifecycle learning basics of hooks... but mostly came to comment on the exceptionally high quality of the teaching and format of the tutorial. I especially love the "diff view".
- Aeolun 7y agoI just don’t get hooks. People say it’s an easy way to keep your lifecycle events close to the functionality they’re dealing with, but in the end it’s just a clusterfuck of function calls in the render method. As far as I’m concerned, dealing with all your lifecycle events in one or two functions makes it much easier to reason about what is going on. Because hooks look so much like magic, people treat them as magic and when they don’t work as expected, they have no clue why (adding more hooks to solve the problem!). I feel like Reacts’ surface area to shoot yourself in the foot has increased fivefold since the introduction of hooks. The only thing I found really compelling about hooks is the ability to write shared functionality to include in multiple components, but practically I still have to find the first time I actually need that.
- ratww 7y ago> People say it’s an easy way to keep your lifecycle events close to the functionality they’re dealing with, but in the end it’s just a clusterfuck of function calls in the render method. Maybe the code you work on is a special case, but in my experience that sounds exactly like an opportunity to refactor your code into custom hooks, as you mention in your last paragraph.
- artyomavanesov 7y agoThe pace at which React is evolving is both exciting and frustrating. There is so much you can do with all these new tools, but your code is never really up to date. I picked up web development 1.5 year ago and already went through several stages of re-learning. It's great that the ecosystem is developing this rapidly. But it does require significant effort to stay on top of the latest techniques. Especially if you're not a full-time developer.
- ratww 7y agoI wouldn't say that the pace is too fast, it's just that you started at the right time :) Hooks are by far the largest change ever in React, but that happened after many years of stability.
- steve_taylor 7y agoTry creating a hooks-based component that increments an internal counter every second, using useState to track the counter and setInterval inside useEffect to start the timer on mount and stop it before unmounting. Good luck with that.
- andrewstuart 7y agoThis should work: import React, {useState, useEffect} from 'react'; const CounterComponent = React.memo(() => { const [counter, setcounter] = useState(0); useEffect( () => { let counterTimer = null; const incrementCounter = () => setcounter(counter + 1); // counterTimer is null if it has not yet been started if (counterTimer === null) { counterTimer = setInterval(incrementCounter, 1000); } // Remove event listener on cleanup - note the remove function signature needs to match the add function sig return () => clearInterval(counterTimer); }, [counter] ); return <span>{counter}<br/></span>; }); export {CounterComponent};
- steve_taylor 7y agoBut you're setting up and tearing down an interval each time counter changes, so it's no better than setTimeout. Compared to a single setInterval, it's going to gradually drift over time. counterTimer is always going to be null where you're checking it for null. You can't really do it properly without also using useRef to track the value. The reason is that the object returned by useRef is always the same, so it's never stale.
- andrewstuart 7y agoAh yes I recall. The key thing to understand about doing this is to set the state in a context, and do the displaying in another component. There's no point in trying to get the component to re-render when the time value updates. That's the wrong way to program react.
- deviantfero 7y agoClass components are not going anywhere, so if hooks don’t fit your use case, choose the better tool to do it
- nojvek 7y agoHooks are incredibly confusing because of the underlying dark magic. I’ve had multiple people try to explain it to me but when I ask what does useState do under the hood? Can you write a simple version of it, they kinda get stuck. Initial react was super simple. A render function that renders a virtual dom tree. It’s diffed against real tree. View is simply a function of state. You could build a simple react in an hour and show the concepts. New react feels doesn’t share the same ethos of radical simplicity.
- acemarke 7y agoI can describe both of those pretty simply (I think). React uses simple objects called "fibers" to track metadata for every component in the tree. When you begin rendering a function component, React creates a linked list of hooks, and attaches the linked list to the fiber so it knows what hooks exist for that component. Each hook can save some kind of value in the linked list. For `useState`, it's the state value. For `useCallback` or `useMemo`, it's the cached value, and so on. When you render again, React loops over the hooks linked list, reads the value at that index, and returns it to your component. Shawn Wang has a talk where he builds a tiny version of hooks in a few lines: https://www.swyx.io/speaking/react-hooks/ https://www.swyx.io/speaking/react-hooks/
- Izkata 7y ago> It’s diffed against real tree. It isn't, it's diffed against the previous virtual dom, to create a set of patches, which are then applied to the real tree. This is why modifying the dom directly through refs can result in bugs.
- thatswrong0 7y agoI’ve see this argument before but I don’t get it. How is this.setState any simpler? Can you explain how class based components work in any detail? For example, why do we need to call super(props) in the constructor? There’s a whole Dan Abramov post about that one (https://overreacted.io/why-do-we-write-super-props/ https://overreacted.io/why-do-we-write-super-props/)... is that really so straightforward? Or are you just talking about “pure” functional components? How would you build a full featured application with just those? I think it’s actually _less_ magical now, because of the required static order of hook invocation. This implies that each component instance has something like an array backing it that keeps track of hooks calls between renders. This array maybe could hold said state value at the index corresponding to the static ordering of that useState call. So then maybe useState just populates the initial value in the array and provides a callback which both sets the value in the array and then queues up a component re-render
- ummonk 7y agoThis article was extremely difficult for me to follow, even though I already have experience with React Hooks and really like them.
- imtringued 7y agoThis looks like dataflow programming to me and the big downside are the so called "glitches". We want to react to every change but at the same time we want some changes to happen simultaneously. const data = useMemo(() => ( expensive(a,b,c) ), [a,b,c]) Now image a,b,c all change at the same time in response to a button press. How often is expensive() going to be called? 3 times? That will take too long. 1 time? That's unpredictable.
- acemarke 7y agoReact batches updates that were queued from within its own event handlers. So, if you had: setA(1) setB(2) setC(3) in a single click handler, there would be a single render, and a single call to the `useMemo()` callback. On the other hand, if the state setters were called outside of a React event handler (such as a `setTimeout` or somewhere in an async function), each of those would cause a separate render pass, and the `useMemo` callback would end up being called 3 times. Note that in the upcoming Concurrent Mode, React will batch updates across event ticks by default.
- gimboland 7y agoNot a comment on the content, but I really wish people wouldn't publish blogs without an obvious publication date. I learnt react this year; I spent quite a bit of time researching and reading, and obviously there was a _lot_ of ground to cover. In that process, it was really helpful to know whether what I was reading was published a month ago or three years ago. The first words on that page (after the title) are "React introduced hooks one year ago". That may be true today; it won't be in a year, but in a year somebody looking at this page will probably see exactly what I'm looking at now. Not showing a publication date does that somebody a disservice. </moan>