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