
Lesson Description
The "Server Components & Data Fetching" Lesson is part of the full, Intermediate React, v6 course featured in this preview video. Here's what you'd learn in this lesson:
Brian introduces server components, demonstrating how they use async functions to fetch data from a SQLite database and render it in a table format. He explains that server components always execute first and handle data retrieval before passing the results to client components. Brian also implements the createRoot and createFromFetch functions, illustrating how to render components efficiently on the client side.
Transcript from the "Server Components & Data Fetching" Lesson
[00:00:00]
>> Brian Holt: Okay, so server component. This is the more interesting part of this course, ServerComponent.jsx. Again, assumed to be a server component, so no use server is necessary. And now we can do something like import(AsyncDatabase) from sqlite3. Cuz this is all running on the server, right, and so we can read files from local disk, which is what we're about to do.
[00:00:27]
We can import path from node:path. Again, can't really do this, path doesn't work in client-side components, but because we're on serverland, we can do that. Again, this assumes you're logged in as id 1, cuz we're not doing auth today.
>> Brian Holt: Export default async function MyNotes. Another very nice part about server components is they get to be async.
[00:01:05]
So we can async await directly in our function bodies, which is very nice, right? You can't do that with client-side components. So on one hand, very nice that we can do a way to directly in our function body. On the other hand, it's cognitive burden to know that this is a server component, therefore I can async await here.
[00:01:28]
But on this one, I can't for other reasons, right? It's just another level of complexity onto your app here. Some people are very okay with that, and some people are like, this feels harder, cuz objectively, it just is. We'll have our console log here, console.log("rendering MyNotes server component").
[00:01:57]
>> Brian Holt: And then I'm just gonna write an async function right her, fetchNotes.
>> Brian Holt: Some of the rules kinda go out the window here. This is just gonna run once, and it runs on your server. So worrying about churning too much stuff and introducing stuff into the hot path doesn't make as much sense cuz it doesn't matter as much anymore.
[00:02:21]
This isn't gonna run a bunch of times on the client, it's gonna run once on your server. So you can be a little bit more inefficient, I suppose, is guess what I'm trying to get at. Still that's running your server, that still incurs server costs, but this won't.
[00:02:35]
The amount of memory this is gonna burn and churn just by instantiating the function directly in the function body, as opposed to being outside of it, nobody cares, right, negligible. Console.log("running server function fetchNotes").
>> Brian Holt: Okay, const_dbPath = path.resolve. We're gonna put dirname. Do I have to define dirname?
[00:03:11]
I probably do. I don't have it in my notes, so YOLO, we'll see if it works, ../../notes.db. Is that correct or is it just ../?
>> Speaker 2: You got it right.
>> Speaker 3: It matches what you have.
>> Brian Holt: I don't know if I trust previous Brian, dot shifty. = await AsyncDatabase.open(dbPath).
[00:03:49]
So if you're not super familiar with SQLite, one, there's a course on Frontend Masters from a very handsome gentleman that teaches you SQLite as well. So that's available too, but it's just a file, right? So it's not actually connecting out to a real database, it's just a file, which is this notes.db file, which is why it's opening.
[00:04:11]
And then we're just gonna run a query. So const from = await db.all. And we're just gonna write a really quick query here, SELECT n.id as id, n.note as note, f.name as from user, t.name, let's see where we are, as to_user. FROM notes n, you can copy this query as well, this is not a SQL course.
[00:04:59]
JOIN users f on f.id = n.frrom_user, and JOIN users t on t.id = n.to_user.
>> Brian Holt: Where from_user =? I mean, you can just put 1 there if you want to, but in the name of some discipline, we'll just put 1 there, parameterize it, right? And then sometime later, if you feel like changing this to be a bit more robust, you can just replace this with whatever user id you wanna select for, right?
[00:05:51]
So we have a notes database. This is just gonna go grab all of the notes, and it's going to then join that to the from_users, or to the users table twice, once for the from_user, once for the to_user, so that we see usernames instead of IDs. That's all this is doing.
[00:06:11]
>> Brian Holt: Okay, and then this is from, so we're going to return just from.
>> Brian Holt: So this is just gonna be everything from user 1, right?
>> Brian Holt: Okay, so there is that. And then we're gonna say here, const notes = await fetchNotes, and we're going to return.
>> Brian Holt: Something very similar.
[00:06:53]
Again, feel free to copy and paste this, it's kind of a long component to nothing in here is gonna be super interesting for you from a perspective of learning more React. Other than just like sometimes, the practice is good. But we're just gonna turn everything into a table here.
[00:07:14]
Legend div table, and then we have to have a tea head, a thead, if you will. Tr, table row, th, from whatever you wanna say.
>> Brian Holt: To and a th for Note, don't do that. Okay, under the thead, we need the tobody, as they say.
>> Brian Holt: And we're gonna say, notes.from.map.
[00:08:00]
>> Brian Holt: I'm gonna say id, note, from_user, to_user. Okay, and then this needs to be outside of that one.
>> Brian Holt: And we're just gonna make a row for each one of them, right? So tr key=id. And we'll have some definitions here, td from_user, td to_user. This is a place where I definitely would have used AI, just say, generate a table for me that has these things for me cuz this is tedious to write, right?
[00:08:45]
But very understandable for an LLM to be able to generate.
>> Brian Holt: Note.
>> Brian Holt: Okay, so it's just a table showing all of the notes that exist in the table there. But let's talk, what's cool here. This is a React component, and we're writing raw SQL directly in it.
[00:09:12]
There's a part of me from that's been writing React salon that's uncomfortable with this, right? I remember when they showed the demo on stage of, hey, you can do this, you can write SQL directly on your components, and there was revolt, people were upset by this, right? Like, no, that doesn't go there, right?
[00:09:28]
But it kinda does, right? It's nice that everything all lives here. This is selecting the data that it needs, and this is displaying the data that it needs. And I don't need two routes, it's all just kinda shoved together. It's kinda nice, right? It kinda makes sense. The original premise of React is that we took all of the concerns for single components, we just shoved them all together into one small, nice, little package.
[00:09:53]
And this is just one step further in that, right? We're taking now server concerns and also shoving that into that so you have these components that do everything, right? They do everything for these very small little components, which is nice.
>> Speaker 3: Basically like an exploit.\, somebody put HTML into this database, you pull it out, you try to render it, would it actually render the HTML or a script?
[00:10:23]
>> Brian Holt: I see the question, is there a cross-site scripting in here somewhere? Inevitably, the answer to that question has to be yes. Right, there is some way that that could be done. I'm gonna say that you have several layers of failure to get there. How did someone get raw HTML into your database from user-generated content?
[00:10:45]
If you are saving user-generated content into your database, it needs to be escaped before it goes in there, hard stop. And so if they're getting real HTML into your database, you already failed number one. And then two, I guess you could escape it again coming out or do something of that nature.
[00:11:04]
But again, it feels like it should have been escaped when it got in there, not the other place around. I guess you can say this note is UGC, right, and I'm not doing anything about it. React does some of this for you actually automatically. If you actually wanna set it as real HTML markup, you have to do, do you remember this?
[00:11:22]
__dangerouslySetInnerHTML, I think is what it is, right? And then you give it the td or something like that. That's the only way that React will voluntarily actually turn your string into market. Don't, [LAUGH] I think it's my plea for you, please don't do that. So not a big concern on my part.
[00:11:55]
Then again, let's test it, I don't do anything in here to deal with that. But good question.
>> Speaker 3: Are there any gotchas with Orms or using other database technologies in here? Is it just the same sort of patterns that usual?
>> Brian Holt: No gotchas here, Drizzle will work great here, Prisma.
[00:12:16]
Neon's kinda neat sometimes, right? All that stuff would work just great here. You can essentially treat this like an API route. Whatever you would do with an API route, you can go ahead and just kinda treat this as an API route, because functionally, that's what it is. We now need our client.jsx, which we did before.
[00:12:37]
This is like the part that only renders in the browser. Let's go ahead and do that very quick. We're gonna do an import createRoot. We're not doing RSC's and server side rendering together. Like I said, it is possible, but we're not gonna do that today, /client, import createFromFetch.
[00:13:03]
This is kind of an interesting one, this comes from react-server-dom-webpack/client. So this is the part where basically it's going to receive markup from the server, and then it's going to turn that markup into React components. That's what this createFromFetch is going to do for us. And then we need our CSS as well, import.
[00:13:31]
Just do it like this, doodle.css/doodle.css, this is the name of the package. This is the name of the file, they end up being the same thing.
>> Brian Holt: Okay, console.log("fetching flight response"), const fetchPromise = fetch. And then we're gonna fetch from this endpoint called react-flight.
>> Brian Holt: Const root = createRoot document.getElementById, and we're going to get root.
[00:14:21]
Const p, you can call whatever you want, but this is just a promise, createFromFetch(fetchPromise); fetchPromise like that.
>> Brian Holt: And then if you wanna see this, I thought that was kind of interesting, you can see the rendering root and you can see the promise here. And then root.render, and this is the part that kinda blew me away when I saw it, (p).
[00:14:54]
So this root.render now knows what to do with the promise. So it's gonna go out, it's gonna get the markup that it needs from the server. And the render's gonna wait on that promise to be done, and then it will render everything out for you. Wild, right, to me, I found that was very interesting.
[00:15:11]
But yeah, the novelty here is this function here, createFromFetch.
>> Brian Holt: So this is what you'll end up doing if you ever decide to implement this by yourself. Again, you probably won't, trying to do this in the future. I don't know, I'm not your mom, feel free to do whatever you want.
[00:15:26]
But you'll end up having one endpoint that'll handle all of these React fetchings and all that kinda stuff. If you look at your Next.js logs, you'll see that they all come and go from one endpoint typically.
>> Brian Holt: Okay, we're so close to getting done with this, our client-side react app is done.
[00:15:49]
So this is all ready to go. But now we lack the next part of heavy lifting here, which is, we need the server that's aware of our components that's able to actually do the server side rendering here.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops