
Lesson Description
The "Mixing Server & Client Components" 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 explains that server components cannot be children of client components, but there is a workaround for loading server data into a client component. He demonstrates this by creating a teacher view server component that fetches initial notes from the server and passes them as props to a client page client component. He also shows how to implement polling in the client component to continuously fetch new notes from the server.
Transcript from the "Mixing Server & Client Components" Lesson
[00:00:00]
>> Brian Holt: I wanna address the question around how do I mix server and compliant and client component. I'm teaching you these like siloed examples. So it's designed to be simple, right? Nothing in development ever ends up being that way we always have to mix concerns. So I mentioned that at the beginning, but I'm going to say it again.
[00:00:19]
Can't have a server component be a child of a client component. Once you opt into client land, everything in the tree beneath it is also in client land. So you can't go back. With kind of a little hack, do I show you that next time?
>> Brian Holt: Yeah no, so I'll show you the hack to get around it, but that's the next lesson.
[00:00:48]
>> Brian Holt: But what happens I need to load server data into a client component. Now, we could just do it like we always used to do, an effect, use effect. Right, and then loaded in. Nothing about that changed. You can still write API routes and next js, follow Scott's class for that, because I know he talks a lot about that.
[00:01:09]
We're not going to do that. I'm going to show you, kind of the cheater way. And I'm gonna say that you, you kind of already know how to do this. You just maybe haven't put, there's the two dots here you haven't drawn the line yet. So I'm gonna draw the line.
[00:01:21]
So I have this secret teacher feed that monitors every single note that's being passed in the class, and I want it to be up to date. So, I'm gonna pull. So I'm gonna have it pull every five seconds, 15 seconds to see if any new notes have been passed.
[00:01:37]
Now, what do I do on initial page load. That I could just wait for it to pull the first time, that would work just fine. But it'd be cool if it loaded the first time with the complete feed, right? So let's, let's do that, let's see how you would do that.
[00:01:52]
So make a new directory here, call it teacher. Make a page.js.then we're going to import teacherclient from./clientpage. All right, I call this teacher client page, whatever you want to call it, doesn't matter. Import fetch notes from ./fetch notes. And then we're going to export default async function, teacher view.
[00:02:39]
>> Brian Holt: And we're gonna say const initialnotes equals await fetchNotes, and then we're just gonna pass that into return TeacherClientpage, initialNotes equals initialNotes, and fetchNotes equals fetchNotes.
>> Brian Holt: This is it, you just have a server component on top and then you just do everything you need to in the parents server component.
[00:03:14]
And then you just pass it all into the client component because props work, right? We can pass all the props down, that all works totally fine.
>> Brian Holt: And now we just need to make this client page.js and we'll also need to do a fetch notes.js page. So let's go do fetch notes first, that's quick to do.
[00:03:42]
So this happens all in the server, fetch notes. So we're going to use server, and we're going to say import async database from promise sqlite3, and we're going to say export default async function fetch notes. And I'm going to give do it since, because basically you only want to grab things from a certain ID backwards.
[00:04:13]
And again, this is all database stuff, so you don't really have to be super concerned with it. Const db = await AsyncDatabase(".open./notes.db"),
>> Brian Holt: Let rows if (since) rows is going to be equal to await d.all. Select, how long is this?
>> Brian Holt: Yeah, again this is gonna be a quote.
[00:04:54]
n.id as Id n.note, feel free to copy this. Definitely don't need to go through this as well if you don't want to. f.name as from user and t.name as to user from notes and join.
>> Brian Holt: F on
>> Brian Holt: f.id equals n.from user,
>> Brian Holt: And then you have to have another JOIN yeah, yeah, JOIN users.
[00:05:46]
See JOIN users, and then I called this one t, yeah, t, on t.id, equals n.to, user.
>> Brian Holt: Where n.id, is greater than I needed, then question mark, and then, I put limit 50 in here as well. You can put whatever you want, though you don't have to have of limit on there if you don't need to.
[00:06:20]
And yeah, then since, right? So this will be an ID and it'll just give me everything newer than that ID. Otherwise we're gonna say else. Rows is assigned essentially the same thing you can even go as far to copy most of this. We're just gonna drop the where essentially, and this isn't gonna have anything in it.
[00:06:51]
Okay, and return rows.
>> Brian Holt: Okay, questions about fetchNotes.
>> Learners: How would you paginate this if you wanted to? Just high level?
>> Brian Holt: Yeah, you just have page right and then offset and all those kind of SQL tricks. Maybe in other ways nothing interesting to to show you. Limit 50 offset, question mark, right, and then you would do the math.
[00:07:26]
>> Brian Holt: Or actually, much better answer use something like Drizzle. Yeah, let Prisma do it for you.
>> Brian Holt: Okay, let's go make client page then. And so client page, first thing, this is a client component, right? So first thing we're gonna to put in here is use client. And then we're gonna import use state, use effect.
[00:07:56]
I mean 99.99% of the time you want you to be a client component if you need to use useState. You also might find yourself pushing client state down further and further in the tree so you can use more and more server components. And so just the leaf notes of your app end up being client components.
[00:08:17]
That's something that's just I have found as a habit that I didn't necessarily mean to develop, but I kind of did. Default function, teacher, client page, and this is going to take in fetchNotes and initialNotes.
>> Brian Holt: Okay, const notes, set notes equals use state.
>> Brian Holt: And if you have initial notes, then use that, you might not, for whatever reason.
[00:08:59]
And if you don't, then just use empty array, right?
>> Brian Holt: Okay, and then from there we're gonna do a use effect and we're gonna, because we want this to be polling, right. Then we're gonna use an effect to pull that end point.
>> Brian Holt: So const interval = set interval.
[00:09:33]
>> Brian Holt: Async function here, and then say that since if notes.length is greater than 0, then since is assigned notes, notes.length minus 1.id or null. This is called nullish coalescing, if that's not something that's familiar to you, that basically says if this exists, then it's an id. If nothing exists there, then it is null.
[00:10:15]
That's a relatively recent, I don't know. I can't keep track of time anymore. When you have a three-year-old, time just kind of all meshes together.
>> Brian Holt: It feels recent to me, I'm gonna put it that way. Const newNotes = await fetchNotes with since. So this will either be the id of the last thing that it saw or it's going to be null, and then you'll set notes, and then it'll be a concatenation of dot, dot, dot, dash notes and dot, dot, dot, new notes.
[00:10:53]
This is the same thing as saying like notes, dot, concat, new notes, same diff. And then we want this to go however much you want to, I'm just gonna say 5000 milliseconds. So every five seconds, this will pull and I don't want this to run again, so I'm just going to give this an empty array.
[00:11:14]
So it's gonna set up the interval, and then this effect will keep going after that.
>> Brian Holt: Okay, and now we're just gonna say return, and just do a little bit of markup here, div,h1, Teacher's view, h1, teachers view, and we're gonna do a ul. And we're gonna do note.map.note, and we're gonna return here an li with the key equal to note.id.
[00:11:57]
And it's going to be a field set.
>> Brian Holt: h2, from
>> Brian Holt: note.from_user, and to.
>> Brian Holt: note,to_user,
>> Brian Holt: Okay, that's the h2 and then we're gonna do a P here with the note.note.
>> Brian Holt: I think that's it. So should we check so note passer, I'm going to go to secret teacher feed and no such table F.
[00:12:50]
So that's definitely me and fetchNote, that's just me, probably not capturing this correctly. This right here, F right there, from user F. Do have to give it an alias? Nope, that's not right. It's the first JOIN, JOIN users F. Yeah, I mean, you'd be better off just copying and pasting this because it's just a long query.
[00:13:17]
Okay, so now we have our secret feed here.
>> Brian Holt: We can see down here at the bottom that's the last one I sent down there. So let's just create a second one here so we can see it working from the pull perspective.
>> Brian Holt: So I'm gonna say, local host.
[00:13:39]
What is this? 3000, write a note, and we'll write one from Sarah to Brian. Say, I think you smell. So if I click save, you can see there shows up pretty fast. It's pretty cool right? And you can see here you'll see the X as well.
>> Brian Holt: Yep, and again, you'll see those as well on your server, that it's just gonna keep polling that endpoint continuously.
[00:14:32]
>> Brian Holt: Pretty cool, right?
>> Learners: Is the polling showing up as a post?
>> Brian Holt: Probably, yeah, it is, isn't it? Because, it's all happening through the machinery of React flight and stuff. It's not restful, so it's just using it because it has a body and a bunch of stuff like that.
[00:14:55]
>> Learners: I'm getting a 500 on mine which is interesting, so it's working but it's failing, so I'm just looking at that right now.
>> Brian Holt: The polling's not working.
>> Learners: I like, it's working fine, it's just posting as a 500. So I'm thinking it's reaching out for an end point that
[00:15:08]
>> Brian Holt: Doesn't exist.
>> Learners: Yeah, I'm sure it's something I added on accident.
>> Brian Holt: Fascinating. It's interesting that it's 500 and it's still working.
>> Learners: I've never seen it happen like that, but-
>> Brian Holt: Yeah.
>> Learners: I'll take it. I think you need to clear your interval in your use effect,
[00:15:23]
>> Brian Holt: Yeah and I mean, if I was a better developer, which I am not, I would be here in page. Yeah, because it's going to keep going once you switch pages, that's actually a very good point. So you have to say const interval and then let's just get rid of this.
[00:15:40]
You should, again, if you're a good developer, return clear interval on teardown, because if you navigate back and forth from page to page, it's going to keep going. But memory leaks are fine. Not a big deal. [LAUGH] I think I even noticed that and I was like, I should like fix that, I was like, nah, can't be bothered.
[00:16:11]
Okay, any questions on any of this? Make sense?
>> Learners: My hair, by the way, it was the wrong column name. So I was getting all the data so expecting, and I was also trying to go on that doesn't exist.
>> Brian Holt: Got it? Okay, so you were getting something on the SQL side, right.
[00:16:31]
Cool.
>> Learners: Could you actually explain one more time why we need to use the use server directive?
>> Brian Holt: Yeah, this one here.
>> Learners: Yeah.
>> Brian Holt: Save it, see what happens, so what succinctly is going to happen here is that it just assumes that this should be part of the bundle.
[00:16:55]
You import this particularly from a component and so it's gonna include everything that you import into your bundles, right? And so it's going to try and run this function on Bootstrap. And it's gonna be like, you imported promise Sqlite3 here I'm going to include all of that in the bundle as well so that it's accessible from the client side.
[00:17:16]
And in reality, we want to use stuff in here that we can't. So that's why we have to use server and it's saying the stuff in here, leave them out of my bundle. It's only needed for things that are not components, right. For components, it's just implied to be there, but for server actions and server functions and all that kind of stuff, you have to put it there to explicitly say, please do not include this in my bundle.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops