Frontend State
Handling State in React
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 Fullstack Typescript with TailwindCSS and tRPC Using Modern Features of PostgreSQL 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 Fullstack Typescript with TailwindCSS and tRPC Using Modern Features of PostgreSQL, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

[00:00 - 00:15] So we need to think about how we're going to handle frontend state as we reach higher complexity. Frontend state is something that has a lot of solutions and for a while it was redux that everybody was using.
[00:16 - 00:46] That seems to have fallen out of favor in recent years which I think is a shame actually because it's still a very fine library and it requires much less plumbing code than it used to which was one of the things that people had against it. I still think that it is a little bit heavy handed though and for our simple needs there is no need for redux and the other popular frameworks like Sustand or Recoil or Jotai I think one is called, they're all fine but they are a little bit too big for what we need here.
[00:47 - 01:06] I think we can get away with just using React context and then store a little bit of state in our page that is going to keep track of where the user is and what they have entered into our forms. Let's create a context and a little hook that encapsulates the logic that it's going to use.
[01:07 - 01:21] I'll create a new file called booking flow context and I will paste this in. Let's quickly go over this.
[01:22 - 01:34] So what this does is basically it creates a context here. So create context is the React function and that creates a context of the type booking flow context which is specified just above here.
[01:35 - 01:43] The booking flow context type basically has two primary things. So there are some methods or some functions and some state.
[01:44 - 02:01] It will provide on go back and on proceed which will allow the user to go back and forth in the flow and then there is the state which is of type booking flow state which is defined right above here as well. And that is basically where we're going to store whatever the user enters throughout the flow.
[02:02 - 02:09] So they're chosen date and their email address and so on. We don't have any of that right now and that's why I'm just using this empty object.
[02:10 - 02:27] You can see I'm getting a linter error here because supposedly just a double quote or curly brackets isn't good for specifying the empty object. So what I'll do is just disable this for now because we're going to be changing it in a minute when we start adding some state to it.
[02:28 - 02:56] But right now there's the final thing that this has is an update state function and that will take some fields that are whatever can be in this booking flow state so it doesn't need to be all of it but can be part of it and they will update the state with those. We then create the context and we need to create it with some default values and the reason why I'm setting this to CTX missing which is a function that I've created right here.
[02:57 - 03:14] Sometimes I create this as a shared helper function because it's useful for almost any context. If we don't specify a default value we will have to check for undefined everywhere where we're consuming this context which is annoying and just adds a bunch of typesc ript plumbing.
[03:15 - 03:36] So it's nicer to specify the default value and say there will always be something and then if we know that we're not going to run into a situation where there's no provider around we can safely just set this which will throw an error on runtime if we should have created an error somewhere. As state it will just have the empty object.
[03:37 - 03:50] Finally we then have this hook and basically it takes a parameter called flow which is an array of functional components. So these are basically going to be the pages that the user can switch back and forth from.
[03:51 - 04:12] And so we've moved the active page index and set active page index state in here away from the component itself and then we've also moved this state state in here which is currently just the empty object but we'll improve on that later on. What we then do is we create this memorized value that is being fed into the context provider.
[04:13 - 04:27] So one thing to note here is that I'm creating a component live in this hook and that is definitely a design choice on my part. You may dislike this way of doing it and there are many other ways of doing it but this works as it is.
[04:28 - 04:40] So I just went with this. What it does is basically it takes the provider for the context and uses this value that we've memorized here and then it puts the content tense as a child for that.
[04:41 - 05:00] And the contents is whatever page we were looking at given our current index. So basically we can, by doing so, we can be sure that whatever page we put into the flow array here, they can safely consume this provider because they will always be wrapped inside of it.
[05:01 - 05:19] The value that we're using here is basically so on go back and on proceed our self-explanatory, those are similar to what we had before. The state value is just the state that we're setting here and then we have update state which will set state with whatever it was before and then some whatever new fields we might have added.
[05:20 - 05:32] So it basically just combines those two objects. We then in the end return an object that contains this page, the active page index and the set active page index.
[05:33 - 05:46] So that is basically a utility function that we can use. And we may choose to do so or we may choose to rely strictly on the go back and proceed functions.
[05:47 - 06:00] We can now change our component to use this hook instead. So we'll get rid of this and instead we will fetch everything from our hook.
[06:01 - 06:18] So we need to get the page, the active page index and the set active page index and we call use booking flow and then we need to specify our flow and we don't have that yet. So let's just quickly define that.
[06:19 - 06:31] That will simply be these pages. So let's just say choose type page, choose date page and enter email page.
[06:32 - 06:34] That is our flow for now. So we're not using these anywhere.
[06:35 - 06:59] First of all, this conditional stuff can go now because we just need to show the page here. And now these buttons, while they're actually failing, we need to change this to active page index and well, these are called active page index as well.
[07:00 - 07:09] So this should do it for now. Let's just get rid of these.
[07:10 - 07:13] There we go. We still have the server running.
[07:14 - 07:20] So let's try and see what things look like at this point. We can still go back and forth through these.
[07:21 - 07:24] So and we can still go further. This actually fails now.
[07:25 - 07:38] I'm not completely sure why, but other than that, everything works the way it used to. Obviously, that's going to be because the page will be undefined when you go outside of the erase length.
[07:39 - 07:51] Anyway, let's try and improve on this. So one thing I want to do is that I want to move the back and next buttons into the page itself because they might need to be conditionally disabled.
[07:52 - 08:00] And in fact, the first page shouldn't have a back button at all. And the last page maybe shouldn't have a next button but a complete button or a thank you or something like that.
[08:01 - 08:18] But what should be out here maybe is some sort of progress indication. And I think we're going to do that by combining some class names and creating little circles that indicate whether or not you're on a page or whether it has been visited or if it will be visited.
[08:19 - 08:31] And so when combining class names, there's a popular library called class names , but there's a different one called CLSX that I tend to use. Based on their marketing, it's a lot faster and simpler.
[08:32 - 08:40] I haven't actually done any benchmarks myself, so I'm just trusting them. There are also some newer alternatives that are specific to tailwind that can do various trickery.
[08:41 - 08:47] I don't have experience with those yet, but I definitely think they look promising. So I might be looking into that in the future.
[08:48 - 09:05] But for now, CSLX, CLSX, pardon me, will work for this. So let me go into the front end and add CLSX.
[09:06 - 09:10] And we still have Veed running. I think it picks up on new dependencies like so.
[09:11 - 09:23] So we can import it here. It's declared but never used.
[09:24 - 09:38] So what we're going to be doing is let me just copy some code here and insert it. So we're already specifying this div here to be a flex with column layout, column direction.
[09:39 - 09:53] So that means that if the page is a div, this will go above these buttons. Let's actually wrap this in a div so we're absolutely certain that it will.
[09:54 - 10:11] Just in case this is nice so that we don't have to rely on the page being one div itself. If we had a page that was just a react fragment that contained two divs, then those would be positioned by this flex direction that we had set out here, which is in my opinion, not very nice.
[10:12 - 10:24] This way we can make sure that the page can do whatever it wants. So the page comes first and then after that comes these buttons, but we'll get rid of those because we want to move those into the page itself.
[10:25 - 10:33] And then instead we will add some circles and I have created this beforehand. So let me just paste this in here.
[10:34 - 10:55] So what we have here is a div that is flex and flex direction row, which means they will be located horizontally next to each other. And I'm using just to give a little bit of tailwind info here, this space X, which will create spacing between the flex elements inside of this div.
[10:56 - 11:11] It's a very nice little utility that it took me a little bit to learn because this doesn't correspond to one particular CSS style thing in CSS. So that is why it tripped me up a little bit.
[11:12 - 11:21] What we then do here is we take the flow, so basically these three pages and we map over them. However, we don't use the page itself for anything.
[11:22 - 11:35] So the first parameter we're specifying here is just an underscore because what we really want is just the index. So basically we just want numbers zero through two to create divs that look like this.
[11:36 - 11:48] And they have the index as the key, which is something that people consider an anti-pattern as well, but it will be reliable here. The index is not going to change.
[11:49 - 12:07] And then we use CLSX to figure out if the index is the active page index, then this should be a green text and a slightly green background. If this index, so this circle that we're looking at right now is beyond the active page index, then it should be gray 400.
[12:08 - 12:14] And if it's behind it, it should be gray 200. And then we specify some other classes that should always apply.
[12:15 - 12:24] So we make sure we get nice round little balls. And then we, as text inside the circle, we will say index plus one.
[12:25 - 12:32] We don't want the first page to be called page zero. So let's try and see what this looks like.
[12:33 - 12:40] So that is what I was hoping for. So the first page, which is what we're on, and now we can't go to any of the other pages right now because we've lost the buttons.
[12:41 - 12:51] So you'll have to take my word for it. But once we go to page two, the two will be green and the one will be light gray.
[12:52 - 13:07] OK, so let's try and actually create the choose type page. I'll create a new file.
[13:08 - 13:14] And I'm going to paste this from the article. So basically, this is a functional component like we already had.
[13:15 - 13:20] It now uses the on-proceed from our context. So it can go to the next page.
[13:21 - 13:29] We're not capturing any state just yet, and we're also not updating any state. But this, I hope this is pretty straightforward.
[13:30 - 13:41] It's a pretty simple form, even though there's actually no form component, but it's the equivalent of an HTML form. So there's a little header and some text, and then there's a text area for a description.
[13:42 - 13:53] Then there is a UL and unordered list with some radio buttons for choosing service types. And you can choose either type one or type two.
[13:54 - 14:04] And then in the end, there is a button that we'll call the on-proceed where it says choose time. So basically, that will take you to the next page.
[14:05 - 14:11] So pretty straightforward. And let's use this instead of our little placeholder in here.
[14:12 - 14:25] So if we delete that, and then we can auto import this instead, let's take a look at what this looks like. All right, so this is starting to look a little bit like a booking flow.
[14:26 - 14:30] So I can type something in here. It doesn't go anywhere, but I can at least choose.
[14:31 - 14:45] I can even choose these radio buttons. They look a little bit out of place here, and radio buttons and other input components per default are a little bit difficult to style with tailwind CSS.
[14:46 - 15:02] They don't work very well with utility classes, but there's a little plugin that we're going to install called the tailwind CSS forms that helps with that. Before we do that, let's just try and click on this button and see if the on- proceed is called.
[15:03 - 15:12] Well look at that. Choose date, and now we're on page two, and page one is the light gray.
[15:13 - 15:17] This is all tailwind forms. We'll do that.
[15:18 - 15:22] Maybe I should just keep this terminal open. We'll add that as a developer dependency.
[15:23 - 15:38] So it's called at tailwind CSS slash forms. And then we need to add it in our tailwind configuration.
[15:39 - 15:50] So there is an empty plugins array here. We will add it and the way the documentation states it, you simply require it right in here.
[15:51 - 16:02] We are now getting a weird Linter error, but since this is a configuration file , I'm going to ignore it just for now. Obviously, there are a few Linter errors here and there.
[16:03 - 16:05] You will have noticed it. I think I had one up here.
[16:06 - 16:24] I removed those, but you will want to enforce Linting on CI, and in that case, you need to get rid of all of these errors. And one way to do it is to add little disable statements above, but obviously it's better to actually get rid of them in the proper way, whatever the solution is.
[16:25 - 16:38] So with this in place, let's try and see if things have changed here. You can see now that these radio buttons have changed their appearance from what they look like before.
[16:39 - 16:52] And not only that, if we wanted to change the outline color and so on now, it would be much easier to do using the utility classes. But I think we'll just keep it like this because this actually looks fine for our needs.
[16:53 - 17:05] So the final thing we want to do in this lesson is to actually store the state that the user enters here. So we have, we can see here we have this description, and then we have the service type ID.
[17:06 - 17:13] Actually this isn't really service types just yet. We do have that table in our database, but we're not using it here.
[17:14 - 17:26] But let's just say that we have a service type ID, which can now be either one or two, and then this description. So we will start by going into our booking flow context here and update our type.
[17:27 - 17:50] So we will now here have a service type ID, and we will make that a number, and we will have a description. And the reason I'm making both of these optional is that this booking flow state is supposed to be the state that is during the progress of the flow.
[17:51 - 18:00] So initially there will be no service type ID selected and there will be no description. But as soon as the user has entered those, then they will be there.
[18:01 - 18:12] And now I could get rid of this lender disabled because now there is actually a proper type here. So we will update our choose type page to actually use this.
[18:13 - 18:30] So first of all, we can, in addition to this on proceed, we can get the state element out of our context, and we can also get the update state function. So we need to now specify that this text area should represent whatever the description was.
[18:31 - 18:38] So we will set the value. And you can see here now that the state is correctly typed.
[18:39 - 18:48] So the value should be the state dot description. And on change will represent changing that description.
[18:49 - 18:54] So we will create a little function here. So target.
[18:55 - 19:05] So when you get something from an input component, it will always have a target that has the value on it. So I just destructory this directly in the parameters.
[19:06 - 19:12] You could also just say event and then use event dot target. But this works fine.
[19:13 - 19:20] We'll say update state. And then this needs to take an object that is a partial of our state.
[19:21 - 19:28] So this will take whatever fields we want to update in our state. And the only field we want to update here now is the description.
[19:29 - 19:36] And that should be target.value. Do I have all my parentheses matched here?
[19:37 - 19:39] I think I do. Right.
[19:40 - 19:52] So the other thing that we need to support is our little radio button boxes buttons here. So both of these, let's just do this manually now.
[19:53 - 20:12] So it will be checked if state dot service type ID equals to one. And on change is going to be.
[20:13 - 20:26] Let's see, we will update state to be one. Don't actually need this value.
[20:27 - 20:37] We'll just call this when, yeah, that will be fine. And so I can copy these down to this.
[20:38 - 20:48] I need that one as well. Let's just say two here.
[20:49 - 20:52] Let's try and see what that looks like. Well, it shouldn't look like anything.
[20:53 - 21:00] So it still works. And that's nice because it could have not worked if we had done something wrong .
[21:01 - 21:07] I assume if you're doing any react development, you will have the react developer tools installed. If not, I definitely recommend it.
[21:08 - 21:20] I'm using Firefox here, but it works in all the major browsers. I think maybe there's a problem with Safari, but you should be able to use a different browser just when you're developing your react code then.
[21:21 - 21:25] So let's go in and find our context. So that is wrapping this whole page.
[21:26 - 21:31] So let's see, choose type page. There we have the context.
[21:32 - 21:40] So if we look at the state inside of that, we can see that service type ID is one. If we change it to two, nothing happens.
[21:41 - 21:45] Oh, there we go. This doesn't update quite as fast.
[21:46 - 21:51] And let's try and see if we update the description. That updates as well.
[21:52 - 21:51] Nice.