Hydrated State: Building Interactive Applications with Server-Side APIs and Client-Side State
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
This lesson preview is part of the Next.js Complex State Management Patterns with RSC course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to Next.js Complex State Management Patterns with RSC, plus 70+ \newline books, guides and courses with the \newline Pro subscription.
[00:00 - 00:24] Hello and welcome to the last lesson of this module. In this particular video, we're going to look at what we call high-traded state, which is the last technique that I'm going to talk about and I'm going to show you, which will come in handy when you want to transfer state or affect state or do anything, essentially with state that has a server component and a client component talking to each other.
[00:25 - 00:36] The first thing, again, as we've been doing so far, is answer the question of what problem we're trying to solve with this particular technique. And essentially, you've seen prob sharing from server to client component.
[00:37 - 00:43] You've seen cookies. But either of those techniques are limited and they are great for their particular use case.
[00:44 - 00:56] But there are always going to be different use cases that really don't make sense to be solved using those techniques. And when that happens, when those techniques are just not enough, here's where high-traded state comes into play.
[00:57 - 01:09] So high-traded state is going to make heavy use of what we call internal server-side API. Either if you've been using next for some years now or not, you had the option of creating internal APIs on the server-side.
[01:10 - 01:20] They are essentially files called route.js within a path of your application. And wherever next finds those files, it will create an endpoint.
[01:21 - 01:37] So that is what we're going to be using. You can think of these API endpoints as a middle layer between the data sources, such as external APIs, database, file system, or whatever else you want to get data from, and two, and your client components, or your UI.
[01:38 - 01:50] You could definitely-- and again, you might say that you're definitely able to not use an internal API endpoint and interact with an external API directly from the client. And that is completely true.
[01:51 - 02:01] You can send requests to external APIs. However, you might run into security issues because you might be sharing information you don't want to on a channel that is accessible to users.
[02:02 - 02:16] Or you might even run into course issues where the external APIs are not properly configured or a chest configured to not be queried directly by browsers. You probably faced some of these issues in the past.
[02:17 - 02:29] And the way to solve them is through internal APIs. Declined, or essentially the browser will always be able to query or send a request to an internal API because it's always within the same domain.
[02:30 - 02:35] It's always within a trusted environment. So it will trust your API endpoints implicitly.
[02:36 - 02:50] And that makes it a lot easier to just use those and then have the code that actually gets or modify the data inside that endpoint. And the question is now, why are these internal API endpoints relevant to us?
[02:51 - 03:00] That is the final way in which we're going to interact with our state. You might have state that is persistent somewhere else, your database, your file system.
[03:01 - 03:18] State that is too big to fit within a cookie or just too complex or just too varied to be shared through props. So you might sort it somewhere else in a place where the foreign data doesn't really have their access to and that is where the API endpoints come into place.
[03:19 - 03:27] To review this method, I'm going to show you different sections of our application over the sample application. First, I want to show you that context.
[03:28 - 03:37] And we already saw this and how we changed it to perform a better initial data loading on module 5. So I'm not going to focus on that.
[03:38 - 03:47] But this particular provider actually has a function that will affect the list of tasks. Not only on the client side, but also on the server side.
[03:48 - 04:02] So if we go back-- if we go to that function, it's called add task. And what this function is doing is simply creating a new task, essentially adding the default values like a new ID, the default state.
[04:03 - 04:12] It is performing an internal fetch request. To this endpoint, which we're going to look into it in a second, but you can see it's an internal API endpoint.
[04:13 - 04:20] It's going to be a post request. And it will have the content of the task as its payload, essentially.
[04:21 - 04:35] Now, as a response, if it gets a success code as a response, which we have here, then it will add the task that we're creating into its internal state. So think about it.
[04:36 - 04:48] It is modifying the copy of the state on the database through the internal API. And it's also updating its internal state, which is what all the other client components are depending on.
[04:49 - 05:01] So why do you think that we're doing both things? Why do you think we have to keep synchronized both state storages, essentially?
[05:02 - 05:10] The answer to that question is we don't have to. In fact, we could just have the database being the single storage for our list of tasks.
[05:11 - 05:22] However, if we did that, to update whatever you have to update on the front end. And in some cases, for example, updating the state of your task, making it as done or removing it, that makes sense.
[05:23 - 05:38] If you have very limited amount of tasks, which is our case on our sample application, then we don't really have to go back to the database, filter those values based on our filters, and then get back the data and show that data. We can definitely filter it live.
[05:39 - 05:46] And that is what this context is doing. The filter function is actually filtering the data without taking into account the database.
[05:47 - 05:53] Why is that? Because we have a copy, a synchronized copy of the data on the client and on the server side.
[05:54 - 05:58] This is something that is useful in this case. It's an optimization in this case.
[05:59 - 06:05] But it's not mandatory. And it's definitely not useful if you have too much information to filter or to query on the database.
[06:06 - 06:21] In those cases, it might be a good idea to simply have all these functions interact with internal API inputs and not have a copy of the state, how we have it here. Now, how do you implement an internal API input?
[06:22 - 06:40] If you've never done it before, or if you're new to the app router semantics, this is what an internal API input looks like. Essentially, inside your route file, you will create a function for each HTTP bear that you want to make available.
[06:41 - 06:46] In our case, we have the POST function. Because as you saw, whenever you're creating a new task, you're sending a POST request.
[06:47 - 06:56] So that POST request is going to be handled by the POST function. And then simply to get the payload, you do a JSON on the request, and you get that payload.
[06:57 - 07:03] And then I'm just calling the createTask function that is going to interact with the database. It's going to store that data on our state storage.
[07:04 - 07:08] Let's put it away. As a response, I'm just sending a success code back.
[07:09 - 07:16] And as an error, I'm sending it as a false, and then another message. This is a very simple API input, but it's also very useful for what we want.
[07:17 - 07:18] It's very to the point. It's not doing anything else.
[07:19 - 07:26] So up to the point, we saw that we're doing fetch request, and we saw that we have API input. So what is a hydrated state?
[07:27 - 07:44] What does it come into place? So the reason why I was introducing all this concept is because hydrated state can be defined as state that gets rendered on the server and then gets hydrated and made interactive on the client's side.
[07:45 - 07:51] And the perfect example for that, we already saw it, actually. And that is here, the list of tasks.
[07:52 - 08:05] We actually saw how to turn that on module 5, how to improve this page, make it load the data here. But we never really talked about this being hydrated state.
[08:06 - 08:20] But essentially, that is what it is. This server component is pulling the data from the database and then rendering on the server by turning it into a JSON and then passing it as a prop to the list of tasks, which is a client component.
[08:21 - 08:51] So it gets rendered, the state gets rendered, and then gets added into the server component payload, and then gets shipped into a client where it gets hydrated, where it gets made interactive through all the code in the client component. In our case, that means the state is shared without the components through the task context, as we saw already, and filtering task management, all those actions are made available on top of the state through the hydration.
[08:52 - 09:09] So when it comes to hydrated state, there are two practices that I always try to recommend the developers to keep an eye on. One is considering that the initial state render, the initial data fetch, essentially, should provide enough data for a meaningful render on the client.
[09:10 - 09:22] In our case, we were getting all the tasks from the database and sending all their information to the client. So the client could already, on the first render, show everything it needed.
[09:23 - 09:35] Imagine, for example, if instead of getting all the data, we were just getting the IDs of those tasks. Then on the client side, when the client component gets rendered, we would not have enough information to actually render all that.
[09:36 - 09:50] So you would have to perform different fetches to get the task details and so on. So there's not a proper way to do hydrated state, because you also affect the second point, the second best practice, which is client side fetch should be minimized.
[09:51 - 10:02] It means that fetching data from within the client is expensive. It's expensive in the sense that you're adding latency to the user interactions.
[10:03 - 10:19] Whatever the user is trying to do, even if he's just looking at a list of data, you add milliseconds to that user experience through the fact that you're having to go back to the server and perform that round trip. That round trip is what we're trying to eliminate with the first point.
[10:20 - 10:31] We're trying to provide enough data for a meaningful render on the first try. And then, yes, definitely whatever extra you want to do, either get more information if the user needs it or modify the information and store it.
[10:32 - 10:50] Yes, definitely you will need to perform client side fetches, but just be smart about them and only make them if they are needed. If you can provide enough data for a good first render that doesn't need any extra fetches, then you're improving the page loading speed.
[10:51 - 10:55] And that is a huge improvement and a direct improvement on the user experience. So keep that in mind.
[10:56 - 11:24] However, don't take this to the extreme of saying, well, I have 10,000 tasks and I'm going to just pull them all directly and show them all into the initial payload and then have the client render all 10,000 tasks directly. Just make sure that you define the concept of meaningful first render and provide enough data for that.
[11:25 - 11:41] In the case of the list of tasks, if you really have 10,000 tasks, you might not really need to display them all at once. You might be a good idea to paginate that either through an infinite pagination or just the classic one with all the page numbers at the bottom.
[11:42 - 12:03] So in that case, a meaningful first render might be the first 10 tasks and the list of pages, for example, at the number of pages or something like that. You can then perform more features in the background or whenever they're needed to get the rest of the data, but the first interaction the user has with the page is fast enough.
[12:04 - 12:19] So again, this is not a one rule to follow blindly and then do it like this every time. However, it is a best practice, a recommendation, and you have to be smart about it and adapted to your context, to your use case.
[12:20 - 12:33] And remember that I'm just showing you a never simplified version of what a real production application might look like and the amount of data usually is considerably different. So keep that in mind.
[12:34 - 12:47] And remember to properly define what a meaningful first render is in your case. And finally, if you really want to look at state hydration in action, then just go back to previous module.
[12:48 - 13:02] Go back to module five with this concept in mind and then re-watch those videos and you're gonna see that I'm actually implementing state hydration in both cases. I would just not mention it before because we were talking about another concept.
[13:03 - 13:18] However, if you go back, you'll probably get more out of these videos now than you did before. So as a wrap up for this module, remember that it is crucial to recognize the interplay between your server-side APIs and the client-side hydrated state.
[13:19 - 13:30] By combining the two, you can craft interactive applications that react to data changes without hindering performance, which is critical. So thank you for showing me in this last lesson of this module and the course.
[13:31 - 13:35] I'll just see you on the next one for a quick wrap up of the entire course.