3

React JS Best Practices From The New Docs

 1 year ago
source link: https://sebastiancarlos.medium.com/react-js-best-practices-from-the-new-docs-1c65570e785d
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

React JS Best Practices From The New Docs

If you don’t have time to read the new React docs, I sifted through them for you.

1*I5cfGM7n7kMkU2mhlMNlfw.jpeg

Photo by Sora Shimazaki on Pexels

New React docs were recently shared on the official React website.

The React team is making a final pass before fully switching over but — at the time of writing — all of the commonly used modern APIs are documented, making it the best way to learn the framework.

0*WIcEqdv1ozbUkte4

React veterans out there know that to be a true React grandmaster, you need to follow every React team member on Twitter to make sure you don’t miss any precious nuggets of information.

Not anymore! All that esoteric knowledge is now available in an easy-to-read format. Ferocious kids coming from behind will take your jobs thanks to the new React docs!

Your only chance to stay ahead is to humble yourself, sit down, and read every single word in the new docs.

But fear not, for I have selflessly taken one for the team. I went through the docs from top to bottom and broke it all down for you.

Disclaimer: This article contains cursing. Think of me as an unruly React sensei who’s been through the trenches and is now dispensing some wise hot takes and giving zero fucks.

Introduction

The new React docs have two sections: “Learn” and “API Reference.”

This article covers the “Learn” section. It’s meant to be read sequentially.

If you are an experienced React dev, you can skip ahead and skim through the headlines. If something piques your interest, feel free to deep dive.

The last two topics from the docs, “Effects” and “Custom Hooks,” are not included here because they are so complex that I would break my Hippocratic oath if I ever attempted to summarise them.

Now that we got that out of the way, let’s get into it.

Section 1: About React

1. React was originally created by Jordan Walke.

Today, Jordan Walke doesn’t work at Meta anymore.

2. React has 17 core team members.

Compare with Vue’s 19 team members and Solid’s 7 team members. Svelte doesn’t publish the number of core team members.

Section 2: Getting Started

3. The docs use CodeSandbox for their interactive code examples.

The docs also mention that React is supported by several other sandbox services. They explicitly name CodeSandbox, StackBlitz, and CodePen.

4. The new docs take a page from Vue on explaining the different use cases of React.

From “adding some interactivity” to “more complex apps” and even “large apps”, the new docs do a great job at mapping out the different levels of React usage.

5. Adding React to a simple HTML page is easy. It doesn’t even require a build step.

Just add React as a script tag, write a component, and render it on a root. If you want to add JSX support, you can set up a simple build step with babel-cli.

6. You can have multiple React roots.

Again, this is useful if you want to add a few interactive components to a static HTML page. You can simply create a root for each component, instead of creating a root for the full document. Yet, the docs don’t clarify if it’s possible to share information between components on different roots.

7. The new docs still recommend the godawful Create React App.

Create React App might be convenient and unopinionated, but it’s a pile of outdated trash that never fixes important bugs. Just take a look at the Github issues if you don’t believe me. Luckily, the docs also recommend popular alternatives like Vite and Parcel.

8. Next.js is recommended for production-ready projects.

No surprises here. Next.js is good, especially now that it allows more flexibility on page layouts. Other recommendations are Gatsby, Remix, and Razzle.

9. There’s a fine list of tool recommendations to build your own toolchain.

If you want to fly solo, the docs suggest a sensible and trendy list of options. A pleasing surprise to me was that there’s no mention of Lerna, which makes sense after recent debacles. Instead, the recommendations for monorepos are Nx and Turborepo.

10. The recommended editors are VS Code, WebStorm, Sublime Text, and Vim.

No big surprises there. I would only add that any JetBrains editor is up to the task, not only WebStorm. Also, Sublime Text is a meme, Dan Abramov only mentioned it because he still feels bad about that time on a conference talk when the Sublime Text pop-up for buying a licence appeared on screen (he had the licence btw, it was just a technical issue).

Also, Vim’s website looks straight out of the 90s, making the editor literally unusable. It will probably be replaced by Neovim in the docs in a few years.

11. ESLint is required to properly use React.

As it stands, any sane use of React requires the eslint-plugin-react-hooks to be running at all times. In the future this might be implemented on Rome, a compiler step, or some other tool.

12. Prettier is king.

No surprises here. The docs encourage you to use prettier or else, and that’s a good thing. If your codebase uses both ESLint and prettier, the preset eslint-config-prettier is recommended so that ESLint is only used for catching logical mistakes, letting daddy Prettier do all the important work.

13. There’s no React Developer Tools browser extension for Safari.

There’s one for Chrome, Firefox and Edge.

To debug on Safari, there’s a workaround involving the standalone react-devtools npm package, which can also be used to test React Native apps.

14. React apps are made out of components.

The docs blatantly tell you that the main abstraction of the framework is components. What can I say about the mighty MVC killer that hasn’t been said before?

They explicitly say that “A component can be as small as a button, or as large as an entire page,” and that it’s “components all the way down.” Isn’t that beautiful?

15. To learn about JavaScript, the docs recommend MDN and javascript.info.

I was not aware of javascript.info, but it’s open-source and looks pretty legit.

16. They recommend an online tool to convert HTML to JSX

This can be useful if you start from a lot of HTML. It’s needed because JSX is stricter than HTML. For example, JSX requires closing tags. Here’s the tool.

That tool is a goldmine by the way, as it includes SVG to JSX, CSS to Tailwind, and much more.

17. React does not prescribe how to add CSS.

In the simplest case, you’ll add a <link> tag to your HTML. If you use a build tool or a framework, consult its documentation to learn how to add style to your project.

18. You can think of curly braces in JSX as an “escape hatch” into JavaScript.

It’s a nice way to think about them. You can put curly braces in the value of JSX attributes, or inside the JSX tag content.

Curly braces are “a window into the JavaScript world.”

19. The ? and && operators are recommended if you want conditional rendering inside JSX.

It’s nice to see that a tradition as old as time is still recommended. The amount of perverse satisfaction you get from using those operators is unrivalled.

20. The “key” attribute is clearly explained.

An often confusing term, key, is explained pretty well on the new docs. It’s so good that I’m gonna copy-paste the entire section here:

For each item in a list [of components], you should pass a string or a number that uniquely identifies that item among its siblings. Usually, a key should be coming from your data, such as a database ID. React will rely on your keys to understand what happened if you later insert, delete, or reorder the items.

As we’ll see later, “key” is also used for the advanced case of resetting a component’s state. But I still consider the explanation complete because that advanced case is just a consequence of the aforementioned “uniqueness”.

And remember: Keys must not change, or that defeats their purpose! Don’t generate them while rendering. Instead, use a stable ID based on the data.

21. The docs are very good at explaining what’s a rule and what’s a convention.

For example, they explain that setState returns two things, and it’s only by convention that we call them [something, setSomething].

As another example, they explain that it is common to name event handlers as “handle” followed by the event name. (onClick={handleClick}, onMouseEnter={handleMouseEnter}, and so on.)

In contrast, Hooks are functions that, as a rule, must start with “use”.

22. “If you want to use hooks in a condition or a loop, extract a new component and put it there.”

You heard it here first.

The new React docs go hard on the fact that hooks are top-level only. If you feel inclined to put a hook inside a condition or loop, that means that you need to create a new component.

Hooks are functions, but it’s helpful to think of them as unconditional declarations about your component’s needs. You “use” React features at the top of your component similar to how you “import” modules at the top of your file.

23. “Lifting state up” is front and center in the docs.

The famous refactoring pattern is so common that it’s explained in full detail on the “Quick Start” page. This should probably keep those peskier devs in check.

24. Turning a UI mockup into a component hierarchy is a creative process.

The docs don’t shy away from the fact that, given a UI mockup, there’s no one right way to turn it into a set of React components.

However, they encourage some common-sense guidelines, like “a component should ideally only do one thing. If it ends up growing, it should be decomposed into smaller subcomponents.”

25. It’s ok to be top or bottom.

When making a component hierarchy, the docs mention optional techniques like “building a static version first,” and they explore the distinction between building “top down” by starting with building the components higher up in the hierarchy or “bottom up” by working from components lower down.

26. Minimal state is cool.

The docs really emphasize the DRY (Don’t Repeat Yourself) principle when it comes to state. In particular, there’s no need to create new state when it can be computed from existing state.

Looks like the React team have been in contact with the same kind of data hoarding that I have seen in other developers.

27. Props vs State is explained clearly.

“Props are like arguments you pass to a function,” while “State is like a component’s memory.”

28. It’s ok to create a new component solely for holding shared state.

If you can’t find a component where it makes sense to own the state that’s used by several children, it’s ok to create a new one.

29. Hooks are called hooks because they let you “hook into” a component’s render cycle.

It’s always good to know what you are hooking into. Now you know.

30. In a sense, React actually uses “two-way data binding.”

Two-way data binding was the revolutionary solution of the currently-pagan framework Angular. Purist might be surprised to know that React does pretty much the same thing because it uses both “one-way data flow” and “inverse data flow.”

“One-way data flow” is the passing of data from the top to the bottom of the component hierarchy. “Inverse data flow” happens when a component deep in the hierarchy needs to update state at the top (usually because of user input.)

The catch, and the reason why React is not technically “two-way data binding,” is because React is very explicit about “inverse data flow”: The developer actually needs to write onChange event handlers.

Section 3: Describing the UI

31. The docs recommend the component libraries Chakra UI and Material UI.

Yes, in that order. Chakra UI is recommended before Material UI.

As an extra offense, Material UI is not even the current name. These days it goes by MUI as an attempt to distance itself from being a library tied to a specific artistic movement.

32. React has a philosophy of “interactivity first.”

Purists have recently claimed that the web is first and foremost a document sharing platform, and that interactivity should be secondary.

The React team strongly disagrees, and they make it clear on the new docs. I’ll just copy-paste what they have to say about it because it’s delightfully savage:

Traditionally when creating web pages, web developers marked up their content and then added interaction by sprinkling on some JavaScript. This worked great when interaction was a nice-to-have on the web. Now it is expected for many sites and all apps. React puts interactivity first while still using the same technology: a React component is a JavaScript function that you can sprinkle with markup.

Linux boomers are recommended to apply aloe to the burn.

To be fair, the docs make it clear that React can also be used to “add sprinkles of interactivity.”

33. In React, rendering logic and markup live together.

Another guiding principle stated in the docs is that logic and markup should be together (meaning, on the same file). This is good because it “ensures that they stay in sync with each other on every edit.”

Author’s note: Maybe Styled Components and Tailwind got so popular because, in a similar way, they allow style to also live together with markup and logic in the same file.

34. React takes no side on the “default vs named exports” debate.

To their credit, they succinctly explain the downside of default exports, saying that “you could write import Banana from './button.js' and it would still provide you with the same default export.”

Yet, their final say on the matter is just the observation that “People often use default exports if the file exports only one component, and use named exports if it exports multiple components and values.”

They also don’t shy away from having “one default export and numerous named exports.”

35. Never define a component inside another component!

According to the docs, it’s technically possible. But it’s very slow and causes bugs. Instead, define every component at the top level.

36. It’s ok to make components even if they are not reusable.

React is “components all the way down,” so there will always be at least one non-reausable component: the top-level “app component.”

But it’s ok to have even more one-off components because “components are a handy way to organize UI code and markup, even if some of them are only used once.”

37. Fragments let you return more than one element.

The docs explain why a component can’t return more than one element: It’s because “JSX under the hood is transformed into plain JavaScript objects. You can’t return two objects from a function without wrapping them into an array.”

But fear not, for you can return two JSX tags by “wrapping them into another tag or a Fragment.”

Fragments are “empty tags” ( <>…</>) that let you group things without leaving any trace in the browser HTML tree.

Another benefit of Fragments is that they let you pass a key, so they’re great when an item in a list of components needs to render several elements.

38. aria-* and data-* are the only JSX attributes written with dashes.

This is for historical reasons.

In general, everything in JSX is camelCase, even inline style properties, because JavaScript has limitations on variable names.

Additionally, since class is a reserved word, in React you write className instead.

39. “Double Curlies” is an official term.

It’s the explicit name for passing a JS object in JSX, in which you must wrap the object in another pair of curly braces: {{}}.

It’s not a special syntax, it’s just a JavaScript object tucked inside JSX curly braces.

Not to be confused with the “vagina syntax” when returning an object from an arrow function: ({}).

40. The props you can pass to HTML tags are predefined.

For example, “className”, “src”, “alt”, “width”, and “height” are some of the props you can pass to an <img> tag.

But you can pass any props to your own components.

41. If you are spreading all the time, you might be behaving immorally.

Some components forward all of their props to their children. As they don’t use any of their props directly, it can make sense to use the concise “spread” syntax: {...props}

However, the docs warn to “Use spread syntax with restraint. If you’re using it in every other component, something is wrong. Often, it indicates that you should split your components.”

Careful who you spread for.

42. Some components have a “hole” that can be “filled in.”

You can think of a component with a children prop as having a “hole” that can be “filled in” by its parent components with arbitrary JSX.

You will often use the children prop for visual wrappers: panels, grids, and so on.

43. A component may receive different props over time.

A superficial understanding of the difference between state and props might make you think that props don’t change.

Not so! A parent component might have state that changes over time and passes it down to a child component as props.

However, from the point of view of the child component, props are “immutable” — a term from computer science meaning “unchangeable”

So, when a child component needs to change its props (for example, in response to a user interaction or new data) it must “ask” the parent component to generate and pass “new props” by using the aforementioned “inverse data flow.”

We can say that props are read-only snapshots in time: every render receives a new version of props. (And, as we’ll see later, state is also a read-only snapshot in time)

44. The docs recommend to use ternary operators “in moderation.”

They are ok to conditionally render within JSX, but “If your components get messy with too much nested conditional markup, consider extracting child components to clean things up.”

If fellow devs insist on creating a spaghetti sauce of ternary operators, consider sending them to the React docs, or here:

45. Don’t put numbers on the left side of &&.

Old timers know this one.

If you use && for conditional rendering, you’re gonna have a bad time with 0. You will likely end up with a small unstyled zero new to your shinny sans-serif material UI.

Mathematicians hate it.

46. Sometimes it’s best to avoid shortcuts and just conditionally assign JSX to a variable.

Ternary operators and &&s are nice, but if they get in the way, you might as well use old school variables to hold the conditional JSX.

I’m glad that the docs plainly say that it’s ok to write normal JavaScript. Sugar is good, but bitch please, vanilla JS is sweet enough.

47. The docs recommend “crypto.randomUUID()” or a package like “uuid.”

They can help you create unique identifiers for data generated and persisted locally, which you can then use as keys.

Yes, they recommend crypto.randomUUID() before the “uuid” package.

I never heard of that native crypto library before, but it looks solid. If I were the maintainer of the “uuid” package, I would start looking for a new job. These docs just ended their entire career.

48. Your components won’t receive key as a prop.

It’s only used as a hint by React itself.

If your component needs the ID that was used as the key, you have to pass it as a separate prop too.

49. React assumes that every component you write is a pure function.

Don’t go around doing impure things behind React’s back. React is designed around the concept of pure functions.

A pure function has the following characteristics:

  • It minds its own business. It does not change any objects or variables that existed before it was called. (This means that it doesn’t mutate variables outside of the function’s scope, and it doesn’t mutate it’s inputs. In React, “props”, “state” and “context” are all considered inputs.)
  • Same inputs, same output. Given the same inputs, a pure function should always return the same result.

However, it’s completely fine to change variables and objects that you’ve just created inside the function. This is fine because no code outside of the function will ever know that this happened. This is called “local mutation.”

React cares about purity because it helps with performance, server-rendering, and it will enable cool new features soon™️.

50. Detecting impure calculations with StrictMode.

If you are in strict mode, every component will be rendered twice. This is to detect if you are a naughty boy.

51. Not everything is pure on React ;)

I’ll let you in on a little secret, all of us React devs do impure things all the time. And we love it!

While functional programming relies heavily on purity, at some point, somewhere, something has to change. That’s kind of the point of programming!

So, we have our little secret spots on React where we can do whatever we want. We do things differently “on the side.” Tee-hee-hee!

In React, side effects usually belong inside “event handlers,” from which you are free to modify external variables, set state, and do whatever impure thing you desire. Even though event handlers are defined inside your component, they don’t run during rendering, so they don’t need to be pure!

If you’ve exhausted all other options and can’t find the right event handler for your side effect, you can still attach it to your returned JSX with a “useEffect” call. This tells React to execute it later, after rendering, when side effects are allowed.

Section 4: Adding Interactivity

52. They link to a Medium article about design systems.

They really do. Here it is:

Congrats to

, you really made it into the React docs. Can’t wait to see the movie adaptation of your article.

53. They say that event objects are usually called “e” by convention.

Bad move, React Docs, bad move.

I’ve been fighting devs for years to write those as “event.”

What am I gonna do now, React? Tell me, what the fuck am I supposed to do with all those “e”s on my otherwise fucking pristine codebase?

54. Event handlers are just functions that you pass as props.

There’s nothing special about them.

Except of course that they are meant to respond to interactions like clicking, hovering, and so on.

Which means that every event handler, at the very bottom of the React component tree, will land on one of the built-in handlers like “onClick” on some HTML tag like “button”.

55. You should name event handlers based on app-specific concepts.

When your component supports multiple interactions, you might name event handler props for app-specific concepts. For example “onPlayMovie” and “onUploadImage”.

56. The docs remind you that capture and propagation exist.

If you were lucky enough to work on high-level stuff and rely solely on event handlers, the React docs are there to remind you that the complete hell of DOM event propagation is still alive. May God help us all.

57. e.stopPropagation() stops propagation.

It does.

58. e.preventDefault() prevents default.

How nice. This prevents default browser behavior on some events. For exemple, browsers reload the entire page by default on the form submit event.

59. Local variables don’t persist between renders.

When React renders a component a second time, it renders it from scratch — it doesn’t consider any changes to local variables.

By “local variables” I mean something like let index = 0 on the body of the component.

60. Changes to local variables won’t trigger renders.

Few things trigger renders. Changing local variables ain’t one of them. Did you thought this is Svelte?

61. To update a component with new data, two things need to happen.

First, we need to persist the data between renders. Second, we need to trigger React to render the component with new data (re-rendering).

Here’s where the useState Hook comes in. It provides those two things:

  • A state variable to retain the data between renders.
  • A state setter function (aka “set function” or “set state function”) to update the variable and trigger React to render the component again.

Could this be done in a simpler way? Sure, but this isn’t Svelte. This is React, we live close to the metal and we like it like that. No magic on my JavaScript.

62. State is private to each component instance on the screen.

State is not tied to a particular component function. If you render the same component in two places on the screen, each copy gets its own state.

This might seem obvious to veterans but it can trip you up the first times using React. Especially when using useState, which is something that you have to import, and importing is mysterious because who knows what’s on the other side.

63. Triggering, rendering, and committing.

In React, the process of updating a component on the screen has three steps:

Triggering a render is queueing React to render a component as soon as it’s not too busy doing something else, like wasting CPU cycles.

Rendering is running your component and obtaining the result.

Committing is getting down and dirty and actually applying the result of rendering into the infernal machine of the browser DOM API, using contraptions like the Document.createElement() method.

63. There are two reasons for a component to render.

  1. It’s the component’s initial render.
  2. The component’s (or one of its ancestors’) state has been updated.

That’s it.

But how about when a context changes? Nope, that case is already contained within “one of its ancestors’ state has been updated.”

64. There are ways to increase performance, but thread carefully.

The default behavior of rendering all components nested within the updated component is not optimal for performance if the updated component is very high in the tree.

If you run into a performance issue, there are several opt-in ways to solve it, but don’t optimize prematurely!

The main optimization technique recommended in the docs is the “memo” API. It allows you to skip rendering components whose inputs have not changed. This is safe because pure functions always return the same results, so they are safe to cache.

See? This is why all that purity nonsense is not a waste of time!

65. React is smart. Maybe smarter than you!

For re-renders, React will apply the minimal necessary operations (calculated while rendering!) to make the DOM match the latest rendering output.

This right here is React’s claim to fame. Initially called the Virtual DOM or “the diff,” nowadays — especially after some vicious attacks — it’s just referred as “that thing React does,” and no questions are asked because the implementation is so obscure and arcane that you might as well apply to join the Illuminati.

66. React only changes the DOM nodes if there’s a difference between renders.

This might be one of the coolest thing ever.

Let me be clear here: Even if a component re-renders because something changed, React will only commit changes for those specific DOM elements that changed!

Every unaffected DOM element within the re-rendered component will be let at peace! Jesus fucking Christ! Who ever thought of that? Those React folks really had a big brain moment, Jesus Christ!

67. State is like a snapshot of the UI in time.

State might look like regular JavaScript variables that you can read and write to. However, state behaves more like a snapshot. Setting it does not change the state variable you already have, but instead triggers a re-render.

This is because “rendering” is actually taking a snapshot of the UI in time. Its props, event handlers, and local variables are all calculated using its state at the time of the render.

Unlike a photograph or a movie frame, the UI “snapshot” you return is interactive. It includes logic like event handlers that specify what happens in response to inputs.

So here’s the catch: You can’t change state from an event handler and then expect to read the updated value from the same event handler, because you are still trapped on the same snapshot in time. Setting state only changes it for the next render.

You might be wondering, what do I care? What kind of monster would try to read the state that was just updated?

You would be right, this is a very unimportant piece of information, and you would do well to forget everything you just read, unless you plan to impress people at React meetups.

68. A state variable’s value never changes within a render.

Actually, the previous point is not so unimportant.

Just remember this maxim: “A state variable’s value never changes within a render.”

Whatever you do, even if you wrap a console.log into three setTimeouts, you will never escape your render snapshot. The updated state is a privilege reserved for future renders. Abandon all hope.

69. React batches state updates.

React waits until all code in your event handler has run before processing your state updates. This is useful if your event handler sets multiple states, or changes the same state multiple times.

This behavior, known as batching, makes your app run much faster because it avoids unnecessary re-renders. It also avoids dealing with confusing “half-finished” renders where only some of the variables have been updated.

Let’s look at an example event handler:

function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
}

Instead of rendering twice, once for each state update, React will only re-render once at the end.

70. For async even handlers, the “async” part might run on a future batch.

We just said that React waits until all code in your event handlers has run before processing your state updates. But there’s an exception:

When batching async even handlers, React doesn’t wait for the “async” part, it just waits for the event handler itself. This means that, if an event handler sets state after timeouts, promises, or awaits, the deferred state updates might run on a future batch. But, as event handlers are snapshots in time, the future batch will also be tied to the state as it existed at the time of the render.

Let’s look at an example:

async function handleClick() {
setPending(pending + 1); // batch 1
setYolo(true); // batch 1
await delay(3000);
setPending(pending - 1); // batch 2
setCompleted(completed + 1); // batch 2
}

Here, the state updates before the “await” will run on one batch, and the state updates after the “await” will run on a future batch.

Before React 18, the async part was not batched at all. For historical information about those dark times, check this.

71. “Updater functions” allow you to set the same state multiple times from an event handler.

It is an uncommon use case, but if you would like to update the same state multiple times before the next render, instead of passing the next value, you can pass an updater function that calculates the next state based on the previous one in the queue, like setNumber(n => n + 1).

It is a way to tell React to “do something with the state value” instead of just replacing it with some specific value.

You can set state with an updater function multiple times and they will all be queued into the next batch update.

Author’s note: It’s funny to see “updater functions” downplayed as an “uncommon use case” in the new docs. Before, they were the go-to way to update state if you wanted to avoid rare batching bugs. But I guess that’s not the case anymore.

72. Keep “updater functions” pure.

I don’t even want to know what sort of degenerate developers are out there. But know this, if you do impure things on updater functions, StrictMode will catch you.

73. The docs recommend a naming convention for updater functions.

The docs recommend to name the updater function argument by the first letters of the corresponding state variable:

setEnabled(e => !e);
setLastName(ln => ln.reverse());

It is a bit of a bizarre recommendation, as I always use “value” for the state value. Whatever, React docs, you do you.

74. Batched state updates are internally implemented as a simple queue.

I like how the new docs explain how some React internals are implemented.

The internal queue used by React to batch state updates is so simple that any dev could recreate it. And it’s a good way to ossify React knowledge deep inside your amygdala.

The internal queue is just an array containing the things that were batched, which can be:

  1. Values from regular updates, or
  2. “updater functions.”

Here’s how it works: The queue gets iterated. If a value is found, it replaces the state. If a function is found, it is run on the current state value. Easy.

75. Treat state as read-only.

It should come as no surprise — considering that state is a snapshot in time and that React is obsessed with functional programming — that everything you put on React state should be treated as read-only and “immutable.”

This is an example of what you should never do:

const [person, setPerson] = useState({
firstName: 'Barbara',
lastName: 'Hepworth',
email: '[email protected]'
});

function handleFirstNameChange(e) {
person.firstName = e.target.value; // <-- never do this
}

What are you doing? Don’t you see the ‘setPerson’ function sitting all alone up there?

There are two problems with that code:

  1. It doesn’t trigger a state update because it doesn’t call the set function.
  2. Objects are mutable in JavaScript, so what the code is actually doing is changing the state in the previous render “snapshot”. It’s like trying to change the order after you’ve already eaten the meal. Also, it messes up with debuggability and with React’s internal optimizations.

Try this instead:

const [person, setPerson] = useState({
firstName: 'Barbara',
lastName: 'Hepworth',
email: '[email protected]'
});

function handleFirstNameChange(e) {
setPerson({
...person, // Copy the old fields
firstName: e.target.value // But override this one
});
}

That is, you should create a new object and pass it to the state setting function.

76. You can create new objects with the spread syntax.

As seen above, the docs recommend the good ol’ spread syntax (…) to create the new object based on the existing one in state.

setPerson({
...person, // Copy the old fields
firstName: e.target.value // But override this one
});

77. Be careful with nested objects in state.

Careful though, spread is “shallow” — it only copies things one level deep.

If you are vicious and want nested objects in state, you have to spread more and create new objects for every level of nesting.

78. Objects are not “really” nested in JavaScript.

With all this talk of spreading and nesting, the docs are wise to remind us that objects are not really nested inside each other, they just reference each other.

In fact, there could be all sorts of circular and psychedelic cross-referencing that would completely break the nesting metaphor. One can only watch in despair as all the structures of our western civilization collapse!

79. The docs recommend Immer if you don’t like too much spreading.

For deeply nested state, the docs recommend Immer, in which your updates look like you are “breaking all the rules” and mutating state:

const [person, updatePerson] = useImmer({
name: "Michel",
age: 33
});

updatePerson(draft => {
draft.name = 'Sebastian';
});

But Immer, thanks to the magic of JavaScript Proxies, creates a new object in the background.

The React docs even suggest the React-specific use-immer wrapper, in which you import useImmer instead of useState.

80. Treat arrays in state as read-only too.

Just like with objects, when you want to update an array stored in state, you need to create a new one (or make a copy of an existing one).

81. The docs provide a table of which methods to avoid for arrays in state.

Here it is:

1*bZwdOIn99td2XL8Qs-HOgw.png

Naturally, if you use Immer, all methods are available.

Personally, I never use “concat”. I like to spread.

I don’t like to use “slice” because I always confuse it with it’s evil twin “splice.” But sometimes, when you want to insert into an array in a specific position, it’s the only way. Luckily, Github Copilot can save me a trip to StackOverflow when that situation arises.

82. The docs remind you that arrays can contain objects.

Sometimes, creating new arrays is not enough. If they contain objects, you should take care to create new objects too when updating state.

Again, Immer could be of help, especially if you have very nested data structures.

Section 5: Managing State

83. Think like a designer when managing your state.

The docs are happy to remind you that we don’t live in the jQuery dark ages anymore. You are driving a declarative framework, not an imperative monstrosity.

Therefore, you are well advised to think higher-level. Instead of focusing on minuscule behaviours of buttons and divs, you should think of the different states that your whole component can have, such as “loading,” “initial,” “error,” “success.” And which actions trigger transitions between them.

Keep in mind “state machines” and “designer mockups” when defining your state, and React will leverage it to keep the complexity low.

If this sounds a bit like magical thinking, it is. But it’s magical thinking by some of the most successful UX programmers of our time, so shut up and listen.

84. Declarative logic might use more lines of code than imperative logic.

Despite what we said above, thinking like a designer might produce more lines of code. And there’s an example of this in the docs.

Still, this is a worthwhile tradeoff because the declarative code should be less fragile and it should let you introduce new states without breaking old ones.

85. The docs address the usefulness of tools like Storybook.

While the tool Storybook is not explicitly named, the docs say that, if a component has a lot of visual states, it can be convenient to show them all on one page. And these pages are often called “living styleguides” or “storybooks”.

This part of the docs suggests that you don’t necessarily need an external tool to have “storybooks,” they could simply be part of your main app.

86. Group related state.

Just do it.

If some two state variables always change together, it might be a good idea to unify them into a single state variable. Then you won’t forget to always keep them in sync.

87. Avoid contradictions in state.

The docs suggest to design your state so that “impossible” or “contradicting” states can’t happen.

For example, instead of having two states, isSending and isSent, which allow for the contradicting case of both being true if some distracted dev forgets to update them correctly, you should have a single status state with the possible values sending and sent.

You can still declare some constants for readability:

const isSending = status === ‘sending’;
const isSent = status === ‘sent’;

But they are not state variables, so they will never be out of sync.

88. Don’t mirror props in state.

Just don’t. Two reasons:

  1. Why would you even do that?
  2. If you do it, your state will be out of sync when props change, because state doesn’t update after initial render.

The only legit case of mirroring state is precisely when you want to ignore all updates for a specific prop. In that case, by convention, you should start the prop name with initial or default to clarify that its new values are ignored.

89. Avoid deeply nested state.

The docs recommend to have “flatten” or “normalized” state instead of deeply nested state. This is done by having a bunch of ids and arrays of ids, as if pretending to be a database table.

It brings joy to my heart to see that this pattern is still being recommended. It feels like yesterday when Redux-era Dan Abramov taught us uncivilized front-ends how to do things a bit more back-endy.

90. Avoid deeply nested state — extra tip!

Sometimes, you can also reduce state nesting by moving some of the nested state into the child components.

91. “Controlled vs uncontrolled component” is clearly explained.

The myth is put to an end. The description of these terms is so good that I’m just gonna copy-paste from the docs:

It is common to call a component with some local state “uncontrolled”. […]

In contrast, you might say a component is “controlled” when the important information in it is driven by props rather than its own local state. This lets the parent component fully specify its behavior. […]

In practice, “controlled” and “uncontrolled” aren’t strict technical terms — each component usually has some mix of both local state and props. However, this is a useful way to talk about how components are designed and what capabilities they offer.

92. There is a thing called CSSOM.

The docs, in an attempt to prove that browsers use a lot of trees to structure UI, tells us that there’s not only a DOM but a CSSOM (CSS Object Model) too! There’s even an Accessibility tree in the browser too!

93. React has an internal “UI tree”.

It should come as no surprise that React also uses an internal tree to structure the UI:

1*j1C3INhTD0Q-rNqWj4tu6Q.png

94. State is tied to a position in the UI tree.

It’s a bit of a simplification to say that “state lives inside the component,” because a component might be rendered in more that one position in the UI tree, each with its own isolated state.

So, more accurately, we can say that state is tied to a component in a specific position in the UI tree.

95. React will keep the state as long as you render the same component at the same position.

The moment you stop rendering a component in a given position (either because it gets removed, or because a different component gets rendered at the same position), its state disappears completely. Actually, the state of its entire subtree disappears completely.

Remember that it’s the position in the UI tree — not in the JSX markup — that matters to React! If your component conditionally returns different JSX tags which both result in the same component in the same position, React will keep the state (Even if the props change!)

Let’s look at an example:

if (isFancy) {
return (
<div className="fancy">
<Counter isFancy={true} />
</div>
)
} else {
return (
<div>
<Counter isFancy={false} />
</div>
)
}

You might expect Counter’s internal state to reset when the condition changes, but it doesn’t!

Here’s a good rule of thumb: If you want to preserve the state between re-renders, the structure of your tree needs to “match up” from one render to another.

96. Resetting state at the same position: The hacky way.

Sometimes you do want a component to reset state. For example, let’s say that you have a Counter component that shows the score for a player:

{isPlayerA ? (
<Counter person="Taylor" />
) : (
<Counter person="Sarah" />
)}

There’s a bug here: When the condition changes, the score remains.

If you want to reset the state when the condition changes, there are two ways. Let’s look at the hacky way first:

{isPlayerA &&
<Counter person="Taylor" />
}
{!isPlayerA &&
<Counter person="Sarah" />
}

This works because null is a valid element in the UI tree. React recognizes this setup as two independent positions.

Naturally, this is only a good solution if you have a few elements. I was surprised to see this hacky shit in the docs! I don’t recommend it at all.

97. Resetting state at the same position: The Chad way.

React seniors out there know that the legit, generic way to reset state at the same position is to pass a unique key.

You might have seen keys when rendering lists. You think that’s it? Get ready to get your mind blown.

Keys aren’t just for lists! You can use keys to make React distinguish between any components.

By default, React uses order within the parent (“first counter”, “second counter”) to discern between components. But keys let you tell React that this is not just a first counter, or a second counter, but a specific counter — for example, Taylor’s counter:

{isPlayerA ? (
<Counter key="Taylor" person="Taylor" />
) : (
<Counter key="Sarah" person="Sarah" />
)}

Here, the state will reset when the player changes.

Remember that keys don’t have to be globally unique. They only specify the position within the parent.

98. What if you want to revert the old state when going back?

Do you think that React will do everything for you? Nope!

React gives you the sane default of preserving state for the same component in the same position. And it allows you to opt-out of it by passing a key to mark your component as unique. Now you want more? You want to recover the old state when rendering the component again?

If you want that, you need to write some actual code and use tried and true patterns like “lifting state up,” or localStorage, or hide stuff with CSS.

Section 6: Reducers

99. Sometimes reducers can help you model complex state.

According to the docs, sometimes a reducer (by “reducer” I mean a useReducer() reducer) is better than a series of states if you want to faithfully model a complex state machine, in a way that easily avoids “impossible” or “contradicting” states.

Also, your event handlers become concise because they only specify the user “actions.”

100. Each reducer action should describe a single user interaction.

Managing state with reducers is slightly different from directly setting state. The docs recommend that instead of telling React “what to do” by setting state, you specify “what the user just did” by dispatching “actions” from your event handlers.

Each action should describe a single user interaction, even if that leads to multiple changes in the data. It makes more sense to dispatch one reset_form action rather than five separate set_field actions.

The state update logic will live in the reducer.

101. Conventions for reducer action objects.

An action object can have any shape. By convention, it is common to give it a string type that describes what happened, and pass any additional information in other fields.

dispatch({
type: 'what_happened',
// other fields go here
});

102. Put your reducer outside of your component.

Because the reducer function takes state and actions as arguments, you can declare it outside of your component. This decreases the indentation level and can make your code easier to read.

You can even put it on a different file. Component logic can be easier to read when you separate concerns like this.

103. It’s convention to use “switch” statements in reducers.

Sure, you could also use if-statements, but you would be a bit of a pleb.

If you use “switch” statements, the docs recommend to wrap each case block into the { and } curly braces so that variables declared inside of different cases don’t clash with each other. Also, a case should usually end with a return. If you forget to return, the code will “fall through” to the next case.

104. There’s no satisfying explanation to why reducers are called reducers.

The docs give a sad attempt though:

Apparently reducers are a bit like an array’s “reduce” method. But instead of taking “the result so far and the current item,” reducers take “the state so far and the action”.

La di fucking da.

105. Using reducers is a matter of personal preference.

It’s nice for the docs to say that reducers might not be everyone’s cup of tea.

We recommend using a reducer if you often encounter bugs due to incorrect state updates in some component, and want to introduce more structure to its code. You don’t have to use reducers for everything: feel free to mix and match! You can even useState and useReducer in the same component.

They also say that, in general, reducers are usually easier to debug and test. They are more readable if the logic is complex. But surely they require more code.

This author’s opinion is that reducers are nice, but they are an artifact of the Redux hype of yesteryear, paired with the need for a reliable alternative to useState when hooks were just released, given that there were bugs caused by lack of batching. Lord have mercy!

106. Reducers must be pure.

As almost everything else in React, reducers must be pure. Don’t go around doing any weird shit. As always, you can use Immer to make your existence a little bit less painful.

Section 7: Context

107. Context lets you pass information without “prop drilling.”

I’m happy to say that the new react docs use the phrase “prop drilling,” a phrase already embraced by other frameworks like Vue. Drill my props, daddy!

Passing props is a great way to explicitly pipe data through your UI tree to the components that use it.

But passing props can become verbose and inconvenient when you need to pass some prop deeply through the tree, or if many components need the same prop. The nearest common ancestor could be far removed from the components that need data, and lifting state up that high can lead to a situation sometimes called “prop drilling”.

That’s right, when you need to pipe some data deep, you might as well suck it up and declare a Context. Context lets a parent component provide data to the entire tree below it.

108. Context is as easy as one-two-three!

Using context is easy, just follow these steps:

  1. Create a context. (With createContext.)
  2. Use that context from the component that needs the data. (With useContext.)
  3. Provide that context from the component that specifies the data. (By wrapping their children with the context provider <SomeContext.Provider value={1}>) (If you don’t provide the context, React will use the context’s default value.)

It saddens me a bit that there’s three elements to handle when working with contexts. In contrast, Vue’s and Svelte’s versions of context requires only two. If you know why React and Solid have an extra step, let me know in the comments!

109. A children using a context will grab from the nearest provider.

That’s right, there can be multiple providers of the same context above you, but when you use a context, you will grab the value from the closest one.

110. You could use and provide the same context from the same component.

Sure, this is indeed some weird shit, but the docs provide a reasonable example.

It could be useful when your nested components want to override the same context for some sodomite fucked up reason or some shit. I don’t judge.

One component may use or provide many different contexts without a problem.

111. You could think of context as CSS property inheritance.

In CSS, you can specify color: blue for a <div>, and any DOM node inside of it, no matter how deep, will inherit that color unless some other DOM node in the middle overrides it with color: green. Similarly, in React, the only way to override some context coming from above is to wrap children into a context provider with a different value.

Different properties like color and background-color don’t override each other. Similarly, different React contexts don’t override each other.

112. Don’t overuse Context!

Stop! Collaborate and listen. The docs warn you about the temptations of context.

Before reaching out for context, keep in mind that a bit of prop drilling is not so bad. It can even make you feel good, as props make the data flow more explicit.

Also, consider that maybe the reason you’re drilling so much is because you haven’t extracted pesky visual components that don’t do anything with the data anyway. Maybe they should just take a children prop instead.

113. The docs list common use cases for context.

  • Theming (e.g. dark mode).
  • Current logged in user.
  • Routing (Most routing solutions use context internally to hold the current route. This is how every link “knows” whether it’s active or not.)
  • Managing a lot of state.

114. Remember, Context is not just for static values.

If you pass a different value on the next render, React will update every single motherfucking components reading it below!

115. Context + Reducer = Chad Developer.

Do you have what it takes to use the Chaddest pattern in all of React? Buckle up, then. We are not in Redux anymore.

With the Context + Reducer pattern, a parent component with complex state manages it with a reducer. Other components anywhere deep in the tree can read its state via context, and they can also dispatch actions to update that state.

116. Even more Chad: Have two contexts for your reducer.

To bring this pattern to the next level, you should have two contexts for your reducer: One for the state, and one for the dispatcher (or for your custom event handlers). Yolo!

Each component reads the context that it needs.

This will prevent unnecessary re-renders because not every component will need both the state and “dispatch”. And “dispatch” never changes, so it will never re-render components that only dispatch.

117. Giga chad: Create custom hooks for your contexts.

Something like this:

export function useTasks() {
return useContext(TasksContext);
}

export function useTasksDispatch() {
return useContext(TasksDispatchContext);
}

Now you can use a context without having to import both the context and useContext. One import is all it takes!

Warning: If any coworker see you typing this, they will know that you are a massive Giga Chad.

Section 8: Refs

118. Think of refs as a “secret pocket.”

Let’s look at how you add a ref to your component:

const ref = useRef(0);

useRef then returns an object like this:

{
current: 0 // The value you passed to useRef
}

The docs explain refs succinctly with a rich metaphor and a lovely picture that I just had to steal:

1*bBu_6xH9He3Faxo1Ca0f8g.png

Illustrated by Rachel Lee Nabors

A ref is like a “secret pocket” on your component that React doesn’t track.

The ref is a plain JavaScript object that you can’t modify (immutable). In fact, useRef always returns the same ref object. It has acurrent property that you can read and modify (mutable).

In the illustration above, the current property is represented as an arrow because, as an object property, it can “point” to anything that you want to track. (Also, from a nerdier perspective, JavaScript object properties are implemented in the underlying C++ runtime as “pointers”, so the arrow metaphor is very appropriate.)

You can’t change the pocket, and you can’t change the “current” arrow. But you can change where the arrow is pointing to.

119. When to use refs.

Sure, secret pockets are cool, but why would you use them?

Refs are very similar to state. Like state, refs are retained by React between re-renders. Actually, most of the time you want to use state.

But here’s the difference: Setting state re-renders a component. Changing a ref does not!

So, here’s a rule of thumb for when to use refs:

  • When a piece of information is used for rendering, keep it in state.
  • When a piece of information is not used for rendering (maybe it’s only needed by event handlers) and changing it doesn’t require a re-render, using a ref may be more efficient.

Typically, you will use a ref when your component needs to “step outside” React and communicate with external APIs — often a browser API that won’t impact the appearance of the component. Common examples of this are:

  • Storing timeout IDs (for setTimeout or setInterval)
  • Storing DOM elements.

120. You shouldn’t read (or write) the ref.current value during rendering.

Sure, ref.current is mutable. But if you are trying to interact with it during rendering, you are doing something wrong.

If some information is needed during rendering, use state instead.

Since React doesn’t know when ref.current changes, even reading it while rendering makes your component’s behavior difficult to predict.

(The only exception to this is code like if (!ref.current) ref.current = new Thing() which only sets the ref once during the first render. But this pattern is a bit of a stretch already, since most of the time you would initialize refs on the useRef() call.)

121. You can combine refs and state in a single component.

Of course you can. Why wouldn’t you?

122. How does useRef work inside?

Get ready to get your mind blown.

The docs explain that, in principle, useRef could be implemented on top of useState. You can imagine that inside of React, useRef is implemented like this:

// Inside of React
function useRef(initialValue) {
const [ref, unused] = useState({ current: initialValue });
return ref;
}

Note how the state setter is unused in this example. It is unnecessary because useRef always needs to return the same object.

React provides a built-in version of useRef because it is common enough in practice.

And also because the useState implementation above only works by breaking the “state is read-only” dogma, which is sacrilegious! React, in its mercy, provides us with useRef, which is a sanctified way to have mutable state that’s independent of the “one-way data flow.”

123. When you mutate ref.current, it changes immediately.

Keep in mind that limitations of React state don’t apply to refs. For example, state acts like a snapshot for every render and doesn’t update synchronously. But when you mutate the current value of a ref, it changes immediately:

ref.current = 5;
console.log(ref.current); // 5

This is because the ref itself is a regular JavaScript object, and so it behaves like one.

124. How about using a variable outside of the component instead of refs?

Careful readers might be thinking: “Hold on a minute, refs are mutable data that aren’t used for rendering, so why do I need refs? Why don’t I keep the data in a variable outside of the component?”

You would be right, that’s definitely possible, but that only works if you have only one instance of the component. As soon as you render more instances, they will all share the same external variable, and that might not be what you want.

Using refs works fine for many components because, just like state, refs are tied to a component in a specific position in the UI tree.

125. Refs help you read the latest state from an asynchronous operation.

The docs mention a very common pattern.

State works like a snapshot, so you can’t read the latest state from an asynchronous operation like a timeout. However, you can keep the latest state in a ref! A ref is mutable, so you can read the current property at any time.

In order to do this, there’s a bit of boilerplate: You need to update the current ref value manually. It’s nice to see that the example in the documentation does this by having two calls in an event handler, instead of the infamous anti-pattern of using useEffect to keep state and ref in sync.

const [text, setText] = useState('');
const textRef = useRef(text);

function handleChange(e) {
setText(e.target.value);
textRef.current = e.target.value;
}

126. React wants you to keep your hands away from the DOM.

Too many cooks will spoil the broth. The entire reason for using React is to let it handle the nasty bits of the DOM.

However, sometimes you might need access to the DOM elements managed by React — for example, to focus a node, scroll to it, or measure its size and position. And to access the DOM elements, you need to use refs.

127. If you really need a DOM node, use a ref.

This is how you do it:

const myRef = useRef(null);
// ...
<div ref={myRef}>

If you perform those steps correctly, React will automagically put a reference to that node into myRef.current.

Then, you are free to do whatever naughty thing you would do to a DOM node, like myRef.current.scrollIntoView(). As always, keep dirty side effects like these on event handlers.

128. How to manage an unknown number of DOM node refs?

In the example above there is a predefined number of refs. However, sometimes you might need a ref to each item in a list, and you don’t know how many you will have.

Then, it’s time to get your hands dirty. There are two ways:

  1. Get a single ref to their parent element, and then use DOM manipulation methods like querySelectorAll. Hacky but legit!
  2. A more React-y solution involves passing a function to the ref attribute, which is called a “ref callback”. In short, you need to manually code the magic that React does for you in the previous case. Full details here.

129. Use ‘forwardRef’ to access another component’s DOM nodes.

So far we dealt with ref attributes on built-in components for DOM nodes. But if you try to put a ref on your own component, the magic is gone, by default you will get null.

What were you expecting? React doesn’t know the first thing about how you intend to use your custom component.

Components that want to expose their DOM nodes have to opt in to it like this:

const MyInput = forwardRef((props, ref) => {
return <input {...props} ref={ref} />;
});

Naturally, the component can pass the received ref to any internal component it wants.

You’ve probably seen this before it you aren’t a complete newbie. In component libraries, it is a common pattern for low-level components like buttons, inputs, and so on, to forward their refs to their DOM nodes.

130. Can you use ‘forwardRef’ to forward multiple DOM node refs?

This isn’t explained in the docs, but I wondered about it and found a legit guide on StackOverflow.

It involves the “useImperativeHandle” hook, which I’m about to explain.

131. ‘useImperativeHandle’ lets you control which functionality you expose with ‘forwardRef’.

That’s right, “useImperativeHandle” is meant to be used together with “forwardRef” when you want to do some custom hacky shit.

Or you can use it just to limit what the parent component can do with the exposed DOM node, like so:

const MyInput = forwardRef((props, ref) => {
const realInputRef = useRef(null);
useImperativeHandle(ref, () => ({
// Only expose focus and nothing else
focus() {
realInputRef.current.focus();
},
}));
return <input {...props} ref={realInputRef} />;
});

It’s an uncommon use case, so don’t worry too much about this hook.

131. Avoid changing DOM nodes managed by React.

If you stick to non-destructive actions like focusing and scrolling, you shouldn’t encounter any problems.

However, if you try to modify the DOM manually, you can risk conflicting with the changes React is making, which might result in a full crash.

Still, the docs acknowledge that sometimes this can’t be avoided, in which case you are recommended to put your hacker hat on before proceeding, for good luck.

Closing thoughts

Folks, let me tell you something, when I was first starting off my programming career, I would’ve never thought that the React docs would be this good.

They were always good, but they were never this good.

I remember when the only way to learn about React was to go to those fancy conferences and mingle with the cool hackers. You could count yourself lucky if they didn’t hack your Palm Pilot in retaliation.

So I encourage you, young and old, to take some time off your day and read the new React docs.

And maybe, just maybe, you’ll find that computer programming is the activity that will bring peace and prosperity to our world.

But most importantly, you’ll have fun. And that’s what it’s all about. May the force be with you.

If you’re up for a challenge, go ahead and check the last two topics from the docs, “Effects” and “Custom Hooks.”


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK