Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I've been using React full-time for a year and a half now, and I've never felt like pure-functional components are a good fit for more than a handful of use-cases. If all you're doing is displaying data, then sure, they're really elegant, but most UI code isn't a pure function of state; it also sends messages back to state, which means the two are tightly coupled, which means pretending they aren't feels gross and weird (I'm looking at you, Redux).


> most UI code isn't a pure function of state; it also sends messages back to state, which means the two are tightly coupled

I think this is what the Flux model is all about, making UI code actually be a pure function of state. By that, I mean literally a function, where state is the argument, and where all you render depends solely on said argument.

If you want to change what is rendered, you send a message and a Store deals with the state modification. Once it's done, it calls your pure function again, this time with a different state, so you get a different render.

This IMO, decouples state and and view instead of tightly coupling it. Redux is the best Flux implementation to demonstrate this IMO because it puts all state in a single Store. The messages to this Store can come from anywhere: UI, server, me typing on the console, etc. The state changes independently of the UI and in fact I could have a full application without having a UI whatsoever. Or I could have a UI without knowledge of my state's topography, because Redux lets me morph and cherrypick my state before I pass it to a component as props.

I think that's a good exercise: to try and build an application thinking of it as a series of actions on state first --and then how that may play onto UI-- to get to appreciate functional components. I'm curious to know why you think why you think Redux only "pretends" they aren't though. (:


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). 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).

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)?" 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).

To be sure, there's not really a great way to decouple things in the reactive architecture. I personally lean towards stateful components that are decoupled at their boundaries instead, which does carry its own downsides. The only solution I've ever seen that truly felt right was two-way databinding, which Vue (sort of) still has, but which has fallen out of fashion in general because it requires "magic" and because it tends to come with performance costs.


> 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?

The entire UI is specific to the UI. Trying to separate "entities" or "server data" from "UI concerns" is IMO a waste of time and just confusing.

At the end of the day, you have a state tree, and you project it to a UI. Where that data comes from and what it represents doesn't really matter. Events in the UI generate an log that can get folded into new state to then be projected again. These events could be generated by a button being clicked, or an XHR request completing, but that doesn't change the nature of the payload.

One important thing though, in most of these models, the state shouldn't be "is this menu open or closed". It should be "Is the user focused on an arbitrary task or not". The menu being open or closed is a projection of that. This way when your designer comes and says "No, this thing shouldn't be a dropdown menu anymore, it should be a modal", you don't feel the need to rename your arbitraryMenuOpen: true state key to arbitraryModalOpen: true instead. Saves a lot of time.

Elm is a pretty good example of how all of this works. Its fairly different from Redux and co (even though it inspired it a lot), but its interesting to see how all of these things absolutely can work. They get messy when retrofit into React and JavaScript, but it's not an issue with the concept, just the implementation.


That level of abstraction sounds nice, but isn't really practical. A dropdown menu and a modal have wildly different use cases and relationships with the rest of the app. The only way the former would directly translate to the latter is if the latter contains nothing but a set of radio buttons, in which case you should hire a new designer.

In fact, the reason we have the particular toolbox of standard GUI elements that we have these days is precisely because they serve different use-cases. Each serves a particular one, and the reason it's survived is because it isn't made redundant by any of the others.


> That level of abstraction sounds nice, but isn't really practical

The only thing not practical about it is that in these days and age, the amount of people with data modeling experience is low. But it totally does work in practice and is, IMO, the only way to really be productive with Redux. Otherwise any change you make or any new feature requires modifying components, actions, AND reducers. At that point you're just doing 3 times the work for everything.


I've seen this idea a lot from members of my team that when you use a state management library then absolutely everything has to go through that state management library. This results in some of that pain you describe with state that's specific to the UI. If you're still working with React, try keeping that menu or input state in the component with setState instead of putting it in the global state store. I've found that simple change to clarify our projects immensely by cutting out lots of unnecessary code and highlighting what state is actually important to the functioning of the application.


Yep, this is exactly why the Redux FAQ has a section with rules of thumb for deciding whether a particular value should go in Redux or not:

https://redux.js.org/faq/organizing-state#do-i-have-to-put-a...


That seems like a good approach. Still, it doesn't seem like the rendering code or the Store code could ever really be worked with in isolation. As I said above, picking a coupling boundary is like picking a poison. They all seem to suck for different reasons; I just personally tend to choose component boundaries.


I'd recommend starting with the application as an abstract entity: start with the state and not think about the UI. Once you build a data-model and action scheme from there, working a de-coupled UI is usually simpler.


> 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.


Your state is tightly coupled with the actions that mutate that state, not the user interface itself. React wants you to define a higher order function for a button. The button will call that function, but the implementation of that function is only loosely coupled.




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

Search: