10 ms·
> In the case of modern front end engineering and React especially, you can reduce everything down to two simple concepts... > Rendering the current state > Upd
by notpachet 5y ago
> In the case of modern front end engineering and React especially, you can reduce everything down to two simple concepts...
> Rendering the current state
> Updating the state
Another day, another React team coming to the belated realization that hooks are an inelegant solution to problems that have already long been solved. Separate your view layer from your application state. Get all those network calls the hell out of there. Relegate your components to simple stream transformers -- props in, HTML out. If you're doing anything other than that, your function components aren't really pure functions.
It's frustrating to watch an entire swath of the industry continually rediscover its own inadequacies year after year...
- loh 5y ago> Separate your view layer from your application state. It is actually separated, not from React though because React will need the state (data) at some point anyway. We'll go into more detail on that in the next post. To touch on it briefly here, shared application state exists on its own at the top level of the app, as a composition of stateful hooks. See here: https://github.com/Molecule-dev/molecule-app/tree/6e2456e216767b529c32d16a6952e1ff0858250c/src/App/Store https://github.com/Molecule-dev/molecule-app/tree/6e2456e216... Every possible API request also exists on its own outside of anything React (view). See here: https://github.com/Molecule-dev/molecule-app/tree/6e2456e216767b529c32d16a6952e1ff0858250c/src/API https://github.com/Molecule-dev/molecule-app/tree/6e2456e216... Or for a more specific example, see this API resource route index: https://github.com/Molecule-dev/molecule-app/blob/6e2456e216767b529c32d16a6952e1ff0858250c/src/API/resource/thing/index.ts https://github.com/Molecule-dev/molecule-app/blob/6e2456e216... I may be misunderstanding your complaints though. I appreciate your feedback.
- notpachet 5y agoWhat I mean is that you're violating standard separation of concerns when you have components that themselves are capable of dispatching network calls and updating the state in an async way directly within the components. As a result, those components no longer have an instantaneous view of the universe, and that makes them harder to test, harder to reason about as isolated units of abstraction, and so on. > Every possible API request also exists on its own outside of anything React That's good, but I would take it once step further and disallow any React component from directly invoking those methods. Of course you're always going to need to do something asynchronously when the user clicks a button or what have you. But in my opinion, it's a lot more maintainable to have that just be an event that the component fires, and then have something else listening for that event out-of-band (and then sending the network request / updating the state to say "connecting" or "request failed" and so on). What happens in async land is not really a pure component's business. (I know I'm definitely out of lock step with the current React ethos on this, so permit a crusty neckbeard his pet gripes.)
- loh 5y agoI understand. If you get a chance, please clone the core API and app (https://github.com/Molecule-dev https://github.com/Molecule-dev) and play around with it. Maybe you'll change your mind?
- notpachet 5y agoThat's a fair request. I'll tinker with it the next time I need to spin up a weekend project.
- codecurve 5y agoThis isn't a solved problem. The React ecosystem has spent the last few years trying a model where the application layer separated from the view layer with a pure functional state management solution called Redux. The overwhelming response? People didn't like it. Decoupling systems is a trade-off. Pull your network requests out of your components and you have two bits of code that are easier to test. Indirectly, the component is still going to call that code, and it's up to you to manage the complexity of that indirection. Not every application needs separation of concerns, and in many, colocation of concerns reduces the cognitive burden, because you can reason about components in isolation. To me, that's a more powerful guarantee than a function being strictly pure.
- swyx 5y agoidk if “the overwhelming response” is people dont like Redux. some people are very vocal about their dislike, yes. But 1/3 of survey respondents use Redux despite other choices existing. theres a reason it won the Flux wars.
- codecurve 5y agoMaybe overwhelming was too strong, but I don't see many people getting excited about building new projects with Redux any more. Presumably some proportion of that 1/3 are stuck on Redux, unwillingly? I like (and use) Redux on a daily basis, and I don't think it's a sensible choice for lots of React apps. Maybe the overwhelming rejection that I've witnessed is people discovering that they used it for stuff they shouldn't have.
- azangru 5y ago> theres a reason it won the Flux wars. It won the Flux wars, but there are state management approaches based on paradigms other than flux. There's at least the model that Recoil/Jotai uses (atoms?), the model that MobX/Valtio uses (mutable observable state), the model based on state machines (XState), the model heavily using React context (Constate), etc.
- notpachet 5y agoSorry, but I have stopped using "developers don't like this" as a measure of the technical quality of anything. A lot of developers like things that are pleasurable to them in the small, but harmful to the codebase in the aggregate, especially over years of maintenance cycles.
- phailhaus 5y agoI think you've missed the point if that's your takeaway. The article's solutions actually double down on hooks, proving that they are powerful and expressive enough to manage complex and asynchronous state precisely because they don't couple view logic and application state. That's why a generalized hook like `usePromise` is possible in the first place. The fact that you can trivially drop in a hook to make a component start dealing with asynchronous state validates the hook paradigm for UI development. Remember: components using hooks are pure functions! That's what makes them so effective.
- joshfee 5y agoThey are _not_ pure functions. A pure function will return the same value for the same arguments every time. This is not true of functional components using hooks as the data received from calling the hook can change over time due to side effects.
- imbnwa 5y agoA thought that occurs to me is that React Hooks are Facebook engineering's attempt at an Effect monad in JS that isn't well-typed
- kristiandupont 5y agoDan goes into the topic of algebraic effects in this excellent blogpost: https://overreacted.io/algebraic-effects-for-the-rest-of-us/ https://overreacted.io/algebraic-effects-for-the-rest-of-us/
- imbnwa 5y agoThank you for this, will take a look
- phailhaus 5y ago> A pure function will return the same value for the same arguments every time. Yes, and given the same outputs provided by props + hooks, components will return the same value. That's why you can write hooks functionally, and why we can do away with class-based functions entirely. With the exception of refs, there is no actual internal state that your component needs to deal with.
- nfRfqX5n 5y agoyour preference for model/view/purely presentational components would benefit from using hooks in your model layer as opposed to a class component with the old lifecycle hooks. hooks are the model
- azangru 5y ago> hooks are the model Unless you mock a hook at the module import level (which is hacky), you lose the easy testability of your component. That's long been bothering me about hooks, although I use them plenty.
- joshfee 5y agoAt the end of the day, components will always need a way to interface with state. Often times you want local component state because the cost of abstracting that tiny bit of state into some top level application state is disproportionately high - it just isn't a pragmatic choice. And for application state, ultimately if you have any interactivity in your application (which, if you have state, presumably you do), at _some point_ you're going to have to do something other than "map props to HTML". > If you're doing anything other than that, your function components aren't really pure functions. Nobody claims they are pure functions. They aren't - but that doesn't really matter. Its an API choice that encourages productivity while still having tools for the abstraction that you are advocating for. State hooks can be seen as a sibling of props - both are inputs to the component, but whereas props "push" data into the component, hooks "pull" data from somewhere else. This _is_ separating concerns - when a component needs data that isn't inherently something the calling parent owns, then making it a prop would just be forcing that component's concerns up the tree. Often it is better for it to just be an impl detail. I don't find the article here super compelling, and as a general practice it is often better to author your own custom hooks that encapsulate things like the underlying HTTP request away so that your component is only dealing with its own concerns, but functionally there's no difference and its just a matter of how you split up your code.
- octet1 5y ago> the cost of abstracting that tiny bit of state into some top level application state is disproportionately high - it just isn't a pragmatic choice What other costs are there besides boilerplate?
- joshfee 5y agoIn my experience, the cost of writing and also maintaining code is proportional to not necessarily the (typically fixed) cost of the boilerplate, but the "distance" between the various pieces needed for things to work. Let's say I need a single bit of state in my component. If I store that in a local `useState(false)` and update it with `setState(newValue)`, it is extremely easy to write, and also follow what is happening and how it is happening. But if dogmatically say that this is poor encapsulation - this state should live in some Redux store, with actions and action creators, reducer functions, etc. there is inherent complexity, and I think in the case of a single bit used in a single place that it is pretty easy to see that the complexity is disproportionate to the actual needs at hand. But the way I see it is a sliding scale - your state starts to become more complex? or maybe how the state is updated requires some additional business logic? Maybe you want to start using that state in different places? Any of these reasons can be cause to introduce abstraction, but my preference is to introduce the smallest abstraction that achieves the benefit you're after. Sometimes this abstraction is hoisting state up - now your component doesn't manage its own state, but rather it communicates with its parent via props+callbacks. Now its a little harder to follow (need to consider this component, its parent, and how the prop/callback are being used), but for that complexity we now get to share that state with sibling components. Sometimes this abstraction is moving it to a reusable hook. Now you have a similar scenario where to understand how things are working you have to understand the component and the hook, but for that cost we can now share business logic. Sometimes that abstraction is moving it into a redux store, with all the boilerplate that goes with it. For the right use case this is not a bad thing, but I would only want to do this if I'm getting a proportionate amount of value for the increased cost.
- faizshah 5y agoI have been coding in react full time for the last 8 months in large codebases. I have to say that initially hooks were hard to understand especially useEffect but then as I got comfortable with them they were great to use and I was really productive. But then, as I started shipping production code with react I found that hooks are extremely difficult to debug and very complex to wrap your head around when debugging because of side effects and implicit behavior. I now really dislike hooks because of the amount of implicit behavior and useEffect poisons the codebase to add even more implicit behaviors/side effects to your application. They make the rendering behavior really difficult to reason about. I caught a bug where you had to click a button twice and the reason why it happens is extremely difficult to reason about: https://stackoverflow.com/questions/58106099/react-onclick-not-firing-on-first-click-second-click-behaves-as-expected-simpl https://stackoverflow.com/questions/58106099/react-onclick-n... In my opinion what we need is an explicit state machine of the rendering of a component like vue’s component lifecycle. And these hooks we are using for state management we should instead use xstate to make the state transitions of a component explicit. React hooks (and redux and react-query) are bad (in my opinion) because they are easy to write with and hard to reason about.
- wldcordeiro 5y agoI think that the React devtools haven't kept up with how hooks work. They let you look at your props and state as they are now but not look at the "timeline" like something like the Redux devtools do. My struggle debugging is always trying to look at intermediate states and only having the current app state available, pausing the debugger didn't work as the React devtools crash when you do that.
- imbnwa 5y agoCan you expand on Redux? My gripes w/ Redux is that its pattern is clearly better suited to a runtime with more to offer functional programming than first-class functions, hence all the boilerplate and effort to abstract all that boilerplate, as well as deciding how to pick how you'll get to granular updates (which is where, from where I'm standing, React Hooks' design starts). But in comparison to React Hooks, its vastly superior for affording clear separation of boundaries in an app.
- z3t4 5y agoI heard that React is only the "view" but when actually trying to make an non trivial app (lots of async calls) logic and everything gets entangled. Please show me an app where react is only a view withot side effects
- ajkjk 5y agoI love hooks. Just saying. They're so much better than what came before.
- worldsayshi 5y agoI also really like them but I fear that they are a bit magical, i.e. they solve a problem in a way that feels very simple but might end up blowing up in complexity once you look at them from a certain angle. Like when debugging certain state issues. They are very nice until they break in unexpected ways.
- ajkjk 5y agoI feel that React is like.. 20% of the way to programming nirvana, and hooks are like 24% of the way, but it's very much just another step on the path. They're an inelegant expression of an elegant idea, which class components couldn't express at all.
- worldsayshi 5y agoI haven't really tried it but I would expect that Elm has solved at least some of those problems already. It feels like hooks are (another?) attempt at making react more monad-like but without having the type system to back it up.
- brundolf 5y agoI didn't like Hooks out of principle when I first read about them. I still get slight heebie-jeebies when I think about the fact that they rely on calling-order for identity. But having now used them for six months, I can't deny the practical benefits they bring. I think it helps to not think of them as "just a way of letting you write all your components as functions". That was always a red herring; even before hooks, React "functional components" were impure stateful objects at runtime. Only your part of the code was ever kind-of pure. It was an illusion, and Hooks just pulled back the curtain. I think of Hooks as more like a DSL for declaring externalities, which just happens to take the form of a series of function calls. They're clunky for building complex memoized value-graphs [gazes longingly at MobX], and complex useEffect behaviors can get really hard to follow real quick. But for building average web apps - where 90% of the time you're either pulling in standard externalities or defining little pieces of primitive state - they make things really ergonomic. They also enable some fancy optimizations and scheduling on the React side, from what I understand. Overall I think they were the right move for a for-the-masses UI framework.
- bern4444 5y ago> Hooks as more like a DSL for declaring externalities, That's exactly right in my opinion. Hooks are the place to declare stateful and effectful (aka impure) logic. Using a hook is an equivalent signal to using a Future, or an IO in other languages like Scala. Often combinations of useState and useEffect are best written as a custom hook to reduce noise in the containing component. An easy example can be seen in something like const [ name, setName, isValid ] = useValidatedState(state, validationFunction); Seeing that you can easily imagine how it would be implemented: const useValidatedState = <T>(initialState: T, validationFunction: (val: T) => boolean) => { const [state, setState] = useState(initialState); const [isValid, setIsValid] = useState(validationFunction(initialState); useEffect(() => { setIsValid(validationFunction(state)) } [state]); return [state, setState, isValid]; }; Almost always if there's a useState that has an associated useEffect, it should be turned into its own custom hook.
- city41 5y agoThey also eliminate subtle bugs that can emerge from `this` being mutable in classes, and they make the React lifecycle more intuitive. Rather than thinking about whether the component did mount, will mount or will unmount, you just associate side effects with certain states. Dan Abramov has a nice write up on the pitfalls of mutable this: https://overreacted.io/how-are-function-components-different-from-classes/ https://overreacted.io/how-are-function-components-different... With all that said, I would still say I'm not really a fan of hooks. I more see them as a necessary evil than a great feature. I try to use them as minimally as possible.
- vpfaulkner 5y agoI agree that “separating concerns” is generally a good thing. However, the issue is that the traditional division of concerns is more difficult to maintain in today’s web apps. Compared with web pages 20 years ago, web apps today are dense, interactive and complex. You might have dozens of UI components in a single page, each with their own piece of state, business logic and styling. Moreover, state, business logic, and presentation are oftentimes tightly coupled by design: eg, dragging this slider changes its shading using a complex algorithm. Therefore, it’s becoming more advantageous to decouple individual ui components, each with their own state/logic/styling, than it is to, say, stick all of the state your web app deals with in a single place. In other words, it makes sense to encapsulate all that code related to that crazy slider in one place, even if that includes state, styling, algorithm, etc…
- dstroot 5y ago> In the case of modern front end engineering and React especially, you can reduce everything down to two simple concepts... > Rendering the current state > Updating the state. Not a fan of the negativity in this post (I use hooks all the time) but the advice to separate concerns about managing state and rendering the state is GREAT for ANY architecture.