39 pointsby captbaritone9 days ago11 comments
  • mvdtnz9 days ago
    Every time I delve into a large React codebases (and I have worked on some real monsters) I have a laugh to myself at how badly these guys tie themselves up in knots in order to preserve the supposed simplicity of the state -> UI function. When you invent something as insane as the hooks API in order to maintain purity it's time to step back and consider if you're really on the right track.
    • taeric9 days ago
      I want to disagree with you, but I just can't. Every simple example on the different ideas for managing state in React is easy and seems reasonable. Every application I encounter seems to quickly take that and just start laughing at me.
      • CharlieDigital9 days ago
        Something with React's approach is fundamentally broken, IMO, and this is why we see so many variations of state management libraries on React that we don't see on other frameworks because state kind of "just works" and is really, really simple when you are using signals.

        My sense is that in React, the complexity comes from the management of minimizing the "blast radius" of state changes to prevent over-renders. So there are a lot of different approaches and ways that folks have cleverly engineered to solve this problem, but none of them feel as simple as say Pinia on Vue, for example.

      • Rohansi9 days ago
        I think state management is the worst part of using React. All of the popular/highly recommended packages to manage state require you to write code in unconventional ways without really explaining the design decisions. Why use reducers? They're basically different (worse) syntax for mutable object method calls.
        • hombre_fatal9 days ago
          Try MobX. I think it's the holy grail of state management.

          You have your app state in an object/class, and components automatically rerender when the part of the store changes that they access during render.

              class Store {
                counter = 0
                constructor() { makeAutoObservable(this) }
                increment() { this.counter++ }
              }
          
              const Component = observer(() {
               const store = useStore()
               const click = () => store.increment())
               return <button onClick={click}>{store.counter}</button>
              })
          
          It has a very light set of idiosyncrasies for what it gives you unlike, say, redux.
    • eddythompson809 days ago
      To me, the state -> UI paradigm isn't simple in the sense "oh, that was one click, simple". It's simple in the sense "anyone can do it if you just understand/follow these 10-15 simple rules". Once you know these simple rules, you can jump straight into 95% of react projects and be productive fairly quickly.
    • blurker9 days ago
      I've seen some pretty good React codebases and I've seen plenty of backend spaghetti code. In all cases it's not the tools, it's the programmers and usually it's layers and layers of people not taking the time to write clean code. Probably because their management doesn't value it or they don't have someone with the experience necessary to guide them towards clean code.
    • paulddraper9 days ago
      Hooks are the best thing for frontend dev since async/await.
      • LunaSea9 days ago
        I can't count how many issues I've seen due to: missing useEffect dependencies, cyclical hooks, unnecessary rerenders, etc. linked to this API.

        It has a lot of expressivity but is incredibly brittle and dangerous.

        • paulddraper9 days ago
          That’s true of every other GUI tech.

          Angular too many digest cycles anyone?

      • recursive9 days ago
        I would believe that if there were exactly two things in front end.
  • bob10299 days ago
    The #1 reason I push for SSR/vanilla web is to consolidate all state to the server. Literally the only thing on the client could be a session cookie. A few hundred bits of entropy. That's the client's state. Imagine how simple that would be.

    The cost of a round trip for every UI interaction might seem high, but I've never seen a distributed client/server state machine model that could compensate for any of these alleged UX harms without simultaneously bringing in more complexity than anyone was prepared to deal with.

    • adamddev19 days ago
      But then we lose the ability to do anything offline. Offline web apps are still valuable. Some people want to turn their data off sometimes. Many people live in places where internet access is spotty.

      I also love the simplicity of SSR/vanilla web for some things. But I say keep offline-first SPA/PWAs alive. Cross-plaftorm. Freedom from the app stores. Freedom from needing to be tied online all the time.

      • adamddev19 days ago
        When you have a website that needs to be always accessed online, absolutely, give us an old-fashioned SSR vanilla web experience. Just give us a page WITH ALL THE DATA that loads in half a second. Don't make us wait for 5 seconds with spinners and placeholders while you make the client fetch data itself from different sources. This is insanity and torture! People are using client side rendering for the wrong things.

        But there are good and powerful use cases for client side rendering.

      • bob10299 days ago
        > I say keep offline-first SPA/PWAs alive.

        I have no problem with the properly offline-capable apps using standards compliant web technology. I've been championing the use of PWAs to circumvent the iOS App Store for years.

        To be very specific, the problematic solutions in my view tend to be those right in the middle of pure SSR and pure client. They aren't sure if they are always online or offline by default and the actual customer use cases don't seem to have been fully explored.

        Did you ask the customer if a ~150ms form post to us-east-1 from their main office would be something they'd even care about? Why not? You could save an unbelievable amount of frustration if they don't care. It would take 10 minutes to set up a demo they could try if you think it's going to be a deal breaker at delivery time.

        I've not once worked with a B2B customer in the banking, insurance, finance, manufacturing or retail domains who raised a concern about any of this. Only nerds on the internet seem to fantasize about the customer getting outraged at a browser navigation event or a synthetic browser benchmark score.

        • adamddev19 days ago
          I agree. I think it would nice to have basically two tracks, either fully SSR or fully client. I think if they researched what really irritated customers it would be the 2-4 seconds of staring at spinners and placeholders that has become normal. Now we've got instant feedback for browser events but brutally slow load times for our pages to fill out.
      • nkrisc9 days ago
        Freedom from browsers - I'd rather have an executable I can run.
      • LegionMammal9789 days ago
        Yeah, sites demanding a roundtrip for every small interaction can be a pain to use when traveling. The principle I'd wish more web devs would keep in mind is, "Just because the client managed to contact your server once doesn't mean it will have fast and easy access to it in perpetuity." Indeed, in my experience, too many roundtrips is the cause of most atrociously slow websites, regardless of how heavy or light a framework they use.
    • hombre_fatal9 days ago
      That works for a certain class of applications, but it's not very simple once you do want interactivity on the browser client.

      Nor would I call it simple that the server has to be able to send the whole UI over the wire instead of just data. And it ties your server to the web browser instead of providing a simple data API that can be used by anything that can make an http request.

      There are always trade-offs.

    • paulddraper9 days ago
      Was there any traumatic event that induced memory loss?

      The number of broken stateful server-side page that had some weird server state and couldn't handle URL navigation?

      You can f up either way, but I've seen as many crimes with server apps as with client ones.

    • no-dr-onboard9 days ago
      > Literally the only thing on the client could be a session cookie.

      You know, about 7 years ago I would have heartily agreed with you. KISS, right?

      The thing is, it just doesn't make financial, UX, or security sense to do that. The cost of storing every jot and tittle on the backend is huge. The collateral of anything happening to the backend becomes larger. Enjoy benign things like preferences/app settings, unsent comments not having to be rewritten because your session expired, etc? If you're not storing them via local storage, you can KISS that goodbye.

  • veidelis9 days ago
    "This is the famous UI = f(state) mental model of React". Famously incorrect generalization. Why? For example, the useRef hook enables components to hold their own state. React components are not guaranteed to be pure functions. Of course, it can depend on how one writes their code, but it's not a guaranteed that UI = f(state) in React in general.
    • guhidalg9 days ago
      It's a mental model, not how it works. Your computer isn't actually executing C code, but it's helpful to think that it does.

      If you write React code that strays from that model, you better know what you're doing. When I have to reach for `useRef`, I know that I'm in dangerous water.

    • 10000truths9 days ago
      This is needlessly pedantic. useRef/useEffect are tools to implement the model on top of an imperative reality. Things like canvas rendering APIs don't have a pure interface, but it's still obviously very useful to provide one (hence libraries like react-konva and react-three-fiber).
    • ketzo9 days ago
      "All mental models are incorrect; some mental models are useful"
  • kccqzy9 days ago
    The most direct way of solving the immediate problem is to make transitions idempotent. Why must it be an error to complete an already complete TODO? Completing an already complete TODO should be a no-op. That simplifies things greatly.

    Of course many actions logically cannot be made idempotent, but for the subset that can, do it as much as possible.

    • a_wild_dandan9 days ago
      Exposing invalid transitions to a user is a bug. Idempotency here doesn't solve anything, just hides said bug, which is arguably worse.
      • antonvs9 days ago
        A race condition for which of multiple concurrent users initiated a transition is not a bug, it's a scenario that commonly needs to be handled. In many cases, idempotency can be a simple and effective approach for handling this.
  • whalesalad9 days ago
    Why does it feel like React (not just the lib but the community/ecosystem/everything) took something as straightforward and easy to understand as functional programming and surrounded it with so much fluffy pomp and circumstance that it is unrecognizable?
    • ketzo9 days ago
      The answer -- as it is with every version of "why is this JS so complicated?" -- is that frontend web dev:

      - is the default way that most humans interact with computers

      - is an extraordinarily broad problem space and solution space

      - is something that basically every modern software company needs to do in some capacity, and with even a few developers, abstractions become desirable

      I'm not saying there wasn't a better way for React to adopt FP principles. I certainly have my own gripes.

      But to start a conversation with "why does the most widely adopted framework for the most broadly-used software interface in history have some rough edges?" seems, to me, to be sort of begging the question.

    • hombre_fatal9 days ago
      Because (1) UI app development isn't simple, so there are many ways to cook the egg, and (2) React isn't opinionated, so there are a lot of competing options, and it has quite a large surface area of use-cases.
  • dvtkrlbs9 days ago
    I dont think this is really true in practice. Most of the time there is context and 3rd party sources components get from. It is good idea to have your actual view use this paradigm though.
  • the_gipsy9 days ago
    I've never seen a Teact project where UI = f(state). There is always heavy reliance on the "lifecycle" of components. So you compose "functions", but every function is using some global state, shared or not, it's meaningless to describe it as "functions".

    The one project I've used that had redux was also a complete nightmare to work with, and hooks are the blessed way now apparently.

    • AstroBen8 days ago
      The problem is most react developers are using the library because they want a job and see companies are using it, not because they believe in or even understand functional programming

      The amount of times I've heard that classes = OOP and functions = functional is ridiculous

    • kccqzy9 days ago
      It doesn't have to be this way for most apps. One reason I've seen why people heavily rely on the lifecycle methods is that UI = f(state) isn't fast enough. But in more powerful languages like ClojureScript, you can keep track of which part of the state is accessed, and subsequently rerun only parts of the f that needs to rerun. As I understand, there is something called React compiler (https://github.com/facebook/react/tree/main/compiler) that tries to do this. But in a better language, this doesn't need to be a standalone tool, just a macro.
      • the_gipsy7 days ago
        I agree, Elm also does UI=f(s) really nicely. But it's not something you can do in JavaScript while also keeping everything JavaScript.
  • dfabulich9 days ago
    > A React application can be thought of as modeling a state machine. Each render takes a state and produces the UI for that state. This is the famous UI = f(state) mental model of React. But, for complex applications, formally enumerating a transition table is often not feasible. When the number of possible states is not finite, a table will not suffice and we must instead define a mapping: a conceptual function which takes in a state and returns the set of valid transitions for that state.

    Not really, though. While you can model any program as a state machine, doing so typically adds nothing to your understanding of the program.

    State-machine diagrams are especially useless. A diagram is only useful if it has about a dozen things in it, maybe two dozen, maximum. But that doesn't mean you can model a dozen states in a state-machine diagram; in these diagrams, the transitions, the arrows between the states, are at least as important as the states themselves. So the diagrams are useless when the number of states + the number of transitions between them are greater than a few dozen. Typically that means state diagrams are only with a handful of states with a few transitions hanging off of each.

    But, if your problem is simple enough that you can represent it with a handful of states with two or three transitions each, then you have a very trivial problem, so trivial that the state-machine diagram likely added nothing at all to your understanding of it.

    This is why no popular UI frameworks actually do model UI as a set of states with transitions. It's easier to model the state diagram as a function and then just forget about the state diagram.

    That's what React is all about! A pure function, UI = f(state). It's better than a state diagram.

    This article is saying: "Hey, you could think of React, something conceptually simple, as something unnecessarily complicated, instead!" Gee, thanks?

    • captbaritone9 days ago
      Author here. I think we might actually be in agreement. My point is that in React you don't formally describe the transition table because (and I think this is where we agree) that's infeasible for an app of any reasonable size.

      The observation I'm trying to capture in this post is that even though we don't define a formal transition table, we actually _do_ implicitly define the set of valid (user) transitions for each state via the event handlers we bind into the DOM when our React component tree renders that state.

    • tshaddox9 days ago
      > But, if your problem is simple enough that you can represent it with a handful of states with two or three transitions each, then you have a very trivial problem, so trivial that the state-machine diagram likely added nothing at all to your understanding of it.

      And yet, it's extremely common to see apps with clearly broken states and state transitions for what should be relatively "trivial" state machines. Think play/pause buttons, buttons with loading states, form fields with error states, etc.

  • tripplyons9 days ago
    I might just not be aware of a better alternative for React hooks, but I don't like useEffect. I feel like it makes it much more difficult to manage state and transitions compared to SolidJS or other frameworks that use signals.
    • alpinisme9 days ago
      There’s no denying that the idiomatic solution is sometimes far from obvious, but idiomatic react wants useEffect to only about synchronizing react with external systems. Everyone reaches for it to synchronize between components and do all sorts of other non-idiomatic things though, and that’s where the pain comes in.
      • Jcampuzano29 days ago
        A lot of the time it comes down to the platform not actually providing easy ways to do things that users are reaching for effect for.

        For example some people reach for it for fetching data in routes. It wasn't until recently that routers started to come with built in patterns to do this without effects - example being react routers (it's been a while since I used but remix had this) loaders and tanstack routers beforeload and loaders.

        People fetched in an effect because at the time this was the most simple and obvious place to do it unless you were very aware of the other ways to do it, and we didn't have primitives like react query either.

        Another example of a non obvious things logging an analytics event when a specific component renders. You could attempt to tie this to the specific update that caused a component to show but that update may be very complex or hidden since your component could show based on a variety of states and require so much more work vs just logging in an effect.

        I guess one could argue both of these themselves are syncing operations, syncing your network state and server state for API requests in routes and analytics. But at the same time reacts team told everyone if you're fetching in effects you're doing it wrong without providing the answers.

        That to say yes effects are not a very good pattern for many things we use it for and should be avoided, but react as a framework (yes it's basically framework in my opinion, we're well past just. library point) itself does not educate well or have easy to understand built in ways to do these things that are VERY common use cases

        And this as someone who writes mostly react.

        • Secretmapper9 days ago
          I think everything you said is 100% true. But the latter half is also true - React has (for better or worse) always been a view 'library' and has very little opinion on syncing network state/api requests.

          Unfortunately, many people use inappropriate levels of abstraction (useEffect and/or redux) and it becomes an architectural problem.

      • LegionMammal9789 days ago
        Out of curiosity, I just looked through some of my old code calling useEffect. Most of it was for fetching data from an API on mount; I'd also written a custom little hook that returns a callback to signal that the data should be refreshed.

        But a few instances were to conditionally set one piece of state whenever another piece of state was changed, arguably an abuse of the mechanism. I suppose the proper way would be to wrap the setter function into one that changes both, but it takes a fair bit of discipline to avoid useEffect in the heat of the moment.

        • wk_end9 days ago
          There’s an excellent article in the React documentation about this (“You Might Not Need An Effect”). Back when I was working on a React team I probably threw it at a code review on average once a week.

          I really like React, but given the way developers seem to struggle to use it “correctly” (despite all the lint hooks and weird diagnostics like double rendering to help) it’s hard not to feel like there’s something wrong with it.

        • marksomnian9 days ago
          > But a few instances were to conditionally set one piece of state whenever another piece of state was changed

          That use case is explicitly called out on the "You Might Not Need An Effect" article in the docs (which everyone writing React should read, and arguably was published years too late): https://react.dev/learn/you-might-not-need-an-effect

          TLDR:

          When updating a useState based on another useState, don't use the first useState at all, just compute it inline. If it's expensive, wrap it in a useMemo.

          When updating a useState based on props, call the setter directly from the component function, and React will immediately re-render the component (instead of rendering it, running the effect, and then rendering it again).

          • LegionMammal9789 days ago
            Believe me, I've read that essay from start to finish, but as I mentioned, it was in the heat of the moment and I didn't have much time to architect something more proper.

            > When updating a useState based on another useState, don't use the first useState at all, just compute it inline. If it's expensive, wrap it in a useMemo.

            Well, the problem in this case was that the affected useState was not just a pure function of the useState that caused it to be modified: other actions could modify it as well. (E.g., you have some form state that should get updated whenever a state value somewhere else is changed.)

            I believe useReducer is a bit closer to the use case I'm thinking of, but the dispatch functions for that are verbose and unpleasant to write. Presumably ad-hoc closures wrapping the setter functions would be somewhat more lightweight.

  • jschrf9 days ago
    The vast majority of state problems in React are a result of nonsense cargo culting around the idea that classes are somehow bad.
    • kccqzy9 days ago
      I'm glad I learned React during the time when React.createClass was the only way. It was simple and intuitive. At that time the docs included a very lengthy discussion of what should be put inside the class and what should be passed via props. It was very helpful when it comes to teaching newcomers to architect their app. It also carried over the years of intuition by the typical dev working with OOP. Much better than the current trend of using hooks for everything.
    • blurker9 days ago
      I disagree. With classes, everything was stateful (because, y'know, classes). People were doing all sorts of crazy thing with the lifecycle methods and it was always a pain to have to remember the "this scope" and bind your event handlers. I saw so many bugs written by people who lost track of what "this" was.

      Both paradigms have foot guns but having used both I much prefer the hook version.

    • recursive9 days ago
      IMO it's the central tenet to the dogma that "state must not be mutable".
      • geetee9 days ago
        The absolute nightmares people create in order to attain this ideal...
  • revskill9 days ago
    That is a valid way of thinking.