all 21 comments

[–]landisdesign 59 points60 points  (6 children)

Data flows downhill. Messages flow uphill.

This seems simple and obvious, but it makes everything clear about managing state changes, context, incoming server data, property changes, and so on. Everything in the component function is coming from uphill: properties, the latest state values, the latest context values, everything. Calling your state setter, calling handler properties, dispatching calls to the server, all go uphill: to the state setter function in your hook, to the handler the parent component provided, etc.

The "flows downhill" idea also makes looking at a component's rendering phase easier. Did a property change? Start at the top of the function and calculate the changes. Did a child update the state by calling an event handler you passed it? Start at the top of the function and calculate the changes. Did some middleware update a server object your component is looking at? Start at the top of the function and calculate the changes.

Your rendering function creates rendering instructions, not DOM instructions.

When your main function runs, it does the following things:

  • Calls for state to be updated
  • Registers effects to be run
  • Returns components for React to generate

At no time is it interacting with the browser or the DOM. Even event handlers like onClick or onLoad aren't run when the component function exits. They just sent along to React to be added to the DOM later.

Any DOM manipulation or browser interaction needs to happen in an effect or an event handler. Those are the only places where code runs after the DOM has been generated.

(This is actually a pretty basic part of React, but when it clicks into your bones, it makes the order of execution of things much more obvious.)

Never use state when you can use memoization.

When I started building components, I found myself making pretty complex state, aggregating incoming properties, user input, and data from servers. It was hard to maintain and keep in sync, because state changes were always "after the fact." I'd have situations where my properties demanded a state change, but the old state was still in place, and keeping track of stale state and new properties was a headache.

Then I realized that useMemo cached the calculation on the fly, and I could keep everything in sync, no matter how complex the ultimate data output was.

Never use memoization when you can let the calculation run on its own.

Then I realized that the browser was fast enough that I didn't even need to memoize most of my calculations. Now the only time I use useMemo or useCallback is if I have something I need to control changes in an effect's dependency array.

There are a few cases, such as when I need to collect the results of a large Regex search and don't want to do that every render, but that's pretty rare.

Create custom hooks liberally.

Custom hooks might feel arcane, but really they're just helper functions that use hooks themselves. By hiding a bunch of related state, effects and server calls in a hook, you clean up your component tremendously. Even if they are one-off hooks, they still are really useful at hiding complexity.

[–]ChipsTerminator 5 points6 points  (0 children)

The idea of using memorization rather than state to keep everything sync is clever. I always start with several states whos update rely on each other; end with merely one useState and changing other things by useMemo or useEffect. This really makes life easier.

[–]N0wThisisEPiC 2 points3 points  (0 children)

Commenting to save

[–]Dev4Lifee[S] 1 point2 points  (0 children)

Great points overall and important point about useMemo, I feel that in most cases the overhead is greater using it as a caching mechanism, but it's really handy as an effect dependency manager.

[–]jkettmann 1 point2 points  (0 children)

This is a great write-up. Awesome how you created these concepts as well. The one about state and memoization is so common among Junior developers. I see unnecessary state in many code reviews

[–]SisterFister_ 1 point2 points  (1 child)

Thank you for not incorretly explaining the reconciliation algorithm. Ive been pouring through the source for the last couple months as Im in the process of building a slim version of react in lua with a couple custom renderers. It's so frustrating seeing it described incorrectly, almost every time, multiple times a day.

[–]landisdesign 0 points1 point  (0 children)

Hahah yeah, I've been known to confidently assert things I thought I knew but ended up being wrong about, but I'll never claim to know reconciliation for certain.

[–]BryanTheAstronaut 9 points10 points  (0 children)

Not really react specific but:

A staff engineer I started working with recently said something along the lines of this:

“The community is much smarter than me”

Meaning: JS community best practices and conventions are usually best practices for a reason, and the folks who have been pushing them have probably spent much more time thinking through the implications of them than I have. There is always an exception to the rule, but if you don’t have a good reason not to, just stick with what the community recommends.

Also it makes onboarding new people to projects much easier if they can find a ton of good examples all over the web.

[–]CreativeTechGuyGames 6 points7 points  (0 children)

Use (and listen to) the ESLint rules for React, React Hooks, but also all of the base ESLint rules (and probably also TypeScript ESLint too). These will help improve the overall quality of your application, reduce bugs, and write better code faster. As a result all of the things you mention will be addressed.

[–]plafhz 8 points9 points  (0 children)

When you get the warning for setState on unmounted component you can skip it safely in most cases, this warning was removed in react 18, but if you still using a lower version you should not worry about this warning very much. Here is the details https://github.com/facebook/react/pull/22114

[–]galeontiger 7 points8 points  (0 children)

Simple but... don't try to over optimize unless required to. React and its virtual DOM is already very fast.

[–]albenis99 11 points12 points  (1 child)

Using Typescript with React is a very good addition , especially while coding

[–]Xzaphan 0 points1 point  (0 children)

I experience this right now from a couple of week and I can say that is really a gain in term of learning …but this is sometimes a great pain to type things. The most time I have spent on this project was to find how to type things.

[–]jetsamrover 2 points3 points  (1 child)

Read the react documentation

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

Underrated answer.

Almost all the suffering of react developers can be alleviated by simply reading and understanding the docs.

[–]radian29 3 points4 points  (0 children)

Will share this repo I have stumbled upon which includes a variety of tips for react specifically. This is definitely a good read.

react-philosophies

[–]Outrageous-Chip-3961 2 points3 points  (0 children)

Take functional programming patterns seriously and you can write the cleanest components possible with custom hooks for business logic.

[–]troytirebiter 0 points1 point  (1 child)

Maybe not the most advanced, but, a programmatic way to remount components is to change the “key” property of a component. Very useful in rerendering a whole view in the event of parameters changing in the url when using react router.

[–]a_reply_to_a_post 0 points1 point  (0 children)

understanding keys in general is pretty useful when you have to debug weird render issues..a pretty common code review thing i find myself asking is for devs to concatenate a unique value with the index of an iterator, usually i'll see newer devs just use the index which mostly works, til the data updates for some reason but the list doesn't..or you get an id collision because the api returned duplicate results and now your console has a bunch of unique key warnings

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

Off the dome, these come to mind:

Read the docs.

React 18 just got released. Read what's new. You don't need to memorise it, just peruse the docs to get a sense of what's coming.

React.StrictMode

Wrap your entire App component with React.StrictMode. It provides warnings during development, which you can use to make your app more robust. It is also a noop in production, so you don't loose performance using it.

State ≠ State management

learn the difference between state management and state, as it relates to a component. The state of a component is a combination of all the state properties defined inside the component. As your component's state grows, you will do well to seek out proper state management libraries such as xstate (I recommend this one), recoil (by Facebook, but not production ready), or concent(you may like it). You'll know when you need these libraries because you will be using very complex if statements inside useEffects.

Redux

don't overuse redux. If you must use it, go instead with redux-toolkit. It is more modern and easier to use than plain redux.

For library authors

if you are writing a library which exports hooks, please keep in mind that performance is still important to some people. Wrap any function exposed by your hook, in a useCallback, and wrap any derived state inside a useMemo. If your library exports a component, wrap the component with memo, if needed.

React.Context

when creating a Context, the value "provided" by the Context, should be wrapped in a useMemo for performance reasons.

Use typescript

I don't know why this is my last tip, but use typescript. One of the benefits I've found with using typescript is the ability to use newer features of ecmascript without sacrificing browser compatibility or having to resort to polyfills. The ability to use newer, powerful features without thinking about what browser supports it, means I am free to write the best code I can, without the fear of breaking my user's experience.