Skip to content

Commit

Permalink
what's next in react
Browse files Browse the repository at this point in the history
  • Loading branch information
btholt committed Oct 21, 2024
1 parent 2cdd3ae commit 2b720fa
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 1 deletion.
9 changes: 8 additions & 1 deletion lessons/01-welcome/A-intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@
- Testing a component
- Coverage
- Future of React
- General React 19
- Intro to Intermediate
- use server vs use client
- preload and preconnect
- link go to header
- custom components
- See here to see more
- Form Actions
- Suspense
- Suspense / use
- React Compiler / React Forget
3 changes: 3 additions & 0 deletions lessons/07-testing/H-browser-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,5 +146,8 @@ We do have to bend over a bit backwards to make sure TanStack Router is happy, h

Again, these are early days for browser-based testing with Vite so proceed in your professional settings with caution. However the future is bright with Playwright!

> 🏁 [Click here to see the state of the project up until now: 14-testing][step]
[step]: https://github.com/btholt/citr-v9-project/tree/master/14-testing
[principles]: https://testing-library.com/docs/guiding-principles
[playwright]: https://playwright.dev/
61 changes: 61 additions & 0 deletions lessons/08-whats-next/A-react-19.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
This course has been centered on version 18.3. The nice thing about React is that while they do make breaking changes, they are usually fairly intentional about it and do so sparingly. In other words, what you learned today will still be useful five years from now. You could take the version of the course I taught five years ago and still be pretty up to date on how to write React. In other words, despite the fact that React 19 is imminent (or perhaps even out by the time you read this), there's no need to panic. All the stuff they are adding will just complement what you have learned and not change it.

Do keep an eye out for version 6 of my Intermediate React course. We will cover a lot of the React 19 features in that course.

So, given React 19 is about out, and we know 95% of what is going to be in it, we are going to cover a few features so that you can be prepared for it. Do remember, we are going to use the React 19 release candidate, which means while conceptually that are very unlikely to change big concepts, they may tweak the precise API of how it works. Just be aware that it may change a bit.

First thing, let's discuss a few features that are cool but not worth us spending a lot of time writing code for. Then we'll cover a few that we'll actually add code to our app for.

## 'use client' and 'use server'

Let's start with one of the bigger shifts in React. We will cover this _extensively_ in Intermediate React so if you are curious, check that course out when it comes out. Essentially we can indicate to React "hey, only run this component on the server" or likewise, "hey, only render this client side". Why would you do that?

For server components, we can do things like reference server-side secrets, query databases, call private APIs, or other things that we want data for our client side app but we can't query client-side without risking leaking secrets or DDOS. All of the logic will be executed server-side and then the finished component will be sent down to the client. Pretty cool, right?

They're even taking it step further with taintUniqueValue and taintObjectReference. These allow a develoepr to say "hey, this is super sensitive information, make sure this never is sent to the client". You would use this with like an API key, a database connection string, or some other secret that you wouldn't want to accidentally leak. I love this because what if a developer changes a 'use server' directive to a 'use client' directive? They may not notice and send it to production accidentally, causing a data breach. With the concept of taint, you can make sure that never happens.

'use client' is the opposite – something that would never make sense to render server-side. Think like a highly interactive component like a text editor or a maps widget or something like that – it's meant to be a client-side experience so you better off just passing all the down to the client immediately and let them do the rendering. Keep in mind that you don't need to use this directive unless you are doing server-side rendering. By default it will still do client-side rendering.

## Putting stuff in the head

Imagine you want your React component to add `<link>`, `<meta>`, `<title>` or other HTML pages in the doc. Right now you either need to use a portal or you need something like [react-helmet][helmet] to do that. With React 19, you just need to render the tag and it will automatically stick it in the header.

```javascript
const MyPizza = () => (
<div>
<h1>I love pizza</h1>
<title>🍕 Brian Loves Pizza 🍕</title>
</div>
);
```

Now whenever MyPizza gets rendered it will change the title of the doc (which what shows up in browser tab).

## preload and preconnect

Two pretty helpful functions to help give hints to the browser of what it may want to start loading in the background. If you are not familiar with the concepts, [head here][preload] to read more, but long story short it's for things like images, fonts, scripts, stylesheets, etc. You can let the browser know "hey, we're likely to going to need load these soon, can you either preconnect to this URL to get ready to download it or just go ahead and preload it?" This will allow your user to have more instantaneous expereiences since they won't need to wait for these things to load when required. Keep in mind these are hints though and the browser can ignore them. A good of example of this could be that your phone is in low-power mode, it may ignore those hints as it wants to save battery.

```javascript
const EnterConfirmation = () => {
preload("https://example.com/text-editor.js", { as: "script" });
return (
<div>
<a href="/text-editor">Click here to open the text editor</a>
</div>
);
};
```

## Custom components (aka web components)

It's always been possible, in theory, to use web components with React, but it's been a huge pain in the butt, involving refs and other careful planning to make sure you don't accidentally unrender and re-render it. Suffice to say, it was possible but impractical.

Now with React 19 it will just handle all of that caretaking of the custom elements for you, just treat it like any other normal tag and React will do all the babysitting for you. It's certainly a big step in making custom elements that work across Angular, React, Svelte, etc. but who knows, we've been talking about custom elements my whole career (seriously, I remember seeing a Polymer talk at the first Fluent conf I went to) and it still isn't here yet.

## More stuff!

[Click here][react19] to read the official React 19 blog post if you want to read more. Lots of cool stuff coming!!

[helmet]: https://github.com/nfl/react-helmet
[preload]: https://www.debugbear.com/blog/resource-hints-rel-preload-prefetch-preconnect
[react19]: https://react.dev/blog/2024/04/25/react-19
88 changes: 88 additions & 0 deletions lessons/08-whats-next/B-form-actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
Okay, so let's actually upgrade our app to be React 19.

> 🚨 Pay attention here, as you need to figure out which version of React to install.
[Go here][npm] and look at the versions of React published on npm. If you see that React 19 is still in canary/rc/next (which are all the same) then you can run `npm i react@rc react-dom@rc`. You'll get a lot of warnings about things not matching versions in your command-line, that's totally okay.

If React 19 is latest, then run `npm install react@19 react-dom@19` and then follow these instructions. Be extra cautious because things may have changed!!

Okay, let's talk form actions. A lot of web interfaces is just handling form inputs, and so the React team decided to make it easier to do that. It's actually really similar to what we've done already, just a little less boilerplate. Open your contact.lazy.jsx and let's modify this page to use a form action.

```javascript
mutationFn: function (formData) { // change to formData
// remove e.preventDefault
// remove formData constructor
return postContact(
formData.get("name"),
formData.get("email"),
formData.get("message"),
);
},

// change onSubmit to action
<form action={mutation.mutate}>
```

That's it! It really is just a convenience function to make handling for submits even easier to do. There's nothing wrong with what we had either, and that will continue to work as-is too.

Let's go convert order.lazy.jsx too

```javascript
// extract submission function from form
function addToCart() {
setCart([...cart, { pizza: selectedPizza, size: pizzaSize, price }]);
}
// update to action
<form action={addToCart}>[…]</form>;
```

Same as above. But here's what cool if you're using somethingl like Next.js or Remix: we can use the `'use server'` directive here and make this a _server_ action. Something like

```javascript
function addToCart(formData) {
"use server";
sql(`INSERT INTO cart (user_id, pizza_type, size) VALUES ($1, $2)`, [
formData.pizza_type,
formData.size,
]);
}
```

Since it's on the server, we can now safely insert into our database, directly from inside our React component. React/Next.js will handle all the details of handling the execution of that on server. Pretty cool, right? We'll talk more about this in Intermediate React v6.

Let's do one more cool trick here. There's a new hook called `useFormData` that lets children components see if they're inside of a form being submitted without having to pass lots of data around.

In contact.lazy.jsx

```javascript
// at top
import { useFormStatus } from "react-dom"; // note react-dom, not react

// replace the two inputs
<ContactInput name="name" type="text" placeholder="Name" />
<ContactInput name="email" type="email" placeholder="Email" />

// at the bottom
function ContactInput(props) {
const { pending } = useFormStatus();
return (
<input
disabled={pending}
name={props.name}
type={props.type}
placeholder={props.placeholder}
/>
);
}
```

- This is a silly example, but imagine you had really complicated inputs that had a design system, tool tips, and all sorts of other UIs. This is common in large React codebases. This could be really useful for that.
- As a side note, if I have really small components like ContactInput, I'll just stick in the same file like we did here. It can be a useful pattern. Some people are purists and demand one file, one component. I am not a purist.
- We didn't do the button or the text area just for brevity's sake but you could.
- Since we're local, this pending state will be super short. But you will notice the background flash gray. That's the disabled state.

And that's form actions!

> 🏁 [Click here to see the state of the project up until now: 15-form-actions][step]
[step]: https://github.com/btholt/citr-v9-project/tree/master/15-form-actions
75 changes: 75 additions & 0 deletions lessons/08-whats-next/C-use-and-suspense.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
title: "use and Suspense"
---

We've been talking about React Suspense for a long time, and technically still we are not suppose to use it directly. But we React 19 we will get some blessed ways to start using it. (Some libraries already use it.)

The idea behind Suspense is that a React component can start rendering and then suspend itself if all its data hasn't loaded yet. This is cool because your code reads really well: it basically looks like you're assuming the data is already there, and React takes care of getting it behind the scene.

This is what the idea of the new `use` hook is from React. You say `use(myPromise)` and the React component will only render once myPromise has rendered.

Luckily TanStack Query already has a few ways of doing this, so we're going to use that. But just know that `use` works with any promise, not just TanStack Query.

First open App.jsx, we have to enable the experimental support for this in TanStack Query (you may not need to this in the future.)

```javascript
// replace queryClient
const queryClient = new QueryClient({
defaultOptions: {
queries: {
experimental_prefetchInRender: true,
},
},
});
```

Now in past.lazy.jsx, we need to do some refactoring.

```javascript
import { Suspense, useState, use } from "react"; // import Suspense and use

// move query and page hooks to ErrorBoundary component
function ErrorBoundaryWrappedPastOrderRoutes() {
const [page, setPage] = useState(1);
const loadedPromise = useQuery({
queryKey: ["past-orders", page],
queryFn: () => getPastOrders(page),
staleTime: 30000,
}).promise;
return (
<ErrorBoundary>
<Suspense
fallback={
<div className="past-orders">
<h2>Loading Past Orders …</h2>
</div>
}
>
<PastOrdersRoute
loadedPromise={loadedPromise}
page={page}
setPage={setPage}
/>
</Suspense>
</ErrorBoundary>
);
}

// first line in PastOrderRoute
const data = use(loadedPromise);
```

- We use Suspense to tell React where to pause rendering until _everything_ in it has resolved. If you have several things that suspend, this won't render until they all complete.
- fallback tells it what to render while it's suspended
- We need to query inside the parent component (and thus keep track of the page too) because the promise needs to exist outside of the component that is getting suspended. Otherwise it'd be freshly called every single your component rendered. Similar to error boundaries.
- We shoved everything in our error boundary wrapping component. This should probably be renamed.
- Again, we're using TanStack Query's support for this, but it could be any promise. TanStack Query just made it easy.
- TanStack Query [has several other ways of doing Suspense][tsq-suspense]. We just wanted to try the `use` hook.
- [use][use] has a few other uses. Check it out in the docs.
- Unlike most hooks, use, can be used in conditionals and for loops. Kinda weird given most of the rules around hooks, but still true.

> 🏁 [Click here to see the state of the project up until now: 16-use][step]
[step]: https://github.com/btholt/citr-v9-project/tree/master/16-use
[tsq-suspense]: https://tanstack.com/query/v5/docs/framework/react/guides/suspense
[use]: https://react.dev/reference/react/use
53 changes: 53 additions & 0 deletions lessons/08-whats-next/D-react-compiler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
We didn't talk much at all about performance hacks with React but there are a few. Generally speaking, React is in its "unoptimized" state is fast enough and adding these performance hacks on top make shave a millisecond here or there off, but in general aren't worth doing unless it's a big gain because it makes it harder to code with.

Specifically I'm talking about [useMemo][memo] and [useCallback][callback]. These two tools allow you to control when React will re-render your app. If you're rendering a massive spreadsheet and it's not changing frequently but its parent is, you can use useMemo to say "hey, this doesn't need to re-render unless this condition is true". This only really helps when it's a big thing like this. I don't even teach this in this course because you use it very infrequently and some newer devs get tempted to use it all the time. It will make it hard later to understand why some things are rendering and some aren't. It makes bugs harder to find.

Enter React Compiler (formerly known as React Forget). The React team at Facebook is basically going to do these optimizations for you. It's going to detect "hey, this component shouldn't ever change, I'm going to put useMemo on it for you." I love this as it's free performance with zero thought on behalf of the developer. It's pretty conservative about when it does this, so it should work without a hitch.

> If you do ever hit a case where it is memoizing something and you don't want it do, you can stick "use no memo"; at the top of a file and the React Compiler will skip it.
So let's run the checker on the app.

```bash
npx react-compiler-healthcheck@beta
```

Our app is good to go! You can run this on your codebase too and see if it will work on your code.

Let's go ahead and install the plugin.

```bash
# also prone to change as they're rapidly iterating on this
npm i -D babel-plugin-react-compiler@beta --force
```

Now add this to your vite.config.js

```javascript
// replace react()
react({
babel: {
plugins: [
[
"babel-plugin-react-compiler",
{
target: "19",
},
],
],
},
}),
```

Now let's run your app! If everything is done right, you will notice _nothing_. It will look no different. But open your React dev tools and look through your components. You should see lots of components have "Memo ✨" next to them. These are the components the React Compiler able to automatically optimize for you.

And that's it! Over time this will get lots better. The React team is saying this is safe to try on your codebase today, so you might try it and see if it works for you. I know they'd love feedback on their GitHub repo.

If you want to learn more, I'd strongly suggest [Lauren Tan's talk from React Conf 2024][lauren]. She was/is the eng manager over React Compiler.

> 🏁 [Click here to see the state of the project up until now: 17-react-compiler][step]
[step]: https://github.com/btholt/citr-v9-project/tree/master/17-react-compiler
[callback]: https://react.dev/reference/react/useCallback
[memo]: https://react.dev/reference/react/useMemo
[lauren]: https://www.youtube.com/live/T8TZQ6k4SLE?feature=shared&t=12020

0 comments on commit 2b720fa

Please sign in to comment.