Hacker Read top | best | new | newcomments | leaders | about | bookmarklet login

From the discussion:

> My favorite way to do data fetching is as close to the place where I am using the data. React made it possible up until this change.

Well, yes, but we are nowadays in a situation where every component interacts directly with the state, or makes calls to the API (eg with the overuse of query), going against one of the architecture principles of react. It's like we're not building components, we're building component sized micro-ui.



sort by: page size:

To some extent, this was the purpose of the "container/presentational" pattern that was popular for a few years [0]. By splitting your components into "containers", which were responsible for managing data fetching, and "presentational" components, which just received data as props and displayed the UI, in theory you could swap out the state layer someday without having to alter most of the display handling.

But, the community really began over-obsessing about that, and often treated it as a rule you _had_ to follow (to the point of people seeming to panic and asking for help about whether a particular component should live in a `/containers` folder, `/components`, or somewhere else).

Dan Abramov, who wrote the article that helped really popularize that approach, later updated it to say he no longer finds it very useful.

In addition, React hooks push you towards a very different approach, where each component is now responsible for calling the hooks that it relies on for data fetching. That hook may still abstract where the data actually comes from, but the calls are now part of the component itself. I talked about this change in approach in a blog post and conference talk conference talk [1] [2].

Finally, the testing approaches in the ecosystem have changed as well. Instead of "shallow rendering" components using the Enzyme library, the community has moved on towards more "integration"-style tests with React Testing Library. This does require more setup work in tests to ensure you have all the various data providers wrapping the components under tests, and real or mock data being loaded, but the tests themselves become simpler and any state library usage becomes basically irrelevant to the actual test implementation. See [3] and [4] for some thoughts on that.

Soooo... yes, you _can_ write more abstraction layers, split your components by "containers", and even add DI via React context or some other purpose-built library if you want to. You could even abstract out all the UI components you use from a particular library just in case you end up swapping date pickers or something. But as always, it's a question of whether that will actually provide a benefit, now or in the future. And in general, most React apps do not bother with those extra abstractions.

[0] https://medium.com/@dan_abramov/smart-and-dumb-components-7c...

[1] https://blog.isquaredsoftware.com/2019/07/blogged-answers-th...

[2] https://blog.isquaredsoftware.com/2019/09/presentation-hooks...

[3] https://kentcdodds.com/blog/testing-implementation-details

[4] https://blog.isquaredsoftware.com/2021/06/the-evolution-of-r...


> Let's say your UI changes, like a redesign... you can rewrite your component and probably need to change most of them.

This is exactly what React does especially for stateless components that purely consume data. The component declartively says this is the data I need. The component doesn't care how that data is retrieved, that's an implementation detail separate from React.

For stateful components, this is also achievable but requires a little engineering and some good architecture (this is IMO the hardest part of 'React' - and this isn't even a concern of React - since React doesn't prescribe any specific approach).

If you define all the functionality for a component well, you should be able to easily rewrite just the view (the return value of a fucntional component or of the render method for class components) without changing any of the existing functionality.

This can be done by defining your component's behaviors via hooks (custom or builtin). Those hooks should provide any functionality and data your view needs and exposes that to the component. If no new data or functionality is needed, then nothing in these hooks (and more broadly code external to the render/return value) changes.

All that changes is the return value of your component and any css you want.

If you change the view, yeah you might have to update some event handlers for buttons that are now inputs, but that's not a UI only change, thats changing functionality too.


You're more or less correct.

One of the not-immediately-obvious problems with the React pattern of passing data in via attrs and actions via callbacks is that the parent component needs to accept all the data/callbacks that all its child components require and its parent in turn needs to do the same. In many projects you wind up needing an additional chunk of data on a child component and that change requiring edits in 8 additional components to pipe around the data.

The Realy/GraphQL (also Falcor from Netflix) idea is that an individual component knows what data it needs and having it describe the data it needs and those requirements being aggregated into a hiearchical query over the graph of data present on the server. Structuring your data requirements this way solves both client side (the problem I mention above) and server side problems (supporting multiple client apps with similar but not the same data requirements, only having to write the code to expose a table as part of a query instead of having to shape it to various endpoints).

At least that's the idea. I find Relay as released to be unpleasant just like I found Flux as originally released to be unpleasant. Now that the code is out there expect a million Relay re-implementations before a rough consensus winner emerges. I expect the winner to look something like a combination of react-resolver with some redux middleware.


Yeah, this is definitely a thing. I'm a bit conflicted over it.

One one hand, staying within the world of React components gives you a lot of functionality for free. And with Suspense and Hooks, the amount you can do within a component has become even greater.

On the other hand, there are still things that you can't do within a component. For example, you can't easily run an async function once when the component mounts - which in my opinion, would be the perfect way to fetch data.

I've been building Navi to try and create a more natural way to map routes to views and a stream of data. The thing is, the more edge cases you cover, the closer it becomes to just building React from scratch.

At this point I still think that routing and data fetching is better handled externally from React, but it's less clear cut than it used to be.


I think this in turn is a symptom of conflating rendering with state management in the first place.

The line between React and vanilla code is blurring more every day - which is a bit greedy for something that's purely meant to be a rendering solution. I would personally advocate to push React as far away from my app code as possible, and instead implement a specialised container class that hooks into React's internals (Controller? Model?) purely to handle localised state changes.


That ceased to be true long ago. How do you use native fetch() with a functional component achieving correct/expected behaviour? It’s not possible without buying into react’s [very opinionated] hooks.

In fact, the toolbox this whole HN entry is about is a great example of how React is not just a rendering engine. It wouldn’t even be necessary otherwise. Peek at the code and what you’ll see is intense use of a DSL and built-in methods provided by React, to wrap native APIs that are otherwise usable without any wrappers.

I’m also a happy user of react-query, but it uses the same underlying hooks which are a moving target.


I really dislike the tendency to couple graphql queries with components. Is this idiomatic? If you are comfortable with side effects in your presentation layer i’d much rather manage data at a higher level and have a component request the data it needs from that. As opposed to actually making HTTP requests.

What happens when two components used in two totally different areas of a single page need common data? Relying on a cache feels like a hack.

I really love React but really dislike the way I see it being used these days. I think of it as a polyfill for a UI component that should be stateless and free of side effects. Not an end to end controller/fetcher/renderer.


It’s a misremembering of history. The point of all the “pure functional” discussion was that rendering the UI now shouldn’t depend on how the UI was rendered earlier. The idea was to move away from the “create then update” paradigm to just “render”. React would be responsible for which elements need to be created and which can be updated in place.

To achieve that, it doesn’t matter whether the state is local to a component or global across the application.


Thank you for formalising this.

We came to a similar conclusion as we moved from imperatively handling server state to using ReactQuery.

What I got to like the most was the small cognitive load. Other than the minimally shared UI state, everything I need to hold in working memory is close by. Not that it's novel or impossible to achieve the same through other means, but that this way of building UIs often leads to a manageable result.

It works very well in cases where the server is the source of truth, but I don't want it to feel like it is.

The creator of ReactQuery talks extensively about the philosophy behind his library in a recent PodRocket episode. [0]

0 - https://podrocket.logrocket.com/tanstack


React Query's critical design flaw (from when I used it, anyway), is that data is scoped globally by default, as opposed to per mount.

Using the global cache is kind of the point, yes, but that's implicitly the case and that's the exact opposite of how useState works.

I think that is an egregious mistake, since it is totally unclear to a higher level consumer that this is the case.

This may seem unreasonable, but I've seen it cause severe issues due to this choice and I would recommend against it for many teams unless they can properly communicate that it does not match the behaviour of useState - you need to look through layers to be convinced it's going to do what you expect.

A simple flag to opt-in to the global cache associated to the provided key would solve a lot of problems.

To summarize, I like the API they provide a fair bit, but it's just too easy to misuse and create a damn mess.


I wouldn't say that data/template proximity is required or desirable in React.

Yes, you _can_ do fetching and rendering in a single function, but that doesn't mean you should.

I will even go a step further and say that, in React, there is no data-part. React is nothing but a really powerful templating system.

(EDIT: It's actually more nuanced, but this is how I approach it.)

The data-layer is usually handled by another library like Redux, Apollo Client or React-Query/useSWR.


> What we settled on, for the most part, is components declaring and fetching the data they need themselves, except for some small, more generic components.

How does this interact with shouldComponentUpdate? Generally it seems that when moving data out of props/state, it's harder to take advantage of the performance hooks that React gives you because you don't have the old and new data to compare when rerendering.


I wrote about the various data fetching techniques in React. Check it out in the link

100%, I agree. This was a game changer for me when I was learning to manage state better back when I was learning React. I realized most state management issues emerged as data structures would evolve and get shoehorned into components. Taking a step back and designing the state’s structure appropriately and then the components accordingly tended to yield far better results. Of course the data structure is informed by a rough idea of future components, but it doesn’t need to follow the UI precisely. It’s a good practice

In this way react is choose your own adventure. There is no current signal that the way class components work will change and I don’t exactly expect that to happen any time soon if at all. Rather then new features are aimed at developers with more complex use cases and make heavy use of asynchronous data fetching in a way that lends itself to concurrency.

If this isn’t you I’m sure at least the general performance improvements will be nice otherwise I don’t see why you can’t just keep doing what you’re doing


I think people need to wake up to the fact that react encourages bad architecture.

You can tell me react is a big brained functional library all you want. Fact is you're putting business logic and mutable state inside your functions from props -> jsx. The fact that setState is a 'hook' doesn't change the fact you're setting state.

Every react code base I've come across looks exactly like what they told us not to do in the WinForms and Java Swing days - code behind.


> Well because the UI isn't just a function of state, it's also a function of those actions (the actions have to literally be passed in to the rendering function).

Wait, they don't though. That's the whole point of functional components. They are of the form:

    const Component = (props) => <JSX />
Where are said "literally passed" actions here?

> So there's bi-directional coupling between the UI code and the Store (the UI code renders from the state, and passes messages back to the store).

This isn't bi-directional binding. state determining UI, UI sending queued actions and actions determining state is one-way data flow. Bi-directional coupling would be tying a reference in the state to a UI element.

> Also, conceptually, you often have a lot of state that's very directly concerned with and specific to the UI, and isn't just idyllic, agnostic "data". Things like "is this menu open or closed?" and "what's the current string value of this input (which isn't meaningful until the form is submitted)?"

This is architectural, and is why components have their own state. Its up to you wether you want to keep this as application state or local volatile state.

> In Flux, those things tend to add a ton of boilerplate in terms of change actions, and split tightly-related concerns into separate corners of the codebase (if I remove this menu component I have to remove its open/closed state variable and the corresponding action).

Which is why I don't put it in the main application state (:

> To be sure, there's not really a great way to decouple things in the reactive architecture.

I mean, not if you're dumping everything on your application state, no.

> I personally lean towards stateful components that are decoupled at their boundaries instead, which does carry its own downsides.

Curious to learn what you mean by "decoupled at their boundaries". You mean components that are de-coupled from each other? You can make re-usable, functional or stateful components that are de-coupled from application state.

I think the main takeaway here is that not all state is the same, there's state that's part of your core application logic and state the component needs for its display only. Treating them as the same thing does limit the re-usability of your code and make dealing with your application state tedious.

> The only solution I've ever seen that truly felt right was two-way databinding, which Vue (sort of) still has...

There's other solutions (: I don't think Netflix, Facebook or AirBnB have all the state of their menus tied up with the state of their user data and and have all components be use-once because of that. Good state management can make things scale, and functional components very useful!

> but which has fallen out of fashion in general because it requires "magic" and because it tends to come with performance costs.

Yup. It's also hard to know where or how certain state is changing. It definitely allows you to build small applications very quickly, but it doesn't scale all that well.


That makes sense. In a certain way, it's doing something similar to react-query

Yeah, that's a fair point. React isn't purely functional, but in practice I'd say it still leans heavily toward a functional approach

You're encouraged to keep state only in top-level components, or in a functional-style state management library like Redux, and pass the data as props/parameters to pure components that are just functions. There's a huge emphasis on immutable data, and composing your various components/functions, passing them as props, etc.

The fact that React switched to using classes makes it seem less functional than it is, and honestly I'm not entirely sure why they decided to do so.

Anyways, you're not wrong, but the point I was trying to make is that React is definitely much more functional than other/older approaches to GUIs, and is popular in (large?) part because of that difference in approach.

next

Legal | privacy