Summary
Lazy Initialization Setup
The session began with implementing local storage to persist the todo app data across browser sessions.
window.localStorage.getItem() or localStorage.getItem()STORAGE_KEY constant was defined to maintain consistency across the codebaseState Integration
useStateSynchronization with useEffect
useEffect hook was added with todos as a dependency to sync state changes to local storageUsing a Third-Party Hook
useLocalStorage hook from the use-hooks libraryContext API Overview
Context provides a way to pass data through the component tree without prop drilling (passing props through multiple levels).
Implementation for Todo Actions
A custom context was created to manage event handlers (handleToggleComplete and handleDelete) that were being passed through multiple component levels.
Creating the Context Provider
TodoActionsContext was created using createContextTodoActionsProvider component was built to wrap around child componentsonToggleComplete and onDelete functions as props along with childrenuseTodoActions hook was created to simplify context consumptionRefactoring Components
TodoActionsProvider was wrapped around the TodoList component in the appTodoList since they're now available through contextTodoItem component uses the useTodoActions hook to access handlers directlyReducer Pattern Overview
The reducer pattern centralizes all state mutation logic in one place, making it easier to maintain and test.
Discriminated Union for Actions
A TypeScript discriminated union type was created to define all possible actions:
SET_TODOS - Sets the entire todos array (used for initial fetch)ADD_TODO - Adds a new todoTOGGLE_TODO - Toggles completion statusDELETE_TODO - Removes a todoEach action has a type property and a payload containing the necessary data.
Reducer Function Implementation
The todoReducer function contains a switch statement handling each action type.
Replacing useState with useReducer
useState to useReducer, which returns [todos, dispatch] instead of [todos, setTodos]dispatch with the appropriate action type and payloaduseMemo
Memoizes the result of expensive computations to avoid recalculating on every render.
React.memo
Prevents a component from re-rendering if its props haven't changed.
useCallback
Notes
Transcript
So, the proof of data again, in this user data, but We still want it there for like our first time loan to give our sample.
Okay, so in here in F, At the TSX, we're going to define what do we want Are storage to be stored on it. So in the browser, hopefully you're all familiar with this, but local storage, you can look at it in the application tab in Chrome. And there'll be a local storage. You can see that there's already some data in here. I'm going to delete this just so we don't have this data when we start working.
But just so you can see what the data looks like. It's always string data. The key I used here is to-dos. So that is, where is the data getting stored? And the value, like I said, is always straightened. So to access the JavaScript, you can say window. We don't need the window, but we can say window, local storage, And then you can use a bunch of different ways to do this. You can use dot notation and get back to string.
You can use... Array. Tight notation. And get it back, that's useful if that to do's is held in a variable. You can't use a variable in that location?
You can also use Get item.
And you can see that this is a string. And what does that string look like? It's a bunch of JSON data.
So what we're gonna do is we're going to take that array of to-dos and turn it into JSON data, which is just going to be a string, and then we can store that JSON data in here, and then when we read it out, we have to parse the JSON string, turn it into an array again, and then work from here. So that's how we're going to work. That's how you get through storing like, More complex JavaScript dataAnd just a simple string in local reception storage.
I'm using JSON and serializing.
That's kind of what we're doing. So over here To make this like... Somewhat more clear, I don't want to say more clear, easier, we're going to define a constant that's going to hold the string, that's going to hold our... R, R, key. So We'll do Storage key.
So Just to start, We're going to do-So right now what we had is states just getting initialized to this empty array. And that's fine. But what we're going to change this to is something we haven't seen yet, which is instead of putting a value In the useState default, we can actually put a function in there.
So we'll define a narrow function. So we can find an arrow function. Pause this for a minute. And then This is going to be called a lazy initiate. So whenever we give a function to the initial value of state, React's only gonna call this, like, on the very first time it renders, not every time it re-renders. So typically, like when this app component re-renders in React, Everything that We have at the top, it's called So, like, can we talk a little bit later about some of the other books?
We're going to talk about Use memo Call back. Those are ways to get around this thing of what should re-render, what should run again, all these type of things. But when you pass a function to it, That only gets called on the initial order, not on theNo, it'll still get cold. like a full refresh of the whole app, with a reload, but not when just React re-renders the components.
You can say stored. So we're going to use a ternary here. Do stored question mark and then if stored is true. Not null, not undefined. We are going to return JSON.parse. Store. If stored happens to be null or undefined, We'll return an empty array.
So basically, conditionally setting it to either what's in local storage or empty or empty.
What we're going to do is say Get what's in the storage key and if there is something in the storage key You don't have to default to loading because the data is immediately available.
So what we want loading to be is we want it to be trueIf There is nothing in local storage. and false if there is something that will disturb it. Because if there is a value in local storage, we don't want to render the loading at all.
This is going to return true if there is something in the local So we'll just add an exclamation point in front of it to invert it. And now we're basically setting this to the opposite of if there is something in the whole story.
If Our state is empty. So right now We have the fetch just any time it initializes. The initial render of the component, it's going to run this and it's going to set the data in the to-dos. So we don't really want that.
We only want that to happen if there's nothing in the to-dos storage key. Now that means I want I want this to be, there can be an empty array in the storage key, which means the user's deleted everything and they They don't want to see this activated. Now that means I want I want this to be, there can be an empty array in the storage key, which means the user's deleted everything and they They don't want to see this activated. Really we only want to load the sample data if there's no array
And this is going to show that we can actually call useEffect in our components as many times as we want with different combinations of dependencies. So After that use of fact, We're gonna call useEffect again. And what we're going to do here is we're going to run that effect. And what is our dependency array going to be? Well, our dependency array is our to-dos. Our to-dos is our list that's in state.
So basically, we're saying any time that to-dos list changes in state for any reason, Run this effect. So now inside that effect, we're going to just do something pretty simple. local storage dot set item Storage key And then we're going to say JSON. Good. Stringify. All right, two dudes. So basically, any time our to-do state changes, So again, that could be our app, adding a to-do, deleting a to-do, whatever.
We're still interacting with state. All right. So The next part here I wanted to show is-Remember, we did this with creating our own lazy initialization for all of this. There isHey. library out there that It has a whole bunch of additional react hooks you can use. They happen to have a use, a local storage hook that they built. So now I'm going to take and We're going to refactor this again. to use that book.
Because when we see this, this is just all it was, was a custom hook that was built that we could use that's doing basically what we just did. So let's go look at the library for a second. So there's a use hooks library. There is another one out there that you You may find if you search, it's like use hooks dash TS. This one has all the types of bindings and this is the original one. So this is a collectionOf course, I do all kinds of different Thanks.
So we're going to look at it just using the use local storage one just to see how that comes along. So This use local storage hook, we're going to install it. And then... Then We bring it in just like A typical cookbook. And it gives us our, just like our state value and our way to set the state value. And then internally, this kind of just does all the stuff that we'll be deciding. So let's start by installing the dependency.
We're gonna update our Our state function, so this here, I'm going to just comment, because I want to leave it in there for us to have. And what we're going to turn this to look likeIs it a say cons? This actually will be the same part here. To do is instead of to do. And then... Use Local storage. So that should have imported that for us. It had to be imported. If for some reason the way your editor is set up it's not importing for you, you just don't have to import.
So our initial value is going to be an empty array. Now, Up here, we did our own thing with a lazy instantiation. But this use local storage does that. inside the hook. So we don't have to worry about it. We can just say yes. When you set up, If you don't have any data there yet, put this up to your right. All right, so that Refractors that over. And then... The next thing we're going to be able to get rid ofIs...
You could always do a search or something like that. But let's talk for a minute about context. So Context is a way to pass Um... Information around your app? without necessarily passing it from parent to child all the way down the chain. So in our example, we saw that we did do a little bit of what we call prop drilling. And that's where we say, oh, at the app level, we have this to-dos object. The to-dos object gets passed to the to-do list object.
The to-do list object passes it to the to-dos object. And you're passing something down throughout the whole hierarchy. You still generally want to do that for most data. Bud. There are some things that Benefit from not having to deal with all that property. So this context API was put together for that. There's other ways to deal with it. There's a Other state management libraries, there's Redux, There's other libraries you can add in to add ways to store data in state and pass it to different components and all that.
So being able to store something globally and then just access it from everywhere, It's really helpful. Like I said, we're going to use it. Our little simple app just has, doesn't have a whole lot ofDone. instances where We could really implement it, but our event listeners are a good one that we can. So let's go ahead and... So just some more, when do we use it? So theme modes, user authentication state.
Is the user authenticated or not? Language preferences. Any application-wide settings can be found. Data that needs to be accessed by multiple components. Anytime you want to try to avoid this properly, You might be good. now Hello? Suggest that every time you have a situation where you're like, oh, I've got to pass this data object down three levels of components, let me throw it into a context. Judiciously, if you're passing something down through component chain, but it's only that one chain.
Hello. And then you can use this reducer pattern that we're going to look at later in combination with context. Hello. But we're gonna use it separate. So our two demos are gonna be like, Here's contracts, here's reviews. These context values do trigger re-renders, so any component that It says it wants to use the context. It's going to re-render every time the context changes. So again, that's a situation like if every component is using it for a theme setting, and that theme setting changes, every component is going to trigger a re-render.
Okay, so I'll make a copy of this little storage one. Okay, so again we have a starting point of where we left off just a few minutes ago. All right, so We already kind of talked through what we're trying to Solve there, it's basically these guys handle add to do handle complete handle delete And the fact that we pass them to the to-do list, And then the to-do list. Passes on to the new item. and then to do I am actually using them.
We're just going to implement the context to basically eliminate the middleman and just make them a provider on the app. that has to do actions with our poem. And... That provider is just going to make them available to any components that need them. Our to-do item can then become a User of the context. in order to directly get those functions back. Let's start by going back to app. We're going to create a context file.
You could do this with like, Without making their own component everything for it, but it's a little bit more organized. We actually create component for what we're going to call a context. So in our components folder, I'm actually going to move the component to that. Equal to our components folder, I'm going to make a context file. Go forward. Inside the context folder, we'll make a to-do. So inside here we're going to make aOur content.
So we're going to need to to import a few things,Three, Create context from react and go see if we use Context. And then we are going to need an interface. To do actions. We're gonna kind of call this We're going to make this interface handle all of our actions in our attributes. And then... We need to have In our to-do actions, so if we come back here again. We have the ScalableAd and the toggle of the delete.
The wheat. Remember, we're making an interface, so we're just telling it This is what I, what, data is going to be in this document. So now we have this. Interface created. Let's create the Excellent. Hold it. Action. To do. And The Tudu provider He's going to take our props in. It's going to have A couple special things here. Well, not a couple special things. So the object that we pass to the provider is, right, is that, well, the object we pass to this controller is prompt.
But for this provider we have to do that because we're putting something around like this component is going to be around other components. So there's going to be children components. So we need to get that passed into us. That always just does come in as one of the props under this children. So, all Americans get that. And then we're going to say on. Chicago. So, the cargo complete. And... On wheat, okay so that's, What the object's going to basically be, or the props are going to be-I shouldn't say the object.
It's what the props are. because we're destructuring. So we're getting the children from the component, and then we're going to have an untoggle complete attribute. And then we need to type that. So what is that going to be? Well, it's going to be to-do actions. All right, that's this interface, the new actions. But... This children has to be something too. So with the children, we're going to use Peace.
And this to do context, we'll do create context. And this create context is going to either have to-do actions in it or It's explicit. And then we're going to set it to null to start. No values. And so remember, we're creating it. When we create the context, it's going to use this default value if it doesn't find the provider. When we use the context, we're going to end up using it. Okay, so now down here in this, Provider here.
And then... We're basically creating a hook here. And this is going to return to do actions. Okay, so we have this. We have We have a function which we're building, a hook. Basically, that's going toIt's going to return the to-do actions for us by looking in context. And because we're putting this all in the same file, I don't have to export this file as a context. I imported another file because I can just reference it here.
And about here, we need to surround our So When I did this demo, I was focused on his to-do list. So I put the provider at this level. We're gonna just like say continue this so I keep everything in sync. If we wanted to include this add to do, We would just have to move the provider up a little higher, maybe around this div or something, or even this div, and just have it be a higher load parent.
So that's our component. And then we're going to want this to beThe end of that. So it's still bad because we didn't give it the props that we need to give it. So we need to give it its props. On.
So now what we've just done is we've surrounded this whole thing. So any components that are children,Which are these. have access to this provider. Now we can take These two off. We have to fix this in a second. Take those off because we don't need to pass those functions anymore. And now down here in To do list, We need to update this because we don't have These props couldn't get any more, so we would get rid of these.
But we still need access to those items. So inside this new item component, We will now use the hook we built. So, it's a card. Toggle. Complete and on delete. Set to... Use To do actions. And that's the two new actions that we brought in. So this is also doing this in a way to kind of show like this is what basically like how a trunk is built, right? It's just another file that does something that providesSo now we have our Are there two items that use to do actions?
It's going to give us the state. Roger. Okay. And then it's also going to give us this dispatch function. It's a little different because the dispatch function does different things depending on Which action you submit. Um. So Again, just a comparison between useState and useReducer. useState, very simple state values. It can have a full object in it, we saw with an array and all that, but it's pretty simple.
It's just the values in state or it's not. With reducer, we can actually add additional values. around what does that mean, how does it handle it? All that good stuff. Uh... It's also good for when you want to have multiple state values all being like change at the same time, because you can combine a bunch of things, whereas new state is independent for each value. I put predictable state transitions.
Just a little bit, since it's all centrally located, I think that's the big idea. Here is when you create a reducer, you want to put all your logic to transform that state in one place. spread out in different functions throughout your application. It's harder to implement, but it's a little bit easier to test. You stayed in much simpler thanCircle function. So-I put these as best practices. These two, some come back to it.
Keep producers pure. That just means pure function. This means anytime you give it any parameter, it's always going to do the same thing. It doesn't have other side effects that it does something else. So you want to always keep them pure so that it's... Repeat it for me. Same data, the same action, it's going to do the same thing. We want to use a discriminated union to keep our actions very consistent.
So we want to define our actions. Okay, so up here We do it above the storage keys high. We're going to create a new type. And We're going to create a type called to-do action. So don't get this confused with the to-do action from the context demo. It's a different thing. This is a different demo. It doesn't have that. This is what's called a discriminated union in TypeScript. So we have a union type.
of These objects And they... All have this tight Well, they all have the same properties. But this type property is set to a set of strings. So It's... These unique strings that are in type, that is, making this discriminating union. So basically, type action It can be an object. But it has to have a type of set to do, add to do, title to do, or delete to do. It's kind of like doing the same thing with the You can't think of the right term right now, but when you set up a literal union of strings, Get a tight.
You're basically saying that type only can be those strings. It's the same thing, but it's more broad objects. So we have this unit Because we're going to handle all these different things. Okay. Set to dos is going to be our use effect. Add to do, toggle to do, and delete to do basically are going to replaceOur functions.
So we're creating A reducer function, first parameter is our state. The second parameter is our actions. Uh... Again, that's the defined API of a reducer function. So our state is still going to be what it was. It's an array of to-dos, and our actions is one of our to-do actions. And our actions basically say it's an object that has a type and a payload. What is the payload? The payload is the data.
Oh, like an ad. We're adding the payload as a string because it's the to-do title. Delete and toggle, the payload is the number or the ID of the to-do. So it's like, what are we passing as data? These are directly-corresponding to what these functions are getting past. Right now. Okay. Um... So we have that reducer function. I already had all the logic in here, again just to kind of speed up time. But basically we have a switch statement that's looking at the actions type.
And if it's a set to do's, it's gonna do something. If it's an add to do, it's gonna do something. Toggle, it's gonna do something. Delete, it's gonna do something. So, set to do's. All it's doing is it's returning The payload of the action. Because that's the array of to-dos. We're going to use that in the fetch to set state. Think of this as like Whatever these functions return, Is the value of state now?
after the action's happened. So add to do, basically it's the sameWhat we're ready to do is that It's basically this exact same logic. It's written a little bit Shorter. Because I did this whole return as a one big combined thing instead of creating a new to-do and then doing this, but basically instead of calling set to do, we're just returning the entire state. Same thing with toggle, same thing with delete.
Again, the logic, take a look at this logic, but it's basically exactly the same as what's in these functions. So we have our to-do reducer function. So when I say a pure function, you give it the same state, you give it the same action, it's going to do the exact same thing. This list of an array and say to delete this number, we're always going to get the same results.
The current state and the action is what it takes in and it always returns the new state. So whatever the current state is before the reducer action happens, the function returns what is now the new version of state.
No, we do handle it before. So default, I forgot that. So this would be like if you somehow sent in an action that doesn't match one of these cases, like if you were writing the ad and you screwed something up, it basically just returns the state on change. Again, you look at this logic of how this is all built, I'm just using some Spread parameters. A little bit shorter syntax, but the syntax that was in the functions would work also.
Okay, so now we want to replace The use of useState with use reducer. So this use state here that we had before, we don't want that anymore. And what we're going to do instead is we're still going to have We're still going to have a to-do's. It's still going to be broken out like this, but instead of set to-do's, we're going to get something called dispatch. And then we're going to say, okay, we want to use So we're going to say, OK, we want to use reducerWhat did we do, sir?
So inside that function, We're going to put our logicWe're going to put this same logic that was inside of our initialization function.
So all that's the So we basically just replaced the useState call very similar to how we had it. Um... The three arguments again are the reducer function, the initial state, and then our lazy initializer. How does it initialize our state the first time that it loads? It always returns to us the state object and this dispatch function, which is very similar to our state object and our center function.
Um... The lazy initializer works the same way. It bounced the first time every two local storage, so that now works the same. This initial state, you may ask, well, why are we passing that if we're passing an initialization function? It reacted more so that Initial state. parameter if you give it a lazy initialization function. It's just... We needed to be there in order to have something in thisSecond parameter and If we don't pass an array, then the type system will start complaining because it's like, oh, you're saying it's not an array, but you say over here it is an array.
So all these spots here where we call set to dos, We don't want to do that. So we're going to change these to like for this one, handle add to do. All of the logic that we had in there can go away is that all that logic that moved into the Uh, Moved into the reducer, like I said it's up here, this logic's here. And all we have to do is we have to say dispatch, which is the dispatch function from the reducer here.
And we're going to pass to it. what it wants. It wants an object, tight And our payload It's expecting a string. Which is our title. That's all our handleAdToDo function needs to do now. It just needs to say, "Reducer, I want you to call your addToDo logic and here's the data. You handle all the logic." Handle complete. We can Remove that set to do's line. Just add a dispatch. Sorry.
Is the title to do? And the payload is going to be And then for handle delete, it's actually going to be almost identical to this one, so let's just copy that.
You see here, instead of that, So now hopefully you see that like, Now the logic doesn't exist, the actual state mutation logic doesn't exist in these functions. It exists all in this one place instead of three different places. So we need to look at the logic and solve it in its mind. We do still have one more set to do up here in this fetch. So we do want to fix that. Remember that all that's doing is Taking the data from Fetch and setting it into the state.
So it's just all about centralizing the logic that you take state. So you don't have to set state calls all over the place. So with that, That should... That should be working still. And just to... Make sure we see that it is definitely running the new version of the app. We started. And we'll come back over here. And... Mark complete. And the reducer is just taking Since the reducer is still just giving us our state in the same name to-dos, because we named it to-dos, the same use effects still works to synchronize the state the same way we did.
There's the use memo, I'll come back to this slide in a second. There's this react memo, which is named very similarly, but it's used differently. And then there's this use callback hook. All three of these hooks are all, or a couple of the middle ones are all, it's really technically part of the reacting. These are all ways to optimize performance. Memo, I think, is an easy one to kind of start with so we're gonna start there It's used for memorizing a value of a heavy computation.
So what you can do is you can surround the function inside this useMemo. and give it an array of dependencies that it actually cares about, And then only if one of those dependencies change will it rerun this function. Otherwise it will cache the output of the function. And then this memorized value, which is a function, useMemo gives you a new version of the function. You can put this anywhere in your component.
Your component re-renders. It calls this again, but it doesn't really call the real function. It grabs the cache value instead. If either of these dependencies change, then the next time it gets called, it will actually call the real function and re-evaluate. So it's used to cash Functions that may be computationallyExpensive. So like in this one, I'm just trying to see, this filtered products, Basically, it's only filtering the list of products if the list of products or the text you want to filter it by changes.
You use these when you're gonna Calculation expensive operations or you want to specifically prevent something from causing an unnecessary render. It's not something you would use to avoid simple calculations like adding something together, that's trivial. So just let it happen. Don't go through this extra effort. And you want to just be careful about If the computation is quicker than all the extra Effort and overhead of setting all the memorization up.
I think I have a slide to show memo and callback here in just a second. But remember, it prevents a component from re-rendering if props haven't changed. If a functional component receives the same props on every render, You might think, well, this is never gonna re-record, but In some cases, when a parent component that uses it updates, it sometimes triggers the children to re-render anyway. So you can kind of tell a component, "Hey, I know this is never going to change.
So memo looks something like this. You're making this React memo. component, so this is just a functional component called child. And then when you use child in here,Oh. Child won't re-render. We're in.
Okay, I think The documentation is pretty clear through your cell, but what I'm going to do is I will, after we have thisIs that over here, Bill? I'll create a separate little 10 minute video. Tomorrow or something? that just walks through launching it to herself And I'll upload that to Canvas so you have a walkthrough of taking it from the repo that we end with to launching it, because that's the other component ofOh.
There's a lab that I want you to do. And then next week if there's questions, we can talk about it. and look at it and change and read it for you. I'll make this up to you. If you don't have a Vercel account, just go sign up for one. There's a hobby tier. That has plenty of Like free usage of we'll be able to use.
By the weekend. And... I bet it won't be due by next week or anything like that. So don't worry about that. Use these examples to kind of start looking at it. I'll post up a video showing this specific app getting deployed to Vercel. Okay, so I'm gonna take it right from this point that we see it here, deploy it to Vercel, I'll go through all the steps of that, it'll be on the 10 minute video. I'll post that in Canvas and post an announcement on that, and comment tomorrow.
I'm trying to make it tomorrow night. Um. So that's the other component you'll need, but you'll be able to work around this. You'll have it all at the same time, so it won't matter. But that's going to be the general idea. You can do it however you see fit for the editing. When you read the doc, Bring questions next week and I'll answer them. But basically it's like however you want to handle letting the user be able to select things and edit them from a UI perspective you can.
예