[HN Gopher] Common Beginner Mistakes with React ___________________________________________________________________ Common Beginner Mistakes with React Author : vinnyglennon Score : 55 points Date : 2023-03-11 14:48 UTC (8 hours ago) (HTM) web link (www.joshwcomeau.com) (TXT) w3m dump (www.joshwcomeau.com) | petilon wrote: | One of the biggest issues in React that I have come across is | that you have to make copies of objects and arrays (as mentioned | in this article) when calling setState(). Cloning works well for | small arrays and objects (I use the code below for that), but for | large arrays with thousands of items (for example, datagrid | scenario) this is inefficient. My solution is to modify the | array/object directly then just call setState({}). Does anyone | have a better solution? export function | deepClone<T>(obj: T): T { if (obj == null || typeof | obj !== 'object') return obj; | const clone = new (obj as any).constructor(); for | (let key in obj) { if (obj.hasOwnProperty(key)) | clone[key] = deepClone(obj[key]); } | return clone; } | paulddraper wrote: | Yes, that's the best way. Fortunately, there are relatively few | components that need a hack like that. | | PS Why did you post code a `deepClone` function? You certainly | don't need to deep clone. | petilon wrote: | deepClone is convenient, when you want to modify a field | that's deep inside the object. | nicoburns wrote: | You should only be cloning the modified object, not every | single object in the array. That's what making things slow! | davely wrote: | Check out useReducer and all your problems go away! (It has no | relation to Redux, many people think this but it's a native | React hook that makes updating keys within an object much | easier) | petilon wrote: | Just read the docs for useReducer. It says you should not | change the original object. You have to make a copy. | mrkeen wrote: | Try going all in on persistent data structures. I haven't tried | this one but perhaps it will do the trick: | https://github.com/cronokirby/persistent-ts | petilon wrote: | I prefer to use POJO (plain old JavaScript objects). | phailhaus wrote: | You only need to update the objects that actually changed, | and only along the path of the keys that changed. Deep | cloning is overkill because you'll incur way too many | updates. | | For example: const [largeArray, | setLargeArray] = useState<MyObj[]>([]); const | updateObject = (index: number) => (newObj: MyObj) => { | setLargeArray(prevArray => ([ | ...prevArray.slice(0, index), newObj, | ...prevArray.slice(index+1), ])); } | acemarke wrote: | Deep cloning is almost never the right answer - you'll end up | copying _too many_ object references, which is extra work, and | that can also lead to unnecessary re-renders depending on what | your components are trying to do. | | A correct immutable update is more like a "nested shallow | update", copying all levels of nested keypaths leading to the | actual value you want to update. | | If you prefer to avoid writing those immutable operations by | hand, Immer is a fantastic tool for simplifying the code by | using "mutating" syntax that gets turned into a safe and | correct immutable update: | | - https://immerjs.github.io/immer/ | | - https://beta.reactjs.org/learn/updating-objects-in- | state#wri... | | We even use Immer by default in Redux Toolkit: | | - https://redux-toolkit.js.org/usage/immer-reducers | petilon wrote: | immer makes an "efficient copy" of the object, right? Keeping | references to some nested objects in the original object when | possible? If so that's cool... but not sure it is worth the | effort. | acemarke wrote: | Immer does a correct nested immutable update, same as if | you wrote the corresponding nested spread operations by | hand. It _only_ updates the nesting paths that led to the | value that got modified. | | So yes, if you have a nested object with a bunch of | different fields, and you update `state.a.b.c = 123`, it | makes copies of `b`, `a`, and `state`, but preserves all | other nested references that are anywhere inside of | `state`. | | It's the same as if you wrote: return { | ...state, a: { ...state.a, | b: { ...state.a.b, c: 123 | } } } | | but obviously much shorter and easier to read / maintain :) | | It also conveniently eliminates the chance of a _real_ | accidental mutation (which in the case of Redux was always | the #1 cause of bugs in Redux apps). | petilon wrote: | That's awesome! It lets me use POJO (Plain Old JavaScript | Objects), and it makes efficient copies? Definitely going | to give this a try. | [deleted] | bayesian_horse wrote: | This is what the "immutability-helper" library and immer are | used for. Or Immutablejs to go even more fancy. | | It's not that expensive. You don't need to deep clone an array | to change one element, you just create a new array linking to | all the old elements, except the one you want to change plus | the new version. If you understand "referential equality", this | will make a lot more sense. | Nihilartikel wrote: | I love React as long as it has a thin skim of clojurescript over | top. Rum is the underdog compared to reagent but is still my | weapon of choice - https://github.com/tonsky/rum | | Was disillusioned when I had to dive into a pure js project using | React. | | The real benefit, I think, is that you get the well established | Clojure idioms around isolating and managing mutable state. | | State is stored in a Atom, which is atomically mutated, and | reactive components essentially 'subscribe' to updates upon that | atom to re render. | | The mutations can be handled centrally by a message queue, but | really, event sourcing like that is not always needed. | bayesian_horse wrote: | Depends on the perspective. I know a bit of Clojure, but for me | those idioms are hardly "well established". For me, the | patterns of React, Typescript and Redux are well established... | localghost3000 wrote: | I'd like to add improper use of useEffect to this list. useEffect | is an escape hatch but I often see devs using it as some kind of | ad-hoc event driven system approach. useEffect divorces the | outcome of an action from its invocation which is problematic. It | can lead to some tricky state management scenarios really | quickly. | | And as a side note: can we *please* stop shitting on every React | post that appears on HN? All this hater energy is really starting | to be a drag. | phailhaus wrote: | I would say useEffect is not an escape hatch, it's a | fundamental building block of the hooks paradigm. You need some | way to run impure side effects after your component has been | rendered, and `useEffect` is how you register those effects. | | The most basic example is making an external request based on | props: can't do that in the render loop because it has to be | pure, since React may run it multiple times before committing | to the DOM. `useEffect` is the only way you can get a guarantee | that it will only be run once after rendering, and only when | its dependencies change. | paulddraper wrote: | Right. It's how mutations are done. | | But there is a lot of mutable code that could be immutable, | and `useEffect` is how that happens in React. | phailhaus wrote: | Sorry, what do you mean by mutable vs immutable code? I | don't think it necessarily has to do with mutability, | because you can "mutate state" in regular callbacks without | useEffect. The only way to get true mutability in React is | via refs, all other state is immutable. | bayesian_horse wrote: | The last statement isn't true unless you mean | "mutability" in just the DOM elements. | | All sorts of things can carry state and be mutable in a | React application. Even the properties of a component are | actually mutable, and it sometimes happens that people | pass around arrays or objects and mutate them when they | shouldn't. | phailhaus wrote: | Well, javascript is mutable so technically nothing is | immutable. But I'm referring to working with React's | hooks, where all props and state should be treated as | immutable except for refs. | bayesian_horse wrote: | Refs are just as immutable in that perspective. And no, | you're not supposed to mutate ref.current.anything | outside useEffect or a callback or similar... | phailhaus wrote: | Of course you can! That's the whole point of refs, and | why you can't use them as a dependency to callbacks or | effects. They're totally mutable, which makes them very | tricky to deal with. I mean, I wouldn't _recommend_ | updating them outside of effects /callbacks, but it's | valid. For example: you can make a ref to count the | number of times React calls your render loop by updating | it in the body. | paulddraper wrote: | I mean pure vs impure. | | > you can "mutate state" in regular callbacks without | useEffect | | Yes, via `useState`. That too gets overused. (Note: | useEffect and useState are 100% necessary, but also | easily overused.) | | useEffect for when you want to manipulate state _outside_ | the component. I.e. change the document title, exchange | data with an HTTP server, store data in localStorage. | bayesian_horse wrote: | Also: even with hooks, the component is still a pure | function in a less low-level kind of way. | | The hooks yield effect descriptions, to be carried out | later, on a side channel. So the component is still a | pure map of the inputs to the outputs, just that the | outputs are not just comprised of the javascript | function's return value, but also the effects on the side | channel. | vlunkr wrote: | This is definitely true, but I also think it's overused in | typical UI components. I often see patterns where some user | event triggers a state change, which triggers a useEffect | hook. In most cases, you could instead have your event | handler directly trigger your side-effect code. Adding | useEffect into the mix is a huge point of failure because, | well, it sucks. Until you get that chain of deps just right | it's not going to run when you think it is, or it's going to | run with some stale values. | kkirsche wrote: | https://beta.reactjs.org/learn/you-might-not-need-an-effect | | The beta react docs have a number of great examples of this! | wetpaws wrote: | Using functional components would be a mistake in general. | azangru wrote: | Calling useEffect an escape hatch is as much of a recent fad as | shitting on react. How can it be an escape hatch, when it is | the only api provided by react to perform side effects during | the rendering cycle, which do not themselves pertain to | rendering? Calling it an escape hatch is the same as calling | componentDidMount or componentDidUpdate methods of the class- | based api escape hatches. | hyperhello wrote: | I'm not against React but it sure seems like it's more | complicated than vanilla JS. The hater energy might be a clue | knocking at the door, trying to quietly inform you of something | you don't want to hear. | MatthiasPortzel wrote: | The people who work professionally with react all day are | well aware of React's problems. But I'm a front end | developer. I'm not about to change careers and become a | database administrator because managing state in React is | unintuitive. (And while there are other frontend frameworks | and solutions, they have their own problems, and it's more | difficult to find a job using them.) | phailhaus wrote: | Lol "quietly". React has been around for over ten years with | one of the largest grassroots ecosystems built around it. | Maybe that should be a clue that it has something to offer? | | I think HN is averse to it because it's blamed for "how | complicated the web has become". If only we would all settle | for simple static websites! | hyperhello wrote: | It's not binary. There can be reasons to use it and reasons | not to. | blowski wrote: | Maybe, but it's more likely to be boring noise from immature | script kiddies who only want to use the newest tech because | it's cool. | threatofrain wrote: | If I'm not mistaken, the issue with trying to read your writes is | more about the fact that JS has no opportunity to update the | variable until you run the function again: | const [state, setState] = useHook("a") ... | setState("b") console.log(state) | | I could imagine React adopting Signals and ending up with | something like: const [accessor, setState] = | useSignal("a") ... setState("b") | console.log(accessor()) | phailhaus wrote: | It's not about JS, it's because React won't let you render an | inconsistent view of state. `setState` allows it to queue up | multiple state changes, commit them all, and only _then_ re- | render your component. So you should think of `setState` as | "queuing up a change to be applied for the next render cycle". | threatofrain wrote: | Thanks for the clarification. I had thought that React | updates state immediately and may preform multiple re-renders | prior to committing to the DOM, and it's the re-renders which | are queued. | | I wonder how Preact works with Signals, then? | acemarke wrote: | React _usually_ queues up a render, which is then executed | at the end of the event loop tick. This allows many calls | to `setState()` in the same tick to result in a single | render pass at the end. | | The default behavior is that _if_ there are any changes | that are needed based on the render, React then continues | ahead and applies them to the DOM, and most of the time | that is done immediately. | | When a render is queued, React keeps the info about the | requested state update as part of the queue, and those | state changes are calculated and applied as part of the | "render phase". So, if you happen to call `setState(2); | setState(3);`, the second call completely overrides the | first and there won't even be an attempt to render the | component using the `2` value. (You can use the | `setState(prevValue => 3)` form to force React to do each | value separately.) | | As of React 18, the new "concurrent rendering" features | like `startTransition` and Suspense allow React to alter | priorities of renders, and split the calculation into | multiple chunks over time with pauses in between. When | React _is_ done determining any changes in that render | pass, it still applies them to the DOM in a single | synchronous "commit phase". | | The other caveat is that React will sometimes execute a | full render+commit synchronously under specific conditions, | such as a `setState` inside of a `useLayoutEffect` hook. | | I wrote an extensive detailed post called "A (Mostly) | Complete Guide to React Rendering Behavior" that tries to | explain a lot of these nuances: | | - https://blog.isquaredsoftware.com/2020/05/blogged- | answers-a-... | watters wrote: | [flagged] | shrimp_emoji wrote: | [flagged] | waynesonfire wrote: | what would you recommend i install instead? | | well, great timing, "Ask HN: What's the fastest and simplest | way to prototype a web app in 2023?" [0] is on the front page. | It seems like every year the list changes. Seems like everyone | uses react but nobody recommends it. Such a bizarre ecosystem. | | [0] https://news.ycombinator.com/item?id=35110976 | phailhaus wrote: | This is the HN bubble, which holds that React is "just a fad" | despite being one of the few libraries that have now lasted | for over a decade. | [deleted] | paulddraper wrote: | Great list! | | > Missing whitespace. I've since realized that there is no | perfect solution to this problem. | | Indeed :) | | HTML had the same problem, albeit with different and equally | frustrating choices. | | The problem is inherent to markup languages: difficulty | separating intentional content from internal formatting. That is | the both the feature and bug of markup. | caxco93 wrote: | For the examples regarding Mutating state and Accessing state | after changing it I believe it would've been a great chance to | teach about "functional updates" on the `set` functions, which | can be accomplished by passing a function as an argument instead | of the `nextState` value. (ref | https://beta.reactjs.org/reference/react/useState#setstate) | chadlavi wrote: | This is also a much easier to read way of doing it. | | For others' context, it's something like this: | setFoo((f) => f + 1); | jotaen wrote: | Regarding the usage of `crypto.randomUUID()` to generate unique | `key` properties for list items, such a technique should only be | necessary if the items don't come with unique and stable ids on | their own. This is the case for the example in the article, but | in my experience, list items often do have some differentiator on | their own, which you can either use directly, or otherwise derive | for that purpose. If that's the case, this is what I usually | would prefer by default. | | One other aspect that I find important to understand about the | `key` property is that it only has to be unique among its direct | siblings, but it doesn't have to be unique globally. | mediumdeviation wrote: | Right, I'm not sure the author understands key either. It's an | optimization to help React identify insertion / removal / | reordering of array items between re-render. | https://beta.reactjs.org/learn/rendering-lists#where-to-get-... | | Giving every item a new key on every update does not help with | that - in fact it is likely strictly worse than just using | index since it will cause every item to re-mount every update | https://beta.reactjs.org/learn/preserving-and-resetting-stat... | which is unlikely to be what you want. | Swizec wrote: | > Giving every item a new key on every update does not help | with that | | fwiw the author specifies says _not_ to do this. The advice | is to generate a unique ID when items are created, not when | they're rendered. | orangepanda wrote: | Its not an optimisation, its about state management. | | React can't see the difference between a reorder and | remove&insert. When reordering items the state should be | moved as well; when removing and inserting a new item, state | should reset. | | Using an array index is equivalent to silencing the error | suzzer99 wrote: | So what's the problem with using index then? | nicoburns wrote: | You're better off not using a key than using a random one | (unless the random key is kept stable accross re-renders) ___________________________________________________________________ (page generated 2023-03-11 23:00 UTC)