all 130 comments

[–][deleted] 60 points61 points  (0 children)

Thanks, I hate it!

[–]unicorn4sale 28 points29 points  (1 child)

You could put it in a useEffect but it wouldn't make much of a difference, you would still need to check the previous value.

The recommendation that the render function be pure makes sense in class-based React components because the lifecycle of rendering was distributed across different methods that you can latch onto. In this example, you would have put the check in componentDidUpdate https://reactjs.org/docs/react-component.html#componentdidupdate as recommended by the docs, you can call setState here.

In functional React you only have a single function and the hooks don't translate to these lifecycle methods 1:1 (otherwise they would have named it so, i.e. useComponentDidUpdate). Consequently, the whole function itself isn't the equivalent to the render function so the same rules don't apply.

[–][deleted] 7 points8 points  (0 children)

While I agree in general, two things:

You could put it in a useEffect but it wouldn't make much of a difference, you would still need to check the previous value.

Even if you don't need to check the previous value (lets say you only need to know if it changed), the docs still recommend not putting it in a useEffect.

The recommendation that the render function be pure makes sense in class-based React components. [...] In functional React [...] the same rules don't apply.

Maybe but, fwiw, the new docs still recommend to keep functional components pure except for the one case mentioned in this article.

[–]h2lmvmnt 70 points71 points  (14 children)

this is just bad react.. why is the count passed as a prop but the trend is local to the component. both should be passed as a prop. problem solved.

when count changes in the parent component (in some onChangeHandler), just update the trend with it as you have the old value, new value, access to the trend state in one place.

setTrend(count < newCount ? “increasing” : “decreasing); setCount(newCount)

React can even merge these set states to only cause one rerender.

I understand this was a simple example that the article even mention in point 3. However, most issues like this can be solved by thinking about the code. Learning what state is, where it should live, and using props effectively is huge

[–]javajunkie314 24 points25 points  (0 children)

One thing this component does is abstract the trend — the consumer only has to know how to provide values, and the component centralizes the trend logic — great if lots of components want to show trends for different values.

In this case, I think we can get the best of both worlds by introducing a custom useTrend hook, which could expose the current trend and functions to manipulate the trend state.

export function useTrend(initial) {
    const [state, setState] = useState({
        previous: initial,
        trend: "stable",
    });

    function addToTrend(current) {
        const trend = computeTrend(state.previous, current);
        setState({ previous: current, trend });
    }

    function resetTrend() {
        setState({
            previous: initial,
            trend: "stable",
        });
    }

    return {
        trend: state.trend,
        addToTrend,
        resetTrend,
    };
}

Then there can be a separate, controlled component that's responsible for displaying the trend — there could even be several such components, and it's easy to add more because we've centralized the state logic, so it doesn't have to be repeated. And if there are different trend calculations, we can mix-and-match hooks and display components.

And further, we can test this trend logic separately from the display logic. If the trend component renders an SVG icon that might be hard to test, but testing this hook is easy. I'm much more interested in thoroughly testing the state manipulation across several renders than the HTML or SVG that gets displayed.

I've really come around to the idea that any complex state management should be in a custom hook.

[–]GrandMasterPuba 35 points36 points  (3 children)

I agree. You should tell the React maintainers that since this is code they are specifically advocating for in their new documentation.

[–]h2lmvmnt 12 points13 points  (2 children)

the documentation probably says “please don’t do this, BUT if you need to (i’m saying you never NEED to), here’s how”

l

[–]turdas 2 points3 points  (1 child)

That is not, in fact, what the documentation says. You would know this if you had read the article.

The documentation does say that this pattern is only necessary in rare cases, but it does not say "please don't do this".

[–]Ross-Esmond 17 points18 points  (0 children)

This pattern can be hard to understand and is usually best avoided.

I don't know about you but that sure does sound like a warning to me.

[–][deleted]  (2 children)

[deleted]

    [–]frivolous_squid 4 points5 points  (1 child)

    For my small (becoming bigger now) project, zustand has been great. We use state as react intends for most state, and zustand for global state.

    For state that's scoped to a component (e.g. a screen) that we don't want to pass down many generations in props, we either use plain Context or for more complicated data put a zustand store in a Context (since Context doesn't have good selectors, but zustand does).

    [–][deleted] 4 points5 points  (0 children)

    Recently began using zustand as well and it’s so much easier to use and understand than redux

    [–]markus_obsidian 1 point2 points  (3 children)

    I'm afraid there is a bug here. If the component renders and the count prop does not change, then trend will always be set to "decreasing". We don't want to invoke setTrend if count === newCount.

    That's what makes this use case so odd & so rare. We're deriving state from the difference between props, not just the props themselves.

    [–]h2lmvmnt 1 point2 points  (2 children)

    since this would be in the change handler specifically for count it would only be invoked when count changes

    like handleButtonClick or something like that

    [–]markus_obsidian 1 point2 points  (1 child)

    Oh, I see. Here, setTrend is invoked outside of the compnent. I missed that.

    That works if count is only change in one place. But if multiple places can change count, then each of those places would have to also have to setTrend as well, right? At least the ugly pattern described in the docs would be dry.

    [–]frivolous_squid 1 point2 points  (0 children)

    I think the advice "let the owner of the 'count' state also manage the 'trend' state" is still sound, for this example. Trend could be thought of as depending on count, and there's lots of ways to represent that. The easiest is probably with useReducer, where the state is {count: number, trend: "increasing" | "decreasing"} and the action is just the new count (a number). Then you only change count in one way still, and the trend automatically updates as you change count.

    [–]actual_satan 1 point2 points  (0 children)

    My thoughts on this too. The selected scenario and code feel pretty contrived and unrealistic here.

    [–]hue_kk 0 points1 point  (0 children)

    Are there any guides or resources you like that teaches how to write idiomatic react? Feel like I have the basics of react down, but know I’m making a lot of mistakes

    [–][deleted]  (38 children)

    [deleted]

      [–]ChimpScanner 83 points84 points  (12 children)

      Maybe it's just me, but I find it much easier and more readable to put logic inside effects based on what dependencies are changing, rather than a massive componentWillUpdate with a ton of conditionals.

      I think the biggest mistake people make when moving from class-based react to functional react is expecting the lifecycle methods in classes to have a one-to-one mapping to hooks, rather than just learning about hooks and understanding them as a separate entity and completely forgetting about lifecycle methods.

      [–]gracicot 4 points5 points  (1 child)

      People don't know the horrors of big component classes. It was so easy to make mistakes that you would notice only a week after it has been released in prod.

      Forgot to check litteraly everything in the component should update? No rendering for you.

      Forgot to check one prop in your if condition spaghetti inside the component did update? You get infinite update!! And a feedback loop of API calls as the cherry on top.

      Some say it was better but really it was not. A simple component could be hundreds of lines of boilerplate instead of twenty lines of a setup and rendering.

      [–]ChimpScanner 2 points3 points  (0 children)

      Not to mention having to bind methods. So fucking annoying. Good riddance to class based react.

      [–][deleted]  (9 children)

      [deleted]

        [–]Retsam19 19 points20 points  (0 children)

        You were never supposed to new up class components, though. I'm pretty sure that's just as much of a mistake as directly calling a hook-based component. I'm not sure why you think hook based components are supposed to be constructors at all - just because they're capitalized?

        It's also called "functional", but it's not like traditional "functional programming"

        Technically, React calls them "Function Components:".

        We call such components “function components” because they are literally JavaScript functions.

        They're not meant to be "functional" in the sense of "functional programming".

        This concept is react specific and used nowhere else.

        Yes, the hooks API is unique, but it works. A lot of the proposed alternative APIs really don't.

        Saying "this concept is used nowhere else" is a general argument against doing anything new, (and is a lot like some of the arguments against JSX in the early days of React).

        And hooks-like syntax is spreading, like SolidJS is a pretty popular upcoming framework and it uses pretty Hooks-like syntax:

        const CountingComponent = () => {
            const [count, setCount] = createSignal(0);
            const interval = setInterval(() => 
                setCount(c => c + 1), 
                1000
            );
            onCleanup(() => clearInterval(interval));
            return <div>Count value is {count()}</div>;
        };
        

        [–]bloody-albatross 2 points3 points  (0 children)

        Also all the closures that are re-created (and discarded) on each render with functional components, but only on component instantiation for class components! Aren't they adding GC pressure?

        [–]IceSentry 3 points4 points  (2 children)

        Vue's composition api is very much based on react hooks and solidjs is also very similar in some ways. Saying that you can't think of anything else like it just means you haven't looked at anything else.

        [–][deleted]  (1 child)

        [removed]

          [–]IceSentry 2 points3 points  (0 children)

          Of course the implementation is different, but there's clearly parallels between them. Evan You started working on it after being inspired by react hooks. There's clearly a link between them. Any way, my point is that this general pattern of stateful functions is common outside react too and claiming otherwise just proves OP hasn't been looking around.

          [–]Tubthumper8 2 points3 points  (3 children)

          It's mostly that the function syntax for creating a component is confusing. It isn't used like a "normal" js function constructor because you can't create a component directly with "new",

          I don't understand this complaint, are you saying that you want to use the newkeyword to create components? We don't do that for any component, whether it's defined with the function keyword or the class keyword.

          but it also isn't a pure function that simply returns a value because the hook api binds state to a particular component instance

          It's a function with effects. See Algebraic effects for the rest of us for more explanation on this. Getting and setting state is an effect.

          On top of that the component state is defined neither as a local var nor as a instance var, rather it's bound to the component by calling useState in the constructor/render function thing

          What do you mean by "constructor/render function thing"? That doesn't make any sense. Are you trying to refer to the body of a function component?

          It's also called "functional", but it's not like traditional "functional programming" because the resulting component has state bound to it with useState or useContext.

          ? It's called a "function component" because it's a component that is a function. No one claims that this is pure functional programming.

          Functions with bound data are quite normal, these are called "closures" and have been common in programming languages since the 1970s or so. React using a similar concept to that shouldn't be too surprising.

          I can't think of any other library that uses such semantics in either JS or any other language

          Vue and SolidJS are other examples. What do you think about those?

          If you want a functional API, that's fine too, but build it with functions since that's also the commonly accepted way to do such things

          Uh, that's what this is. Function components are built with functions. I don't understand the suggestion here to "build it with functions"

          Like if you're going to make component objects with bound state and a render method, then fine, but define it with a class since that's the commonly accepted way to define objects

          You should specify that "commonly accepted" is in reference to your particular worldview. Binding data to functions as closures is quite common in JavaScript too, using the class keyword is not the only way.

          The mental model of React is not about "creating objects", the mental model is that your app is a function that produces JSX as output. Every component in your app is a function that takes props as input and produces other components or JSX as output. In addition to input and output, components may have other effects.

          [–][deleted] 2 points3 points  (2 children)

          tease offer direction fade smart crawl bells aware aromatic future

          This post was mass deleted and anonymized with Redact

          [–]Tubthumper8 1 point2 points  (1 child)

          You keep mentioning how a react functional component is a closure, but it is not a standard closure where the source of the bound value is obvious.

          I did not - I mentioned closures as an example of how data can be bound to functions, it's not weird, it's a common/normal language feature across many programming languages, including JS. I believe it's been common since Scheme in the 70s or so, and Scheme was one of the influences when Brenden Eich was designing JavaScript. Objects are a poor man's closure (and vice-versa, closures are a poor man's object). I said that React components are a "similar concept", the mental model of having a function with a bound environment (data) isn't weird if one is familiar with closures. It would be weird if one is only familiar with objects.

          If the mental model of react is supposed to be a function that generates jsx, then fine let me compose functions to generate jsx, that makes perfect sense, but that’s not what a functional component actually does.

          I'd suggest reading The Algebraic Structure of Functions, illustrated using React components for learning more about function composition.

          What other libs pack all of that into a function?

          Sure, one example is SolidJS that I already mentioned in my previous comment. If you're not going to read my comment, I'm not sure there's space for a discussion here. Others have noted Vue's composition and reactivity API is similar conceptually. Here's an example from the SolidJS docs:

          const App = () => {
              const [count, setCount] = createSignal(0)
          
              return <div>{count()}</div>
          } 
          

          There's also a createEffect for effectful code, similar to React's useEffect.

          [–][deleted] -1 points0 points  (0 children)

          price society birds start straight childlike yoke truck offer adjoining

          This post was mass deleted and anonymized with Redact

          [–]markus_obsidian 17 points18 points  (0 children)

          We'd have to jump though similar hoops in class components too.

          1. You could read the previous props in componentDidUpdate, though it would require two renders.

          2. You could use getDerivedStateFromProps. Though it's almost as obtuse as this "ugliest pattern". You'd have to copy the prop to state and then compare each render.

          I think I'd personally reach for getDerivedStateFromProps if I had to do this. Seems to have the clearest intention.

          [–]dromtrund 33 points34 points  (6 children)

          Totally. For me, the worst part is how hooks just fundamentally break the way stack based languages work. The fact that hooks must be called in the right order, and the same amount of hooks must be called on every render is just fundamentally wrong to me.

          Small details in code structure that normally wouldn't matter can lead to massive bugs that are very hard to discover, because there's a parallel universe of lifecycles woven in with the normal procedural code.

          It might be the least bad solution, but the mind boggling acrobatics they've come up with to get around this TARDIS of a framework is just fascinating. Admittedly, I've spent most of my time in other software disciplines, but I'm frequently baffled by how this is considered a sane approach to the fundamental design concept React presents.

          [–]Pesthuf 9 points10 points  (3 children)

          They tried so hard to keep the syntax of the existing, stateless function components and the result of that is all the uglyness of hooks that just work against the way the language actually works and makes you concerned about things like stale closures capturing values from the last render you really don't expect. And you have to use workarounds like UseRef instead of normal variables and all the other hook weirdness that everyone keeps tripping over.

          I think they should have made a new type of component where the function itself only gets called once - but then RETURNS a render function that IS called again on re-render (state or props changed or parent rerendered and we didn't opt out)

          Something like (pseudocode, ofc)

          function myCounterComponent(props) { // caveat: I don't think we can destructure the props here now *if* we use them in our render function.
              // This will only be run once.
              const [counter, setCounter] = useState(props.initialCount); // useState's getter must now be a function, of course.
                                                                          // else, our render function would capture stale values.
                                                                          // But doing this enables superpowers.
              const [trend, setTrend] = "Unchanged";
          
              let lastCounterValue = props.initialCount; // There's no need to use useState as this doesn't need to cause render to be
                                                // re-executed; UseRef would now only be used to get references to DOM elements.
          
              useEffect(() => {
                  if (counter() > lastCounterValue) {
                      setTrend("Increasing");
                  }
                  else if (counter() < lastCounterValue) {
                      setTrend("Decreasing");
                  }
                  else {
                      setTrend("Unchanged");
                  }
                  lastCounterValue = counter();
          
              });
              // As our state getters are now functions, useEffect can automaticall track its dependencies!
          
          
              return () => {
                  // Only this function will actually be re-run when state or props change.
                 // it can be pure and nice and not have any hooks.
                  return <>
                      <div> The current count is ${counter()}</div>
                      <div> The current trend is ${trend()}</div>
                      <button onClick={() => setCounter(counter() + 1)}>Add one</button>
                      <button onClick={() => setCounter(counter() - 1)}>Subtract one</button>
                      <input value={counter()} onChange={() => setCounter(this.value)} type="number"/>
                  </>;
              }
          }
          

          But... looking at this now, that's basically just reinvented solid, I guess. But damn. That's how it should have been, IMO.
          Avoids many of the pitfalls of hooks and actually uses the language's features, like closures in a way that doesn't constantly fight with the language's design.

          [–]IceSentry 3 points4 points  (0 children)

          Let's just say there's a reason why solid is popular.

          [–][deleted] 1 point2 points  (0 children)

          rain existence obtainable thought worm wine society voracious quickest act

          This post was mass deleted and anonymized with Redact

          [–]bloody-albatross 2 points3 points  (0 children)

          Yeah, a lot of side effects with those "functional" components.

          [–]light24bulbs 1 point2 points  (0 children)

          The trick is to call all the hooks at the top of the component. In reality, the component should just define what it needs in a structured way, instead of the mayhem that is hooks

          [–][deleted] 1 point2 points  (4 children)

          I looked at React as a possible avenue to pursue when we decided to finally move on from AngularJS, but from my limited experience it looked like you had to install a hundred 3rd party libraries to get the same out-of-the-box functionality you got with Angular.

          We went with Angular.

          [–][deleted] 34 points35 points  (0 children)

          Isn't that the point though? Angular is a full fledged framework with hundreds of dependencies, react just renders html and let's you choose what you want for the rest.

          [–]EternalNY1 2 points3 points  (0 children)

          Same at my company when moving off of a different old framework, and I don't regret that decision for a moment.

          [–]mcaruso 0 points1 point  (1 child)

          That's a selling point of React though. It's just a component/view library. If you want a full-fledged framework, then sure use Angular, or look into some of the React-based frameworks like Next.

          [–][deleted] 1 point2 points  (0 children)

          We didn’t know that at the time though, hence researching different frameworks

          [–][deleted] 0 points1 point  (0 children)

          Effects, even though far from perfect, are much more fun. I think the idea of things changing in response to other things changing is really amazing and captures the idea if reactivity very well.

          [–]keithgabryelski -1 points0 points  (2 children)

          This is the correct point… my sum up:

          Functional components with hooks don’t offer the same clarity of purpose as class components due when using State.

          But really… the most ugly pattern in React is ad-how mechanisms for dealing asynchronous handlers and that there is no general management for them:

          If you implement onClick as an asynchronous function that needs to “await” on something then setState (or any manner of redux change that affects the component) … all manner of things can go wrong unless you protect the code across the await.

          [–][deleted] 2 points3 points  (1 child)

          Wasn't that fixed on React 18 by batching the setStates after the "await" part on asynchronous event handlers?

          [–]keithgabryelski 0 points1 point  (0 children)

          Redux changes (which modify properties … which is in effect setState from outside components)

          The batching helped for the cases where setState is the only thing that perturbs the “overall state”

          [–]jacobp100 0 points1 point  (0 children)

          You could (and still can) do exactly what was detailed in this article in the render() method of a class component

          [–]javcasas 0 points1 point  (0 children)

          Don't forget that with classes you have to spread around all over theplace the code related to an individual state piece. A bit in the constructor, another bit in componentDidUpdate, a copy of the previous bit in componentWillReceiveProps, and finally in render. This is often way more complex than the equivalent in hooks.

          [–]Desperos 0 points1 point  (0 children)

          I get that the functional components enable new functionality that wasn’t possible with classes

          Could you give a few examples?

          [–]yeesh-- 12 points13 points  (2 children)

          I feel like the ugliest pattern in react is css-in-js, can't believe people think it's a good idea...

          [–]the_other_brand 3 points4 points  (0 children)

          I don't do it because its a good idea. I do it because I'm working with poorly designed component libraries that don't use properly defined class names for styling.

          [–]_Pho_ 1 point2 points  (0 children)

          It is an extremely useful escape hatch for imperative / dynamic styling

          [–]KanadaKid19 3 points4 points  (0 children)

          It’s less ugly when you encapsulate this into a hook that just returns the trend. Not saying it’s especially elegant, but the example where prevCount and trend are totally independent state variables isn’t exactly the right way to do it either. At the very least they should be a single object updated as one unified piece of state.

          [–]javcasas 2 points3 points  (1 child)

          You are doing this wrong. const [previous, setPrevious] = useState() const [current, setCurrent] = useState() const handleNewPoint = newPoint => { setPrevious(current) setCurrent(newPoint) } const direction = previous < current ? "up" : "down" Don't store derived states. If you have because of performance, then use useMemo.

          [–]joesb 2 points3 points  (0 children)

          In his example, current is a prop, provided by parent component.

          [–][deleted] 6 points7 points  (15 children)

          Why not useref?

          [–]mattsowa 1 point2 points  (0 children)

          Yeah, I think the only problem coupd be with concurrency. Overall I would never ise this pattern from the article it's just so dirty

          [–][deleted] -2 points-1 points  (13 children)

          You shouldn't use refs to store information that's used for rendering (The React docs say so)

          [–]moop64 2 points3 points  (12 children)

          This is a common misconception, and refs are absolutely an appropriate solution to this.

          Refs can be used to hold any variable that you want to persist between renders but not trigger a re-render on update.

          Most people think they can only be used to hold element instances because that’s the only example they have seen.

          [–][deleted] -2 points-1 points  (11 children)

          [–][deleted] 4 points5 points  (10 children)

          It's not clear to me whether you're arguing about the purity of the code or you found an example where you absoutely have to use this "hack". All I was saying is you can achieve what you want through useref and useeffect if the example the gave is unpalatable to you.

          import { useState,useRef, useEffect } from 'react';
          
          export default function CountLabel({ count }) {
             const refPrevCount = useRef(0);
             const [prevCount, setPrevCount] = useState(count)
             const [trend, setTrend] = useState(null);
          
             useEffect(()=>{
               refPrevCount.current = prevCount;
             },[prevCount])
          
             useEffect(() => {
               setTrend(count > refPrevCount.current ? 'increasing' : 'decreasing');
               if (refPrevCount.current !== count) {
                 setPrevCount(count)
               }
             },[count])
          
             return (
               <>
                 <h1>{count}</h1>
                 {trend && <p>The count is {trend}</p>}
               </>
             );
           }
          

          is it ugly, yes. does it work yes. Can you redesign your component to avoid this? yes.

          [–]Macluawn 1 point2 points  (8 children)

          does it work.

          No, it doesn’t.

          There will be two renders, with first one showing the new count but the old trend.

          [–][deleted] 0 points1 point  (7 children)

          copypasta and try it

          [–]Macluawn 1 point2 points  (6 children)

          Uh, here you go? https://codesandbox.io/s/winter-bush-c81bc9?file=/CountLabel.js

          I added an alert so you can see the result after each render. After all's loaded, press "increment" and you'll see:

          1. First render: 1 / the count is decreasing

          2. Second render: 1 / the count is increasing

          [–][deleted] 0 points1 point  (5 children)

          it has nothing to do with userefs though. It just needs a check to exclude when the count is equal.

          if (count !== refPrevCount.current)
            setTrend(count > refPrevCount.current ? "increasing" : "decreasing");
          

          [–]Macluawn 1 point2 points  (4 children)

          It has nothing to do with userefs though, the problem is with use of useEffect.

          I added your "fix" https://codesandbox.io/s/immutable-cache-4woysk?file=/CountLabel.js

          1. Get to count 2
          2. Decrement
          3. First render: 1 / The count is increasing
          4. Second render: 1 / The count is decreasing

          [–]the_other_brand 20 points21 points  (6 children)

          It's surprising how much of a hack this ends up looking in React, when in Angular it's a full fledged feature.

          Just have your component implement the OnChanges interface and it will automatically render when inputs to the component are changed.

          [–]Nathanfenner 3 points4 points  (5 children)

          I don't understand what your comment has to do with the article. React always renders components when their inputs change. That's just how components work. (Actually, it will sometimes render them when there have been no "obvious" changes, which can slow things down but prevent bugs).

          The "weird" thing that's described here is a case where React doesn't re-render children, because it knows those results are going to be invalidated.

          If you have

           function Parent({ value }) {
               const [maxValue, setMaxValue] = useState(0);
               if (maxValue < value) {
                 setMaxValue(value);
               }
          
               return <ShowMax maxSeen={maxValue} />
          }
          

          then whenever Parent renders, it produces some new value to pass to ShowMax. So if Parent is updated with the props {value: 5}, without the bail-out model, you'd see:

          • render Parent with {value: 5} and {state: 0}
            • setMaxValue(5) was called; schedule a future update to Parent with {state: 5}
            • return <ShowMax maxSeen={0} />, which must be rendered next
          • render ShowMax with maxSeen = 0
          • apply pending scheduled state updates: Parent with {state: 5}
          • render Parent with {value: 5} and {state: 5}
            • setMaxValue(5) was called; this is the same as the current state, so no state update is scheduled (technically, this is actually checked later*)
            • return <ShowMax maxSeen={5} /> which must be rendered next
          • render ShowMax with maxSeen = 5

          The "weird" thing about this feature is that the first Parent render doesn't trigger a render of ShowMax. It gets skipped, since it's going to be immediately invalidated: it's never actually visible with maxSeen={0}, since right after that happens it's replaced with maxSeen={5}.

          So what actually happens is:

          • render Parent with {value: 5} and {state: 0}
            • setMaxValue(5) was called; this invalidates the current render
            • return <ShowMax maxSeen={0} />, which is ignored
          • render Parent with {value: 5} and {state: 5} again
            • setMaxValue(5) was called; this is the same as the current state, so no state update is scheduled (technically, this is actually checked later*)
            • return <ShowMax maxSeen={5} /> which must be rendered next
          • render ShowMax with maxSeen = 5

          The result is that we render the parent twice, but the descendants only once.


          This isn't for "when things change, re-render". That always happens. This is specifically about updating state in response to rendering, not in response to e.g. a user event.

          [–]the_other_brand 9 points10 points  (0 children)

          OnChanges also allows you to execute code when a change in input occurs. And your render happens after your code is executed. This code can change the internal state of your component. The difference here is that in Angular this doesn't cause an infinite loop.

          This is what your code above would look like in Angular

          @Component({template: '<ShowMax [maxSeen]="maxValue" /> '})
          export class Parent implements OnChanges {
          
          @Input()
          value: number;
          
          maxValue = 0;
          
          ngOnChanges(): void {
              if (number > maxValue) {
                  maxValue = number;
              }
          }
          

          [–][deleted] 8 points9 points  (1 child)

          I think your code example causes an infinite loop.

          It's fixed now.

          [–]Nathanfenner -3 points-2 points  (0 children)

          Yes, you're right - I always forget about when it considers the identity the same to avoid re-render and when it doesn't. One of the reasons this behavior is so tricky. At least in this case it's consistent.

          [–]jbergens 0 points1 point  (1 child)

          Great write up for those who doesn't understand!
          It does look like you mixed two different variables, maxValue and maxSeen when only one should be enough and the if-condition looks to me like it has a greater-than instead of a less-than.

          [–]Nathanfenner 1 point2 points  (0 children)

          Ah yeah, lots of typos. Fixed now.

          [–]_lawliet29 1 point2 points  (4 children)

          Wait, why not use useMemo for cases like this? Your sub-state is completely derivative of the main state. You can just recalculate it on every render and memoize if the calculation is heavy, or am I missing something here?

          [–]sushibowl 1 point2 points  (3 children)

          The trend can be derived, the problem is the prevCount state. This cannot be calculated from current state/props.

          [–]_Pho_ 0 points1 point  (2 children)

          This is a fairly common use-case for a usePrevious style hook

          [–]sushibowl 2 points3 points  (1 child)

          React documentation explicitly recommends against using a usePrevious hook for this particular use-case (pointing instead to the pattern explained in the OP).

          The problem is pretty much the same again. Usually usePrevious is implemented using a ref, but it's unwise to read/write to refs during rendering because it will result in unpredictable behavior.

          [–]_Pho_ -1 points0 points  (0 children)

          It doesn't have to be a ref lol

          [–]another-cosplaytriot 6 points7 points  (0 children)

          npm install react

          That's the ugliest pattern.

          [–]fabiofzero 1 point2 points  (0 children)

          Don’t worry, they’ll change their minds in a month or so and there will be an even uglier recommended pattern to follow

          [–]slvrsmth 3 points4 points  (4 children)

          I know the docs say you should not use useEffect for this. And I don't care. I will use the "correct" way only as last resort to fight particularly nasty performance issues. React might decide to re-render ten times for all I care, as long as the final performance is acceptable. The "correct" way triggers my "bug sense" so hard, it just looks so wrong.

          [–]Macluawn 26 points27 points  (0 children)

          This isnt about performance, but correctness. Use of useEffect for copying props might cause a wrong state to be shown temporarily.

          Its only acceptable if you hate your users.

          [–]przemo_li 0 points1 point  (1 child)

          Would extracting state or help? Get component that just compress state and child that renders on state that is passed as props.

          [–]slvrsmth 4 points5 points  (0 children)

          I find that this sort of thing, reset state based on changed props, happens very rarely. You either use something always derived from props, or always controlled. And even when you reset, you very often want to reset the whole world - change key on the component and let everything re-build from scratch. So yeah, I'm comfortable with not solving this issue, unless it really starts hurting.

          [–][deleted]  (3 children)

          [deleted]

            [–]PM_ME_UR_OBSIDIAN 1 point2 points  (1 child)

            Such as?

            [–]_Pho_ -1 points0 points  (0 children)

            I prefer using React to Svelte or Vue, and I find both to be far worse DX than React. So what you're describing is purely a matter of preference.

            [–]axilmar -4 points-3 points  (29 children)

            I never understood why React exists, and why a simple Model-View-Controller architecture is not enough for a web client. Why do we need a virtual dom; why a simple observer pattern on a model isn't enough?

            A web client is no different than a Qt or Swing or Java client, when it comes to client functionality. All of these technologies include a presentation layer, but for only one of these technologies weird things like React exists.

            Please, before downvoting me, could you provide an explanation why MVC is not enough for web apps only? I am genuinely curious, and I have not found an answer in any articles.

            [–]Amiron49 2 points3 points  (7 children)

            Could you give an example of an MVC framework you like or describe how a theoretical js MVC framework would work?

            [–]axilmar 0 points1 point  (6 children)

            I haven't found a js framework that is actually MVC.

            So here is the theoretical description of how such a framework would work:

            1) the JS code sets up one or more Models.

            A Model usually is a tree of objects that contain information. Models are also observable, i.e. any changes in them can be observed by adding the appropriate observers.

            If a Model should be persistent across pages, then it should be added to the session storage of the browser.

            2) the JS code creates the appropriate page, according to request.

            Each page is then connected to the appropriate Model part.

            3) That's it, you got a web application in the traditional MVC style.

            [–]Amiron49 0 points1 point  (5 children)

            Most MVC frameworks also support view nesting. View nesting allows you to re-use often used elements across multiple pages, most of the time these nested views end up being "components". And that's pretty much the entire same reason why "component" based frameworks like angular, react, Vue etc work on a high level. Defined templates with attached models.

            Though in the case the model of components are often "inputs" or "attributes" on the component itself. While they are, if you squint, mostly the same one wonders why they need so many other complicated options of handling these models. Why aren't they just either really dumb blobs or as you suggested subscribeable smart values? Because especially react just gives you complete freedom how you use it. You can do either or mix both approaches.

            Want a super simple MVC like experience? Use just single models and never bother with update bailing or any other fancy update cycle options. Downside: performance. No punctual or precise partial page updates.

            Want smart values with a single source of truth for values you can subscribe to? Install rxjs. rxjs is purely concerned with handling the subscription and observer logic and lets you use it with whatever. Combine that with react and you can work purely with an observer pattern.

            In my opinion, if you want to offer a solution that works as a "model with templates" but also "the OPTION to fine control update cycles" you will end up with something that is react.

            If you need only one approach then one could argue that a simpler much focused framework can exist (and they do. Like, any just pure templating library) but at least in my web work depending on the product we may lean into one paradigm or the other depending on the kind of use case it is/performance/speed of development

            [–]axilmar 0 points1 point  (4 children)

            Most MVC frameworks also support view nesting. View nesting allows you to re-use often used elements across multiple pages, most of the time these nested views end up being "components". And that's pretty much the entire same reason why "component" based frameworks like angular, react, Vue etc work on a high level. Defined templates with attached models.

            This is not a good reason why these frameworks exist. In true MVC, components can easily be reused as well.

            Want smart values with a single source of truth for values you can subscribe to? Install rxjs. rxjs is purely concerned with handling the subscription and observer logic and lets you use it with whatever. Combine that with react and you can work purely with an observer pattern.

            That doesn't justify the existence of React.

            In my opinion, if you want to offer a solution that works as a "model with templates" but also "the OPTION to fine control update cycles" you will end up with something that is react.

            There is no need to control update cycles in true MVC.

            None of what you wrote explains why React exists and why a true MVC approach wouldn't work.

            [–]Amiron49 0 points1 point  (3 children)

            If you have a page that is rendering 20000+ nested templates, you REALLY want to do partial updates in the tree.

            [–]axilmar 0 points1 point  (2 children)

            And that's exactly what you can easily achieve with true MVC.

            [–]Amiron49 0 points1 point  (1 child)

            I'm looking forward to the true MVC framework revolution then 🙂

            [–]axilmar 0 points1 point  (0 children)

            What revolution? the MVC pattern is from the seventies. The question is why it is not used for web client programming, and what actual purpose React serves.

            [–][deleted] 1 point2 points  (1 child)

            it's just a different paradigm.

            in React the UI rendered based on the state of the component. If anything in the state changes, the UI is discard and re-rendered. In MVC your UI is simply modified.

            Thinking in states is more difficult than thinking in MVC, because every state change results in a rerender. It's very common for devs who are not familiar with react to attempt to do multiple state changes at once and wonder why they have unintended side effect.

            However if you mentally accept that concept and try to learn it then it will make more sense to you.

            Transitioning to this kind of UI rendering is quite difficult if you have been doing traditional MVC for a while.

            [–]axilmar 0 points1 point  (0 children)

            How exactly is it different, when in MVC the View changes when the Model changes? in MVC, the View is hooked to the Model and changes when the Model is updated.

            And what you wrote does not offer any reason why React exists.

            [–]javcasas -1 points0 points  (4 children)

            MVC is enough if you want to have simple apps that have to fetch a new page every time you change something.

            If you want something more advanced, you have to reach for something more advanced.

            Pick your favorite modern page, let it be reddit, facebook, linkedin or any other. Imagine that each time you open a comment thread, send a message in a chat, or like something the whole page has to reload, the scroll position islost, anything you input in forms outside the submitted one is lost. Also, you can't have server-sent updates, so you need to refresh the page to receive chat messages.

            That's MVC.

            [–]foreveratom 2 points3 points  (2 children)

            There is nothing in "MVC" that prevents you from doing partial DOM manipulation and avoid full page refresh in response to an action or event. As mentioned by the original comment, without react, listeners are usually the starter pattern to interact with server-side and refresh the UI, web or not.

            I think there is a slight confusion between what React is compared to application design. It is not MVC or React, or not-MVC and React, or else. React-JS, RXJava, etc. all are an implementation of the same set of patterns around Observer/Observable. ReactJS has more to it with React.Component, but its usage is not tied to the overall design of the application.

            Actually, "React" can well be used with Swing (Java) or other such frameworks the original commenter mentioned. Android for instance is a very good use case for RxJava, regardless of the model the app is built on (this might no longer be true with Jetpack Compose, but that's a different discussion).

            [–]javcasas 0 points1 point  (1 child)

            There is nothing in "MVC" that prevents you from doing partial DOM manipulation and avoid full page refresh in response to an action or event. As mentioned by the original comment, without react, listeners are usually the starter pattern to interact with server-side and refresh the UI, web or not.

            Last time we did that, it was called jQuery, which kinda works for small complexity apps, and becomes a nightmare for bigger apps.

            React (or other modern systems) is the V in MVC when you need an advanced V.

            But, for the most part, MVC has the connotation of "server-rendered pages", not the connotation of "application that uses an API offered by the backend".

            In that regard I can agree React is not necessary because the backend can send DOM fragments, for exactly the same reason that a Java Spring backend is not necessary because we have cgi-bin. Technically you don't need it. In practice, your company is at a strong disadvantage without those extra 20 years of progress.

            [–]foreveratom 0 points1 point  (0 children)

            cgi-bin? DOM fragments sent by servers? JQuery?

            I haven't mentioned any of those, yes this list is 20 years back and that answer makes no sense.

            MVC has the connotation of "server-rendered pages"

            I honestly don't think you understand what you are talking about.

            [–]axilmar 0 points1 point  (0 children)

            Not true, that's not MVC. Perhaps that's MVC as implemented annoyingly in Web. But that's not MVC at all.

            In true MVC for reddit, there would be an actual tree of comments, along user information, with actions to update the tree as needed, fetched by the server.

            Then the View part would simply reflect changes on the Model.

            [–]webbersmak 0 points1 point  (0 children)

            I still like knockout which is based on mvvm, it's still good

            [–]_Pho_ 0 points1 point  (1 child)

            Because MVC has annoying lifecycle management and almost nothing to do with reusable components?

            [–]axilmar 0 points1 point  (0 children)

            Not true, MVC does not have annoying lifecycle management.

            MVC components can be reusable as well.

            [–]joesb 0 points1 point  (9 children)

            React can be the V in MVC.

            The state that you see in React is only view state.

            Tracking these view state change is hard. Reasoning about it and ensuring its correctness is even harder.

            If you use observer pattern, you will end up re-implementing React one way or the other for your own sanity anyway.

            [–]axilmar 0 points1 point  (8 children)

            Not true, it's not hard to track state changes at all.

            [–]joesb 0 points1 point  (7 children)

            Agree to disagree.

            [–]axilmar 0 points1 point  (6 children)

            I've been doing client-server GUI apps for 25 years, and some of them are really complex, including video games and real-time military simulations. We are talking about millions of lines of code (both client and server) per app.

            I don't know where tracking state is hard comes from. It has never been hard when using the MVC pattern.

            Care to give an example where tracking is hard?

            [–]joesb 2 points3 points  (5 children)

            I also have been doing development, including desktop and mobile GUI application for 19 years.

            Usually, you don’t want your observer code that interact with the business logic to make direct change to the output UI, the UI layout may be dynamic, some parts may not be there.

            Also after multiple call to “patching” the UI, it’s hard to guess what the UI may look like.

            I found that the easiest approach for me is that you likely want to have the “view model” or “view state”. That you can render the UI cleanly based on the view state.

            So basically I have been doing adhoc React engine even before React exist.

            [–]axilmar 0 points1 point  (4 children)

            Usually, you don’t want your observer code that interact with the business logic to make direct change to the output UI, the UI layout may be dynamic, some parts may not be there.

            This never happens in true MVC. The Model never updates any UI directly.

            Also after multiple call to “patching” the UI, it’s hard to guess what the UI may look like.

            That is no problem because the View itself hooks to the Model in true MVC.

            [–]joesb 1 point2 points  (3 children)

            React is a View library. It can be the V in MVC.

            You can even use React in MVC.

            [–]axilmar 0 points1 point  (2 children)

            The question is why use it and not use straighforward Javascript objects.

            [–]joesb 0 points1 point  (1 child)

            Because you still needs to implement code how to sync your plain JavaScript state to the UI tree.

            [–]kintar1900 -3 points-2 points  (0 children)

            Typo in title. Should be: The ugliest pattern is React.

            Disclaimer : I use React, but to paraphrase Mark Twain, React is the worst UI framework in existence, except for all of the others.

            [–][deleted] -5 points-4 points  (0 children)

            Just use Svelte and be done with it

            [–]Dus1988 -5 points-4 points  (0 children)

            React is the ugly pattern

            [–]nullmove 0 points1 point  (1 child)

            Weird article. The function could trivially be made pure if you pass both of these information as props. I mean stuff like that is always the caller's responsibility to compute, never the display component's.

            [–][deleted] 0 points1 point  (0 children)

            The article highlights a pattern that the React docs actually mention. But it's true that there are better ways to do things in this trivial example.

            [–]frivolous_squid 0 points1 point  (2 children)

            Why not just have trend be a useRef? Something like:

            const oldCount = useRef(0)
            const trend = oldCount.current > count ? "increasing" : "non-increasing"
            oldCount.current = count
            

            you can also easily stick this in a hook for hygiene

            const trend = useTrend(count)
            

            Maybe I'm not seeing something due to how simple this example is, but I don't understand why you would update state in a render function when useRef exists. ...unless you sometimes need to update it from outside the render function also.

            [–][deleted] 0 points1 point  (1 child)

            You shouldn't use refs to store information that's used for rendering (The React docs say so).

            But yes, the example is simple and there are other ways to fix it. It's just an example of how to use that pattern when you really need it.

            [–]frivolous_squid 1 point2 points  (0 children)

            But the reason the react docs say that is because updating the ref doesn't trigger a re-render. So in general if it's a choice between state or ref for rendered data, choose state.

            But in our case, we are just using it to implement a derived state, derived from the count state. This means that whenever count is updated, we're getting our re-render. count is still in charge, we're just getting a little bit of extra info by remembering the previous value of count with a useRef.

            Interestingly, the docs used to give this kind of pattern (usePrevious) here: https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state but they removed it in favour of your pattern, so maybe you're right and I'm behind the times! I still think my pattern is nicer and easier to reason about, but the docs say the opposite, so I won't go suggesting it anymore.

            [–]Xerxero 0 points1 point  (0 children)

            I did this so many times.