- So to get started with this project,
the very first thing we need to do
is just create a Vite application.
We want to use TypeScript instead of JavaScript.
So we can say "$ npm create vite@latest".
We wanna do it in the current folder.
And what I wanna do is I wanna go to React,
I'm gonna use TypeScript with SWC
'cause it's a little bit faster.
Let's install these dependencies.
And we're going to run the development server for this.
And then the next step after that,
if we look at our README here,
is just to kind of get the basic calendar done.
As you can see here,
we wanna create the calendar component
that has those buttons to jump backward and forward.
So really the first thing I wanna do
is just get a basic calendar rendered out onto my screen,
and we're gonna be using date-fns
for a lot of the libraries for this.
Again, similar to some of the projects
we've done in the past on this course.
Just to make it a little bit easier,
some of the stuff that we're gonna be talking about
should be rather familiar
because the date picker uses a lot of the same logic.
So let's go into the after folder here,
you can see we have everything we need.
And I wanna get rid of a lot of this basic boilerplate code.
We don't need pretty much any of this.
There we go.
Inside this app here.
Export default function app.
And we're just gonna return h1 that says "Hi."
So now if we actually go to that page,
you can see we now have our h1 with "Hi"
being rendered out.
So now let's go ahead
and actually create a calendar component.
I'm gonna do that inside of a components folder.
I just like to break this out into a different structure
because we have a little bit of more of a complex project,
so it makes sense to break things out
into different folders.
So we're gonna create a calendar.tsx,
'cause this is a TypeScript project.
We're gonna have that component.
It'll say export function calendar, just like that.
And inside here,
I essentially wanna return all the HTML from here.
So I can just take all the code for my calendar.
I'm just gonna copy this entire thing over.
For now, this isn't going to be perfect
'cause it's gonna have a lot of boilerplate information,
but if I give this a quick save
and we look over at our code here,
and I make sure I actually render out
that calendar component,
we should at least see our component
showing up on the screen.
You can see we don't have any of our styles
being rendered out yet.
So let me just make sure I import a style sheet.
We're gonna say "./styles.css".
I'm just gonna copy that file over into our source here.
So now if I give this a refresh,
you can see that this and this,
they both look pretty much identical.
We have a few different changes we need to make,
but overall it looks pretty much exactly the same
between the two of them.
And I'll actually close out of that version,
so we just have the actual running version
working on our screen.
The first thing is all of this code that says "class"
should be replaced with "classNames".
So I'm just gonna really quickly select all of these.
It's gonna take a little bit of a second
to actually select all of them,
probably should have done a find and replace.
There we go.
"ClassName".
There we go.
And that should get rid of all the errors
that we have on this page.
And it looks like it has.
And again, our code over here looks great,
which is exactly what we want.
Now, the next step
is going to be breaking this down into different sections.
So you can see here we have this calendar section
which has a header.
Okay, pretty straightforward.
Then we have our day section.
And this day section really makes sense
to render out into its own component
because each one of these is a day.
As you can see, we have a day inside each one of these.
And all of these days
essentially share the exact same logic.
So these days are kind of like their own little component.
So we can create a brand new component for a calendar day.
And this one, I don't need need to export,
I'm just gonna call a function CalendarDay.
Just like that.
And we can actually use that CalendarDay
inside of our calendar.
And I don't need to create another file for this
or anything.
It doesn't make sense to
because obviously this component
is only ever used in our calendar.
So it just makes sense to put them in the exact same file.
So now every time I have one of these different days
I can replace it with that CalendarDay component
right here,
which is really great.
That already cleans up our code a lot.
So let's actually go ahead
and try to get the days for the current month.
So we can come up here,
we're gonna need a state variable
'cause we're gonna need to be able to modify
what the current month we're rendering is.
So I'm just gonna have a variable
called selectedMonth setSelectedMonth.
We're gonna set that equal to useState.
And by default,
I want that to be whatever the current date is.
So we'll just set that to the current date.
Just like that.
Now we have our selected month.
Now we just need to get the days within that month.
So we can say that our calendar days
are going to be equal to...
And of course, we're gonna be using date-fns for this.
So I'm gonna come in here
and I'm actually gonna install that date-fns library.
So first of all I want a CD into the after folder
and then I can npm i date-fns
to get that date-fns library installed.
Once that finishes,
I'll close out of this terminal
since we no longer will need it.
There we go.
So now I can get everything essentially that I need
from that date-fns library.
For now,
the main things that I'm going to need
are the start of week
and the start of month.
And that'll get me started
with the beginning of my interval.
For example, I want to get the start of the week
and from there I wanna get the start of the current month
and I wanna get that from the selected month.
So what this is going to do
is just give me the start of my current month,
which in my case is June 1st.
And the start of the week
is going to give me the Sunday
at the beginning of that week.
So we go from this Thursday, the first, to May the 28th.
Just like that.
And that, I actually want to set
to a slightly separate variable.
We'll just say const here
is going to be equal to firstWeekStart.
We'll set it equal to that variable.
But now, I'm just gonna set this to null here.
The next thing we wanna do is get the end of the last week.
So we could say lastWeekEnd, just like that.
And we're going to get the end of the week,
so let me make sure I import that.
We're gonna get the end of the month,
so lemme make sure I import that as well.
And we're getting that again from our selected month.
Then what I want to do
is I wanna get each day of the interval
and that's going to be starting at our start date.
So that's our firstWeekStart,
and it's ending at our lastWeekEnd.
And this, we can set to our calendarDays.
So I can just come down here,
place this right there.
And there we go.
We have our calendarDays,
which is just an array of all the days
between our start date and our end date.
Again, this should look very familiar
from the date picker project,
'cause essentially it's the exact same code.
Now, one important thing to note about this though
is that this code is going to run on every single render.
Even if nothing about our calendar changes,
this code is going to run every single time.
It's not a huge performance hit,
but obviously we don't wanna run this code
every single time our component changes.
Instead we only wanna run this
every single time this selected month changes.
So what we can do is we can actually wrap this
inside of a useMemo.
So I'm gonna take this calendarDays,
bring it back up here,
I'm gonna set it equal to the result of running useMemo.
And inside this useMemo,
I just wanna wrap this entire set of code right here
to run it as a function.
And my dependencies for this
is going to be my selectedMonth.
Every time that changes, I want to rerun this useMemo code.
So now it's import useMemo at the top.
And what we can do here
is we can just return this eachDayOfInterval.
If I expand my screen a little bit,
it'll be a little easier to see what's going on.
As you can see, we still have our calendarDays.
We're wrapping all the code for it
and we're making sure we're only doing this
every time our selectedMonth changes
to make it a little bit more performant.
Now, we can actually take this,
and inside of "days" here,
we can actually map through this.
So we can say we wanna map through each day.
And then inside of here,
we essentially want to render out these day components.
So I'm just gonna copy this code,
paste it into here
and we can just make sure we set a key on this,
which is going to be our day.
And since the key can't actually be a date,
we can just call the getTime function
and that'll give us a unique actual indicator
for the specific day.
So now with that done,
we can actually take all of these other days
and we can just remove them from our code.
So I'm just gonna go all the way down
to the bottom of the page
and remove all of these extra days that we don't need.
There we go.
So now we just have this one day
being rendered essentially in a loop.
And if I save,
we should just have the same day
being rendered over and over again,
which is perfectly fine
for the case that we're actually using here.
Obviously, we're gonna fix this and make it render properly,
but for now this is good enough.
Also, I should probably take all of this code
that's rendering out our day
and we should move that into our calendarDay component.
So here I'm just gonna make it return all this information.
My key, I'm obviously going to remove
'cause I need to move that up here.
And we can render out that calendarDay
with a key that is set to our current day.
There we go.
This should say calendarDay,
make sure I spell that properly.
There we go.
Now again,
it should render out just exactly the same day
over and over again,
which is fine for now.
Now, to figure out what information
we actually want to render out for our day,
let's go down to this component
and figure out where all of the unique information
is coming from.
For example, here we need the week-name,
we need the day-number,
and we also need to render out here
any different events that are inside of it.
For now, I'm just gonna kind of ignore this event section
because we're not at that part of the actual to-do list yet.
So I'm just gonna comment that out.
So right now it's just rendering the day,
and we need to parse in all the information we need.
For example, we need to know what the actual day is,
so we're obviously gonna need to parse that in.
And then also if we look at our index.html here,
one thing you'll notice is the actual weekday,
that Friday, Saturday, Sunday and so on,
that is only showing for the first seven days.
After that, you'll notice
this is not being shown anymore at all
because we only wanna show it at the top of our calendar
instead of on every single day.
So again, we need to parse in
if we're going to be showing that weekday or not.
So we're just gonna have here, showWeekName,
that's gonna be parsed in as a Boolean
to determine if this section even renders at all.
Really, besides that,
the only other thing we need to know
is if this is going to be in the current month or not.
So the way we can do that
is by parsing in what the actual selected month is.
So we can say that our selectedMonth
is going to be parsed in
so we can determine if this current day
is within the selectedMonth or not.
So now let's actually type these props
'cause right now they're not typed.
So we'll create a type called calendarDayProps.
Generally, when I create a type for a component,
I just give it the same name as the component
and just add "Props" to the end of it.
I know that this is going to have a day, which is a date.
It's gonna have a showWeekName, which is a Boolean,
'cause that's going to be true or false.
And then we're gonna have a selectedMonth,
which is also just going to be a date as well.
So now I can take these props,
add them right here,
and that's giving me type safety for this.
You'll notice up here I'm immediately getting an error
'cause I'm not parsing any of that information in.
So let's make sure we parse that in.
We'll come in here,
we'll say that our day is equal to our day.
Our showWeekName here,
this is going to be only for the first seven days.
So here I can just get to the index
and if the index is less than seven,
it's going to be one of the first seven days.
So I'll say index less than seven,
that means we show the week name.
And then finally,
the selectedMonth
is just equal to our selectedMonth variable.
Now, if I get that as save,
that got rid of all the errors up there,
and now we just have errors here
because essentially we're not actually using
these different properties
so we need to make sure we use them as well.
So the easiest thing to do is this week-name section.
So I can do here,
is if showWeekName is true,
then we render out the div that has the actual week name.
And we just need to render the week name right here.
There's a lot of different ways we can do this,
but it's actually rather easy to do
with the internationalization helpers built into JavaScript.
And I'm actually going to create a helper function
for us to do that.
I'm gonna create a brand new folder called Utils
and inside of here
is where we're gonna put that helper function
and we'll just call this format date.ts.
Now, inside of this function
we're gonna export the formatDate,
just like that.
And this is going to take in a date obviously,
'cause we want to format the date.
And it's also going to take in some options,
but we're gonna get to that in just a little bit.
Here, what I wanna do is I wanna create a new
internationalization DateTime formatter.
And here, if you parse in undefined as the first property,
it'll default to the user's current locale.
That means if my website is displaying in English
'cause I live in the United States,
it'll render my date in the English format.
While if you live in Spain, maybe,
it'll render out the date in the Spanish format.
So depending on where you live,
it'll use your current locale to render out the date,
which is great
because dates are displayed differently across the world
and this will take care of all of that for you.
It'll also translate.
For example, the actual things like Sunday
will be translated into Spanish
instead of being displayed in English
if your locale is actually Spanish.
For example, if I change the locale manually,
everything will be in Spanish,
but we're just gonna use undefined here
so it'll actually use the default locale.
Then I need to parse in some options.
And these options I'm actually going to take in
as part of this formatDate function.
Finally, I want to call the format function
and parse in the date.
So what this allows us to do
is to create a brand new formatter
that is going to use the current locale
and whatever options we parse in.
In this options property,
we can actually get from the date time formatter.
So if we just really quickly hover over this,
you'll notice it'll show exactly what I'm talking about.
As you can see here,
the first thing it takes in is the locale.
And the second thing it takes in is these options.
And these options have the type
of Intl.DateTimeFormatOptions.
We can just copy that exact type
and we can say we wanna parse in our own options
that are going to take in that exact same type.
So now as you can see that these types match up
so it's going to work just fine.
The only other thing I wanna say
is I want this to be optional
because I don't wanna have to parse in an option
if I don't need to
because sometimes just the default formatter
is going to be enough for our use case.
Now I can actually use this function to format my date,
to gimme the weekday, to gimme the date number and so on.
So here where I have that Sunday being rendered,
I wanna call that formatDate function that we have.
I'm gonna parse in my date, which is just called day.
And then we can pass in any options.
And the nice thing is if I hit Control, Space,
you'll notice I get my actual auto complete right here,
which is exactly what I want.
In our case, I wanna format the actual week.
So I can say I wanna format the weekday
and in our case I wanna use a short weekday.
So now if I just give that a quick save,
you notice at the top of my page
it says Sunday, Monday, Tuesday, Wednesday,
Thursday, Friday, Saturday,
which is exactly what I want.
If I were to do like long instead of short,
you can now see it says the actual full day.
And the really nice thing is if I was in Spain for example,
so this was a Spanish locale,
you can come in here,
you can now see that it's rendering out
all of these different dates in Spanish
as opposed to English,
which is really, really cool.
By default though, we're just gonna leave this undefined.
Now the next thing we can work on is this day-number here.
We can use that exact same format,
the date function that we just created,
parse in our day.
And here I wanna actually get the day
so we can say day here.
And for the day, I want to get it in a numeric form.
I don't want the two digit form,
I just want the one digit form
if it's going to be a short date.
So now if I save,
you can see we get all of our days
showing up exactly as we want.
And again, if I were to do the two digit form
of that instead like this,
you can see it now just puts a zero in front of all the days
that have a one digit form.
But in our case, numeric is going to work just fine.
Now the next thing we need to do
is deal with all of our class names,
'cause right now
we have the actual content rendering out fine,
but it's not rendering out the correct classes
'cause some of these are non-month-days
some of them are old days, some of them aren't,
some of them are today, some of them aren't,
so we need to take care of all of that.
And you've noticed from previous videos in this course
that it's a little bit difficult to do a lot of conditionals
inside of a class string.
So instead, a lot of times you'll either download a library
or create your own helper function
for handling that for you.
I'm actually going to create our own helper function,
I'm just gonna call it CC,
which stands for concatenate classes.
And I like having a short name for this
because you're gonna be using it all over the place
so it doesn't make sense to have a super long name.
This is again going to be a TypeScript function
and let's create that function right now, CC,
and let's make sure that we export this function.
Now, essentially the way that I want this function to work
is I wanted to take in
essentially a bunch of different classes.
So an infinite number of parameters that I can take in.
So we can use the rest parameter here
and say that these are gonna be all of our classes.
I want them to be in essentially an unknown array
'cause I don't know what type any of these different classes
are going to be.
So when I call this function,
I'll call CC,
and I can parse it a function like okay, day.
So I wanna add the class day,
but then I wanna have some conditionals as well.
So for example, if it's the first day
I may want to add a different class.
So I can write some code like this
that's just like "is-first-day",
just like that.
Actually, it'd probably be like this.
There we go.
So I have a class day
that's going to be applied automatically,
and then I have a bunch of conditionals
that are either going to return to me false
if this is not the first day
or it's going to return to me a string,
which is the actual class I want to render out.
So really all I wanna do
is I want to take this array
and I'm gonna remove everything that's not a string,
and that will remove anything
that returns for example, false or undefined
or anything like that.
So I can parse all these different conditionals in there
and if they actually evaluate out to a string,
it'll render that string as a class,
otherwise it'll just completely remove it from the array,
which is exactly what I want.
So here I can just return.
Oops!
Return.
I can take my class's array
and what I wanna do is I wanna filter.
And essentially I wanna take each one of the values
inside the array
and I wanna just check to see
if the type of that variable is equal to a string.
If so, I wanna keep it,
otherwise I want to remove it.
Then finally, I want to join this on an actual space.
So it's taking this array of values
that are some there conditional,
some are actually applied, some aren't applied.
It's removing all the ones that don't make sense
and then it's concatenating these all into a single string.
So in our case,
if we had our code like this and it was the first day,
our return value would just be day and first-day,
just like that.
But if first day was false,
our return value would just look like this,
where it's just the string day.
That's exactly what this function does.
And it's really nice
'cause it makes it a lot easier
to apply different conditional classes,
'cause otherwise to write this
we would have to do something that looks like this.
We would have day
and then we would need to come in here with isFirstDay.
Then we would put in here that we would return first-day,
otherwise we would return out undefined
just to make sure we don't have like null being showing up
or false inside of it.
And we would have to do this all over the place.
It's much longer, much more convoluted,
which is why I like doing this
with this helper function instead.
This is entirely optional
but as you get more complicated code,
it makes sense to create something like this
or use a library for it.
So let's see what this looks like in action.
In this case, we obviously need a custom class name.
It's gonna come from that CC function.
By default, we know that day is always going to be applied
to every single one.
And then we have some conditional classes
we want to apply as well.
So I'm just gonna remove all this here,
get this fixed up.
There we go.
And then what we want to do
is we want to apply those different conditional classes.
Make sure I close off my div as well.
And we also want to import that CC function.
So by default, this is working
in just applying the class data, everything.
But now, what if we want to apply conditionally
that non-month-day class?
So if we're in a month that is not the current month,
I want to apply a specific class.
So what I can do here
is I can use the function isSameMonth.
There we go.
And I can actually check if my day is the same month
as my selected month.
If that is not true,
oh, then I want to apply a specific style.
In our case, it's non-month-day.
Give that a quick save,
and now you can see all these months at the top
and this day at the very bottom,
these are actually given a different style
'cause they're not in the current month.
I can do the exact same thing
by checking to see if it's before the current day.
So I can do that by just coming here,
placing a comma at the end of this.
And I wanna check the isBefore function from date-fns.
I wanna check before the end of day
and I wanna check if the end of today
is before the current day by saying new date,
that's just saying it's a day in the past,
and I wanna render out that old month day.
So I can just say old-month-day.
There we go.
And now you can see all these days
that are before the current day,
which today is the 23rd of June in my case,
are given this great partially opaque color
and all these other days have that fully opaque,
which is exactly what we want.
And the reason I had to get the end of the current day
is because I wanted to make sure
that the current day is always displayed as the darker color
because it's always the current day.
I don't want the current day to ever appear in the past.
And if we didn't do the end of day here,
there is scenarios
where the current day would show up grayed out.
As you can see the 23rd is now grayed out,
while before it had that darker color just like we want.
So just by using this helper function,
you can see that this has cleaned up our code
for our actual string for our classes just a little bit.
It's not like massively cleaner,
but it is definitely cleaner than it was before.
Now, the next thing we need to do
is move down here to the day-number,
to check if it is the current day as well.
I'm gonna do the exact same thing with that CC function.
By default,
we know that we're going to parse in the day-number here.
And then if the current day isToday,
so if our day isToday,
then I wanna render out the today class as well.
So we'll say today,
I just wanna close that off.
Now, if I get that to save,
you can see the 23rd has a blue circle around it
'cause that is the current day,
this evaluates to true and it's applying the today class.
So now if we really quickly look at the README,
we've taken care of most of this top section.
We've created a calendar component
that renderers the current month by default.
That's exactly what we have right here.
Now we need to make it
so that the buttons to move backward and forward
as well as go to the current day actually work,
'cause right now as you can see,
they do nothing at all.
So let's go ahead and do that now.
What we can do is we can just minimize this calendarDay
'cause we don't really need to work on this right now,
and we can move up here to the actual calendar itself
inside of the header.
So let's minimize that and open up the header section.
So as you can see,
we have three different buttons
that we need to be able to make sure
that we add on a click event listeners
to set the selected month.
So we click the today button.
So let's add up an onClick on the side to that.
What we wanna do is we wanna set the selected month.
There we go.
And we wanna set it just to the current date,
so we'll just say new Date.
There we go.
So now if I click today,
it'll bring me back to the current day,
but we can't really test that
until we get some of these other buttons working.
So for the backwards button,
we wanna again set up an onClick event listener for this
and I want to call that setSelectedMonth function.
And here I wanna be able to take in
whatever the current month is
and I wanna subtract one from it.
So I have my currently selected month,
we'll just call it "m"
and i wanna subtract a month from it
so we'll call this subMonths function,
and I wanna subtract one month from this function.
So now if I click save and click this back button,
you'll notice it's moving me backwards in the months.
My day here is not actually changing us,
so we need to make sure we change it.
If I click today,
you'll also see it brings me to the current month.
So here we're rendering out June 23rd,
I need to change this.
So we need to say that we wanna format this date.
And to format this date,
I want to take in my selectedMonth.
And here I wanna render out
the month name as well as the year.
So we can say we wanna render out the month,
and in our case I want the long version of the month.
I want the full word to show up.
And for the year, we can come in here
and I want the numeric version
'cause I want all four numbers inside the month.
So now, if I give that a save,
if I move backwards,
you'll notice my month is actually updating properly
as well as my year.
And today is bringing me back to the current day.
Now, let's just copy this on click function
'cause it's gonna be essentially exactly the same
inside of here.
We're gonna call the addMonths function
instead of the subMonths
'cause we wanna add one month instead of remove one.
So now you can see we can move forward in the months,
we can move backwards in the months,
and we can jump to the current month as well.
So with that done,
that actually takes care of the very first step
inside of our README
to create the actual calendar section.
The next thing we need to work on is the second step,
which allows us to actually add events.
And then in turn, is going to make sure
that we need to render out these events
as well in the proper order,
which is step number three.
This is where all of the more complex code
for this is going to come in.
So the very first thing we need to do
is when we click this add button,
we wanna be able to pop up a modal
that allows us to create a new event.
We also need to build able the store events
and all of that fun.
So the very first thing I wanna do
is actually create a context.
Now, the reason I'm creating a context
is because every single thing inside of our calendar
is going to depend on these events.
So every single thing in our application
depends on these events.
So it makes sense to put this in a context
so we don't have to constantly parse down our events
and all the event update or functions inside of our props.
We can just do that all inside of one context.
I'm gonna create a new folder,
I'm gonna call this "context," just like that.
Inside here, I want to create an events context.
So we're gonna call this events.tsx
and that's 'cause we're gonna put
some jsx code inside of here.
And we want to create our context just like this.
So we'll just say new...
Oh, I'm sorry,
createContext from React.
There we go.
And by default, we need to give this a value of null.
We also need to type
what is going to be inside of this context right here.
So we want this to be our EventsContext type
or we want it to be null.
These are the only two types that this thing can be.
And the reason for that is 'cause our default value is null.
And if you remember, the Typescript video
where I talked about context,
we're using that exact same pattern here.
So now, it's create a type for my events context.
There we go.
So that we get rid of some of those errors.
And now we need to brainstorm
what is going to be inside here.
Well, obviously the very first thing we need
is essentially all of our events.
So we need our events
which are going to be an array of event objects.
And I'm actually gonna create this as a separate type
'cause we're gonna probably use this
in many places in our application.
So we're gonna create an Event type just like this.
And we know that our event
essentially is going to have a name,
it's going to have some other properties inside of it.
If we look at our README here, you can see we have a name,
an allDay, startTime and endTime and a color.
We also need to make sure we have an ID as well
so we know what the unique event is going to be,
just so we can make sure we have a key for React.
So let's create an ID, we'll use a string for that.
Then what we're gonna do is we're gonna have a name,
which is a string.
And again, this is going to be required
'cause it is a required field.
We're gonna have a color,
and you may think we just put this as a string,
but technically our color
actually needs to be either red, green or blue.
Those are our only three color options.
If we look at our README, you'll notice I say it right here,
the color must be red, blue, or green.
So what we could do,
there's a few different ways you could do this,
is you could say we're gonna have red,
we're gonna have green...
Oops, green, and we're gonna have blue.
And now if we hover over this color,
you can see the type is either red, green or blue.
But I also want to use those exact same colors
inside of my code as well
because I wanna be able to loop through them.
So the best thing for us to do is actually create an array.
We're gonna call this EVENT_COLORS.
And we're gonna set this to an array
that has red, green, and blue inside of it.
And then I can actually use the type of that inside of here.
So what I can do
is I can say I wanna get the typeof my EVENT_COLORS.
And I specifically want to get each number property from it,
'cause essentially I have different indexes,
zero, one and two,
that I want to get from it.
Now, this isn't quite enough because if we hover over this,
you can now see the type of color is a string.
And that's because we need to say that this array
is going to be as a constant variable.
By saying as constant,
we're essentially saying that this array
is always just these three values.
And this code right here
is going to allow us
to get each of those values from our array.
And as you can see here,
this is now the value of red, green or blue.
This is great
because we can actually use this array in our JavaScript
to be able to render out
all the different buttons for red, green and blue,
while also using it as a type
instead of Typescript right here.
Now, the last thing each of our events need
is a day that they're associated with.
So we're gonna parse in a date here,
which is a type of date.
Now comes the more confusing part about our event
because our event can either be an all day event
or can it be an event that has a start time and an end time,
but it cannot have both.
It cannot have a start time, end time and all day.
Obviously, that's not possible.
So what we need to do is we know all of our events have this
and then our events
are broken into essentially two different types.
So the best way to actually display this in TypeScript
is if we put an ampersand here
that's saying that we want to concatenate
two different types together.
And here I want to say,
one of two different types
are going to be concatenated to this.
So the very first type
is where our allDay is set to false.
For allDay is set to false,
well, then we're going to have a startTime
which is just a string
because that's the way that the time input works
inside of JavaScript.
And our endTime which is also going to be a string,
just like that.
So if allDay is false,
this is what our actual type looks like.
But what we wanna do
is we wanna wrap this inside of parentheses
'cause we also want another type.
So we can just copy this exact same type
and I'll paste it down inside of here.
Give this a quick save.
So by default, what this is saying is if allDay is false,
our startTime is going to be a string
and our endTime is going to be a string.
If our allDay instead is set to true,
well, then our startTime here needs to be a type of never.
And we also need to put this print
or this question mark here,
essentially saying
that the startTime is never going to be defined at all.
Same thing here,
endTime is optionally defined
and we need to make sure it never has a type at all.
So if allDay is true,
we can never have a startTime or an endTime.
And now if we hover over our event,
you can see it has this rather complicated type.
But really, all it's saying
is that if we define allDay as true,
we cannot define a start date or endTime.
But if we have allDay as false,
we must define a startTime and an endTime.
We can really easily see that.
But if I just come in here
and I just say const e equals an event,
so we just wanna type it as an Event,
we're gonna set that equal to an object.
You can see if I do my auto complete,
I have all these different fields.
If I set allDay to be true,
now, you'll notice that startTime here,
if I try to set this startTime to anything,
I'm going to get an error.
It doesn't actually show up an error here,
but if I hover above my e,
you can see it's saying that this type
is not actually able to be assigned
and that's because it's missing some of the properties.
So if we come in here,
let's just define everything that we need.
We have a name that is going to be a string.
You can see we have an id that's going to be a string.
Our color, we'll just set as red.
So this has all the different properties that we need.
You'll notice again, we're getting this error right here.
Oh, we need to add date.
Sorry, date is new date.
There we go.
So that is all the different properties we need.
But again, we're still getting an error.
And if we scroll down,
property of type startTime is not compatible.
Type string is not assigned to undefined.
And that's 'cause this startTime must be undefined
because our allDay here is set to true.
If allDay was set to false instead,
then what we could do is we could add our endTime on here
and give it as a string.
And now you'll notice that the error has gone away
and it's essentially just saying this is not being defined,
that's why it's giving me a little bit of an underline.
But again, as soon as this changes to true,
I'm getting an error
'cause I have endTime and startTime defined.
If I remove those,
you'll notice the error goes away
because now it doesn't have those.
So this is just making sure,
depending on what my allDay variable is, true or false,
that my startTime is always going to be assigned properly.
It's a little bit confusing
but it's going to make sense as we start to use this code
because it makes it a lot easier for us to use.
So now let's go ahead and actually define the provider
that's going to wrap everything.
So we're gonna create a function EventsProvider.
There we go.
This is gonna take in some children.
And inside of here,
we just want to take our Context.Provider,
and a wrap of those children.
Super straightforward stuff right here.
And let's give this a type EventsProviderProps.
Type for EventsProviderProps
is gonna be a children prop,
which is going to be a React.ReactNode.
There we go.
And if we wanna make this a little bit cleaner,
we can remove the actual import there
and just import this manually
so we have ReactNode being shown up here.
Now, if I make sure I spell provider correctly.
There we go,
that should link everything up.
And as you can see, it's working.
I just need to provide this a value,
which by default we're just gonna set this to null for now,
just so we get rid of all of our different errors.
Now, what I'm gonna do is I'm gonna export this function
'cause we're obviously going to be using this
inside of our code
to wrap everything that's dealing with all of our events.
Now, inside here we need to actually store our events.
So I'm gonna use state to do that by default.
So we have our events and our setEvents.
This is gonna be using local storage,
just like that.
And this is gonna take in an array by default.
And actually, we're just gonna use state, not local storage.
The local storage portion is for the bonus section,
which we'll get to in a little bit.
So we're gonna use state.
By default, it's an array
and we wanna specify this to be an Event array
so that we actually have TypeScript typing
'cause now as you can see here,
we have an Event array instead of a never array.
So now, inside of our value here,
we can actually parse down the events.
So we can take an object that takes in our events,
just like that.
And that actually solves all the different bugs we have
'cause that's the only thing inside of our event context
that we care about.
We obviously probably need a way to add an event though too.
So we're gonna have an addEvent function.
And this addEvent is going to be a function.
Right here, it's going to return nothing
so we'll say it's going to return void,
but we need to obviously parse some information into here.
So we need to parse in some kind of event.
And you may think we just type it as an Event, like this,
but when we're adding an event,
we don't have an id to parse along.
Obviously, we have everything else except for the id.
Now, luckily,
TypeScript has a way for us to actually type something
that includes everything except for certain fields,
and that's by using the Omit property here.
So we can use this Omit type
and we can parse in an Event,
as well as the thing we want to omit,
which in our case is id.
And now, this event is going to be everything
except for our id.
So it's gonna require all these properties except for an id.
There's one minor problem,
unfortunately, the omit type inside a TypeScript
as well as many of the other types like pick for example,
do not work well if you have these union types like this.
So instead, we need to create our own Omit
that is going to work for unions.
And we're just gonna call this a UnionOmit.
I'm just gonna create a brand new file
that is going to be for that specific type.
So instead of our utils here,
I'm gonna create a types.ts file,
and it's going to define that union type.
So we're gonna export a type for UnionOmit.
And essentially, we're just gonna create
our own brand new omit
that is going to work for a union type.
So to do this,
our union type here is going to take in some properties,
some generics.
We're going to have a generic T,
which is going to be the thing we want to get rid of.
So this is our event in our case.
And then K here is just going to be either a string,
a number or a symbol.
And essentially, it's just saying
it's anything that's inside of this T property here
that we want to be able to omit.
Then we just come in here,
and we can say that T extends unknown,
just like that.
And we're actually gonna use this as a conditional.
So when T is unknown, it's going to do one thing
and when it's not unknown, it's going to do something else.
This may seem like it does essentially nothing
but what happens when you write out code
that says T extends unknown
or you just have some type of generic extends unknown?
What you're saying is if you have a union,
which is exactly what we have here,
we have a union
where we have this type unioned with these two types here.
It's going to distribute this across all the unions.
So when we say T extends unknown,
it's essentially going to take our union,
split it into all of its different parts
and it's gonna run whatever code
we put after this question mark here
for each part of our union.
So in our case, we want to Omit T.
Oh, I'm sorry.
Yeah, we want to Omit T here with K.
So we're just calling the omit function just like normal
with our T and K,
and we're just spitting out right here,
otherwise we're doing nothing at all.
So we're just taking our union
and we're essentially calling omit
on each portion of our union.
That's all that this code does.
It looks really complicated
but that's exactly what's happening.
So now if we just take this,
close out of this and we come over here
and we import that UnionOmit,
now we essentially are properly doing an omit on our union.
So it's gonna make sure to remove the id
from all the different parts of our union,
and everything's gonna work nicely.
It's annoying that TypeScript doesn't do this by default,
which is why we have to create our own type for it.
So now let's create that add event function.
We can come down here,
we can say function addEvent.
This is gonna take in an event.
And we know that this is going to be a UnionOmit
where we omit the id from our event object,
just like that.
And then inside of here, we want to set our events.
We're gonna set our events
based on the events we're getting in.
We'll just call this event.
Ah, we'll just call it e actually,
make it a little bit shorter.
There we go.
What I wanna do is I wanna take all of my current events,
spread them out so we have them all.
And I'm gonna create a brand new event
that takes in this new event
and I'm gonna add an id to the end of it.
And this id is just gonna come from crypto.randomUUID.
There we go.
So it's going to create a brand new event,
and we can parse that in here
as one of the other properties being parsed in.
So now we have no more errors inside of here,
which is great.
And we have the ability to add a brand new event.
Now, all we need to do is to create a hook
that allows us to actually consume this event provider.
So I'm gonna create a function called useEvents,
just like this.
This is a custom hook.
And what I wanna do inside of here
is I wanna get the value from my context.
So we're gonna say useContext,
that's gonna take in my context variable.
Let me make sure I spell useContext correctly,
and actually get that imported.
There we go.
Then if our value equals null,
that means for some reason we didn't use our hook here
inside of our actual provider,
which is obviously not ideal.
So we want to throw an error.
So we're gonna throw a new Error
that essentially says useEvents
must be used within an EventsProvider.
There we go.
We'll call this an EventsProvider.
There we go.
And if we don't have an error,
we can just return our value.
So now this function is always going to return to us
an EventsContext
'cause it takes care of that null version right here.
Now, we just wanna make sure that we export this,
but you're gonna notice we get an interesting warning
if we do that.
Essentially, it's saying that we can't do a fast refresh
if we export a non component
from a file that exports a component.
So the best way to get around it is just remove this hook,
put it in its own file,
we'll call it useEvent.ts,
and we can just make sure we export that here.
Let's come in here
and actually import our stuff that we need,
so we can import useContext.
And we want to import this context as well,
which means we need to export it up here.
So we can say we're gonna export our context,
just like that.
And this is still allowing us to export this properly
'cause this is essentially a React component.
Now, here we can import that context just like that.
There we go.
And that does everything that we need it to.
Now, the other annoying thing
is we won't be able to export this variable right here
for the exact same reason.
So instead, what I wanna do
is I wanna take this variable
and we'll just move it into here for now.
It's not like super ideal,
but it's going to be fine for our use cases.
And then here where we're using our event colors,
we can make sure we import that.
Get rid of all the exports we don't need,
just like that.
Everything is still working fine
and this useEvent hook is working fine.
The reason I exported this and moved it in here
is 'cause we're gonna need to use these colors
in other places.
For example, when we create our form for adding a new event,
we need to know what all the color options are.
So I'll just preemptively move this into here
so we can use it.
And again, the reason it can't be in this file
is 'cause we cannot export something that's not a component
if we want to have that fast refreshing in development.
It's only for development,
so it doesn't matter if you have it in there for production,
but it's going to make your development environment
a little bit quicker and easier to work with.
So now from here,
we can actually wrap our entire application
in this EventsProvider.
So I'm just gonna wrap my calendar in that EventsProvider.
So we can say we have our EventsProvider,
just like that.
We're gonna wrap our entire calendar inside of that,
and that way our entire calendar
has access to all of our different event information.
There we go.
Then what we can do is inside of our calendar,
anywhere we want to use this event information, we can.
So the very first place that I can think we want to use this
is inside of here
because we wanna make this plus button
actually create a modal
that allows us to add a brand new event.
So let's import that useEvents,
just like that.
And we wanna get the addEvent function from that.
So we have access to that add event function.
And we click on this plus button.
What I wanna do is I wanna open a modal
that'll use that addEvent function.
So we obviously need to create a modal
that's going to do this.
And again, I'm just gonna do this in the exact same file.
You could break this into different files if you want,
but in our case,
I'm just gonna create the event modal inside of this file.
So we're gonna come in here,
I'm gonna create a brand new component.
We're gonna call this EventFormModal,
just like that.
And inside of this EventFormModal,
we need a few separate things.
The first thing that we're gonna need
is an OnSubmit function.
So when we submit our form,
we can either create an event or update an event.
We also need to have a delete function as well
because we can delete events from inside of our modal.
And we also need to have the event itself.
If we're doing an update,
we obviously need to be able to update a specific event.
And then we need the date
that this event is going to be happening on.
On top of that,
we're also gonna need all of the information
related to our modal.
But since we have multiple modals in our application,
I actually wanna create a brand new component
called modal.tsx.
And this is where all of our modal related code
is going to go.
And inside of this EventFormModal,
I just wanna return a new one of those modals
and I wanna parse in all of our modal related code.
So I'm actually gonna take all the rest of the props
and they're gonna be my modal props.
I'm just gonna parse those directly into my modal,
just like this.
There we go.
So now, let's go ahead and actually create this modal.
So we can say export function Modal.
Inside of our modal,
we wanna return a div,
which is gonna have a class name here of modal.
There we go, we can close that off.
And inside of our modal,
if we look at our actual starting HTML,
we scroll down to the modal section,
we can see exactly
what each of our modals is going to look like.
You can see we have a modal,
inside of that we have an overlay,
and inside of that we have our body,
which contains all of our content.
So we need to create a div with the className of overlay.
Close that off.
We need to do the exact same thing,
but this div instead is going to be our body.
And this is actually where all of our children
for our entire modal are gonna go,
which are gonna get parsed in up here.
There we go.
Let's make sure we create a type ModalProps.
And we can make sure that children is a ReactNode,
just like that.
And we'll import that.
We also wanna be able to open and close this modal.
So we should probably take in an isOpen prop,
and an onClose prop as well.
So we can say isOpen is a Boolean,
and onClose here,
that is going to be a function that just returns void.
There we go.
So then what we can do
is we can make sure if isOpen is not true,
so if it's not open,
we can return null
so we don't render out our modal at all.
Then we can specify our ModalProps here,
just like that.
There we go.
And our overlay,
we wanna be able to close our modal when we click on it.
So we can just say that onClick,
we wanna call the onClose function,
just like that.
There we go.
And finally, we want to render this as an actual portal
because obviously we don't want our modal
to show up directly where it is,
we want it to show up in a portal.
So we're gonna call, createPortal,
just like that.
Parse in our component.
And we also want to parse in an area to put it in.
So I'm gonna do a querySelector
and I'm going to get here a modal-container,
just like that.
And this createPortal function,
we wanna make sure that we import this.
So we'll just come up here,
we'll say import createPortal from react-dom.
There we go.
And here we're getting an error.
Essentially, the type null is not assignable to element
and that's because this could be null,
but we know for a fact it's not going to be
'cause we're gonna add our own element.
So we're gonna say
that this is specifically going to be an HTMLElement.
And that's because in our index.html here,
we're just gonna add our own element here,
which is our modal-container.
There we go.
It's a div with that specific id.
And that way, inside of here,
we're always gonna make sure this exists.
Now, the final thing I wanna do
is I wanna hook up an event listener.
So we can say useEffect here.
Oops, make sure I import that from React.
And this is gonna hook up an event listener
just so when we click the escape key,
it also closes our modal.
So we're gonna come in here,
put in our onClose function
'cause we want this to rerun every time that changes.
We're gonna create our handler,
which is going to take in a keyboard event.
So we're gonna have a keyboard event.
And if e.key is equal to escape,
that means we click the escape key,
so we want to close this just like that
by calling onClose.
Then we obviously need to hook up this event listener.
So we'll say document.addEventListener.
We're gonna do this on key down.
We're gonna call this handler,
and then when we wanna do the exact same thing here.
We want to return and we want to remove that event listener.
So we're gonna removeEventListener.
This is going to be for key down
and we wanna remove the handle click or the handler, sorry,
just like that.
So now we have our base modal done.
That's essentially just wrapping everything
that's going to be inside the modal.
So if we're looking at that index.html,
that's just this content right here.
We obviously need to create this stuff
inside of our modal next.
So we come into our calendar here,
let's import this modal,
just like that.
And let's set up the type for our EventFormModal as well.
So we can have a type here, EventFormModalProps.
And we know that we have a few different things
we're gonna need.
First of all, we're gonna need our onSubmit,
just like that.
We need our onDelete.
And our onSubmit here is just a function
that's going to return void.
We'll figure out the actual properties for it in a second.
We're gonna have onDelete.
Again, that's going to be a function which is void here.
We also have an event that we're going to be taking in
and this is going to be an event object.
And then finally, we're gonna have a date,
which is a date object.
Now, for our submit function,
we're essentially going to parse in an event.
And that event is everything that our event takes in,
except for it's not going to have an id.
So it's going to be that same UnionOmit
that we've messed with before.
We have an event that doesn't have an id on it.
That's pretty straightforward.
For onDelete,
we actually don't need any properties all being parsed in
because we can get the id
from wherever we place this EventFormModal.
For example, inside here where we have our addEvent,
when we click on this add button, for example,
or when we click on an edit button,
we're gonna have the id of the actual thing we can delete.
So we don't need to worry about the id at all
inside of here.
Now, one important thing to note though
is that if we parse in an event,
that means we're editing our event
so we need to also parse in an onDelete.
But we don't need to pass in a date
'cause we can get the date from our event.
But if we want to add a new event,
we don't have any event to parse in,
we don't have a delete to parse in,
but we do need to tell it
what date we want to add this new event to.
So we kind of have a situation
where we have essentially different unions
we want to create.
We have two different types
based on if we want to actually have an event
or not have an event.
So this I actually want to remove,
and instead I want to do the exact same union trick
that we did before,
'cause all of my types are gonna have an onSubmit.
And then inside of here,
I want to have different types as well.
So what we can do is we can say in one particular scenario,
I want to have the types of onDelete,
and I want to have the type of an event.
And our date here is never going to exist
'cause we can always get that from our event itself.
Otherwise, I want to do the exact same thing,
I'm just gonna copy this code down.
But in our case here, onDelete is never going to exist
'cause this is going to be for a new event,
which means our event is never going to exist
but our date is going to exist
and it's going to be of the type date,
just like that.
So if I give this a quick save,
it should format for us.
And as you can see, we essentially have two different types.
One that takes in just a date
and one that takes in an onDelete and an event.
And that's going to depend on if we have an add event
or if we have an edit event.
And if we add those actual types here,
so we'll say EventFormModalProps,
you'll notice it looks like, for the most part,
everything's working
but we have our modal props which are not correct
that's why we're getting an error here
'cause we need to have essentially the correct props
of children isOpen and so on.
So to do that,
the final thing that we need to do
is to actually take in another add-on here.
So we can say we want to add on another type
and that type is going to be our ModalProps,
which we can just like export here from our modal.
So we're gonna say we're gonna export those modal types,
we're gonna import those,
just like this.
That'll work fine,
but we don't want this ModalProps to contain our children.
Instead, we want to omit that.
So we're gonna just Omit the children property from here,
that way it doesn't actually take in our children.
We need to manually parse in our children into our modal,
otherwise we're going to get an error.
Now, you'll notice I didn't have to do the UnionOmit here
and that's because this ModalProps is not a union,
it's just a normal actual type
so we can just do a normal omit on that.
So now, let's go ahead and get the actual stuff
we're gonna put inside of our modal.
I'm just going to open up that index.html again.
And essentially,
I wanna copy everything inside the modal body.
So I'm gonna start with the title here,
and we're gonna move down.
And this is actually the wrong modal
'cause this is the one showing all the different events.
I want the form modal,
which is this one down here.
So we're gonna start with this title,
go all the way to the bottom
where we have all the content we need,
which is I believe just this content right here.
Yep.
So I'm gonna copy this content, I'm gonna move it over here.
I'm gonna paste it directly into our modal,
so that at least we'll render out our modal.
We have a few errors
because we need to change like class name and stuff around.
But for now, we can actually see if this works
by trying to render out this EventFormModal.
So it's come all the way up here into our CalendarDay.
What we wanna do
is whenever we click on this button right here,
we essentially want to open that modal.
So let's create a state variable to deal with that.
We can say, isNewEventModalOpen,
setIsNewEventModalOpen.
And we can set that to a useState variable
that defaults to false
'cause we obviously don't want it to be open right away.
And then down here we can actually render out what that is.
So here when we click our plus button,
onClick, we want to set our isNewEventModalOpen to true.
There we go.
So we're actually opening up the modal
when we click on the plus button.
And then we need to render out the modal as well.
And we can just do that somewhere inside of this component.
So let's just go down to the very bottom of our component.
We'll just do it right here, for example.
We're gonna have our EventFormModal.
There we go.
Close that off
'cause we don't need any children inside of it.
And we need to parse in the information we need.
So for example, we need to parse in what the day is.
We have that day variable for that.
We also need to parse in if this is open or not.
That's just gonna be equal to that isNewEventModalOpen.
We need to parse in onClose,
and that's just going to be here,
setIsNewEventModalOpen to false
'cause we no longer want it to be open.
And then finally, we need to parse in onSubmit.
So we can say onSubmit...
And that's going to be some type of function
that we're going to deal with.
For now, I'm just going to have it do nothing at all
so we'll just say it'll return null by default.
There we go.
And now we got rid of all of our errors.
And if I try to add like an onDelete onto here
that does something,
let's just say it does like this,
you're gonna notice we get an error
because we can't have this onDelete property
since we did specify an actual date here,
that means that we can't have this onDelete.
And that's exactly what we want.
Now in our case,
instead of parsing in this default value here for onSubmit,
we actually would want to call the addEvent function.
What that's going to do
is it's going to add a brand new event for us.
But it looks like we have an error here.
It's essentially saying that the types here
are not quite correct.
So it looks like our onSubmit
maybe has the wrong type being applied to it.
If we look at our onSubmit,
this is the correct UnionOmit,
that's perfectly fine.
So let's go over to our event actual thing here.
So you can see addEvent is a UnionOmit, which is fine.
That's a UnionOmit here, which is perfectly fine.
That should be working.
I believe I actually know the reason for this,
it's because we're using the wrong event in here.
We actually want to import our event.
So instead of using the event that's built into TypeScript,
if we look up here,
it's most likely importing something
or it's just using the built-in type,
we actually want to use the event type
that we're defining here.
So I want to export this event type,
just like this.
And we can come back here
and we can actually import that event type as well.
So here we have an event.
I want to import my own event type,
that should hopefully clean up some of these problems.
And there we go, it did fix those problems.
So now let's go ahead and clean up our form.
But before we do it, let's just see if it opens up.
If we click the plus button, does it open a form?
You'll notice it does open the form, which is great.
Obviously, none of the other stuff
like this close button work.
But if we click outside of here,
you'll notice it closes, which is exactly what we want.
And if we click escape, it closes.
So at least that is working.
The next thing to work on
is all of this code inside of here.
So here we have class,
we want to replace this with className.
So I'm just gonna select everything that says just class,
replace that with className.
There we go.
That fixed that problem.
Same thing here where we have for.
I want to replace that with htmlFor.
There we go, that fixed those problems
and that cleaned up
just our generic React to HTML conversion,
which is perfectly fine.
Now, what I wanna do
is I wanna fix up some of the code inside of here.
For example, this should say add event
when I create a new event,
and it should say edit when I'm doing an edit.
So just create a simple variable.
We'll say const isNew,
and that's equal to if our event is equal to null,
'cause if we don't have an event
we're creating a new event.
Then I can use that inside of here.
If it is new, this should say add event,
otherwise it should say edit event,
just like that.
So now if we give this a save, open this up,
you can say it says add,
which is exactly what we want
because we're currently creating a new event.
I also want to display the date here properly.
So I can come in here and I can format this date.
So I can say I want to format our date.
In our case I wanna get our date,
but our date could be null.
So if it's null, I want to use our event.date
because in our case,
the event would actually be not null if our date is null.
So that's how we can get the actual proper date.
And then I can parse in
what I want the actual date style to be.
So I can say that our dateStyle
is going to be the short date style
'cause that's kind of what we want this to look like.
So now, if I click this on the 6th of June,
you can see it says 6/6/23.
If I try to open up 30th June,
you can see it says the 30th of June.
Now, here for our close button,
we just wanna close our modal.
So we can say when we click on this,
we want to call the modalProps.onClose.
There we go.
To close out of that.
So now, if I click that close button,
you can see it closes my modal.
Next up we have all of our form inputs.
Now, since it is technically possible
that we could render out this modal multiple times,
it's not good to have our htmlFor and our ids
hardcoded like this.
So instead, I'm gonna use that use id hook.
So I can just say const formId equals useId.
There we go.
And now I can use that form id here.
So instead of having a hardcoded name,
I could say that we want to use that form id
and then just add the name to the end of it,
just like that.
And I can copy this and paste that down as my id as well
so they both properly hook up just fine.
I can do the same exact thing here for all day.
So I wanna get both of those
and I wanna change this to say all-day.
There we go.
Now, let's bring that down another time.
So we'll come down here where we have our start time.
So we're gonna say our start time
is going to be that in both cases.
We're just gonna replace this with start-time.
And this is just to make sure
everything is hooked up properly.
So same thing here for end-time,
let's do the exact same thing.
And this will say end-time.
And again, we just need to do it
for all the rest of the components that we have.
For our input down here,
we have our label right here
and we have the actual id up here.
So let's make sure we do that.
And this will just be a color.
So in our case, we have like blue as our first color,
but we're gonna make sure that we do this inside of an array
so we don't need to hard code all of this.
So these other inputs, we actually don't need.
So I'm just gonna remove these other inputs
and we're gonna remove the label
'cause we're just gonna put this inside of a loop.
So right now, it should just show blue.
And as you can see, it did just show blue.
Also it looks like I have this data attribute on here
from the HTML.
We don't actually need that.
I'll remove that from your starting HTML
so you don't have it,
but I'm just gonna remove that 'cause we don't need it.
Finally, we have our add and delete button down here.
So what I wanna do
is I wanna make sure our delete button only renders
if we have an actual delete.
So if onDelete is not equal to null,
then I wanna render out this button for delete,
and we can just set up an onClick that calls onDelete.
There we go.
Just like that.
And then for our add button here,
I just wanna change the text inside of it.
So if it isNew, then I want it to say add,
otherwise I want it to say edit.
For our input down here,
we're gonna make sure that we do this inside of an array.
So inside here, what's come in,
we're gonna take our event colors,
I wanna loop through each one of them
by mapping through them.
So we're gonna get a particular color,
and I essentially want to render out this code right here
but for the specific color.
And this is going to need to be in a fragment
and we're gonna use the full fragment keyword here
because we need to specify a key on this,
which is just going to be our color.
So we're gonna put this in a fragment
so we can make sure we can render multiple things.
Then inside of here for the value,
I want this to be our color as well.
And here, where we have the color blue,
I want this to be our color.
And finally here, where this says blue,
I want this to also be our color.
So now if we get this quick save,
click plus,
we should hopefully have all three of our colors showing up.
And as you can see, we do.
And we can click on them, just like that,
which is great.
Now, to determine which button
is actually selected as the correct color,
we actually need to use state
to handle this the easiest way possible.
So we're gonna create a state variable called selectedColor.
And if our selected color is equal to our current color,
we're gonna set this as checked.
Then whenever we change this...
What that is essentially saying
is that we are currently selecting this color,
that's what onChange is doing for us.
So I want to essentially call the function
that is going to select that current color.
So I'm gonna set the selected color to the current color
and then we can create that state variable up here.
So we can say selectedColor
and setSelectedColor is equally useState.
Now, by default, I want that to be our event.color,
but if our event doesn't exist,
I want this to fall back to the very first color
inside of our array,
just like that.
So now, if I give that a quick save,
and I click on this plus,
you can see it's selecting red,
which is the very first color in our array by default.
And as I change these colors,
you can see it's still properly changing them,
which is perfect.
That's exactly what I want.
And I close and reopen it.
You can see that's still keeping all that information.
Then the next thing to work on
is going to be this all day button
'cause this was checked,
I want these two to be disabled inputs.
And when it's unchecked, I want them to be enabled.
So we need to be able to check what this value is.
And again, we need state for that.
So we can say isAllDayChecked,
and setIsAllDayChecked.
Set that equal to a useState variable.
And again, we want to get that from our event.
So we'll say event allDay,
or we're gonna set it to false as the default value
if this does not exist.
Then what we can do is we can come down here
where we actually have our end time and our start time.
And on our input here,
what I wanna do is I wanna make sure that this is required
only if our all day is not checked.
So if all day is not checked, it should be required.
And we wanna disable this if isAllDay is checked.
There we go.
I wanna do the exact same thing on this input.
So let's just paste that down in there,
give that a quick save.
So now if I check isAllDay,
it should disable both of these inputs,
but it doesn't look like it's quite working.
That's 'cause I haven't hooked up isAllDay yet
to actually that state variable.
So here for our input,
what I wanna do is I want to have a checked property
which is equal to our isAllDayChecked.
And onChange here, I wanna set that to a function
which just sets the correct value.
So we can get that from our e here
and we can just say isAllDayChecked?
We wanna set that.
So setIsAllDayChecked.
There we go.
And we just wanna do the e.target.value.
And actually this should say checked not value.
There we go.
Give that a quick save.
And now if I toggle this,
you can see that these go from actual valid inputs
to disabled inputs, which is great.
Also our name field here,
we can make sure that this is required.
So we're just gonna put the required check on that as well.
Another thing that we can do
is we don't need these name properties
so we can just remove these name properties.
We don't need them
because of how we're going to be handling all of our stuff.
So let's remove this name property here,
this name property here.
But for our actual radio inputs, we need the name
so it is able to link up all of our radio inputs together.
So we're just gonna keep that as is for now.
So now scrolling back up,
the last kind of validation check we need to do
inside of our HTML
is to make sure that our start time
is always before our end time.
So we need to give this a minimum value
which is just equal to whatever our start time is.
So we need to obviously turn our start time
into a state variable.
So same thing we've done here.
I'm just gonna copy this down.
This is going to say startTime.
This is gonna say setStartTime,
just like that.
By default, it's our event startTime,
otherwise we're just gonna default it to an empty string.
So now, if we give that a save real quick,
and let's say we set our set time to 2:37 PM
and our end time we try to set to 4:00 AM,
if I click add, you're gonna notice
if I actually give this a name,
it looks like it's working.
So clearly it's not quite working like we want it to,
obviously.
And that's 'cause we're not setting our start time properly.
So let's scroll down here
to where we have our start time and our end time.
And what I wanna do
is I want my value to be equal to my startTime,
and onChange here we want to set that start time.
So we can say we're going to set our start time
equal to e.target.value.
So that should hopefully fix that problem.
So now if I try to set my start time to 2:37,
and I set my end time here, we'll say 1:37,
so it's before it,
and we click add,
you're gonna notice it says it must be after 2:37.
If I change my start time to, for example,
be like 8:00 AM or 7:00 AM,
that's obviously before 1:30.
Click add,
and now it's working properly.
So now the next thing to do
is just to finish up the other inputs that we have.
So we have our start time being tracked,
we have our all day being tracked,
and we have our color being tracked.
We just need to track our end time and our name.
And to track those,
I'm just gonna be using refs
'cause that's about the easiest way to do this.
So in our end time here,
I can just say that we have a ref
and that's just equal to endTimeRef.
We can do the exact same thing for our name.
So we'll come up to where we have our name,
we're gonna set a ref to our nameRef.
And let's just create those variables here
so we can scroll back up to where we were
and we can say we have our const endTimeRef,
which is equal to useRef,
just like that.
We want this to be null
because we know that we're going to be using an HTML element
and in our case this is an HTML input element.
Now I can copy this down,
I'm gonna do the exact same thing for our nameRef.
There we go.
So now we have all of our different properties
being hooked up.
And now we can just make it
so that our form has an onSubmit.
So we're gonna say onSubmit,
is gonna call a handleSubmit function,
which we'll just create up here.
HandleSubmit,
just like that.
This is gonna take in an event property
which is a FormEvent.
We wanna make sure that we prevent the default of this.
Then what we wanna do is we want to get our name,
which is coming from our nameRef.current.
And we wanna get the value from that.
I wanna do the exact same thing with our endTime.
So we just have all of the variables to work with.
This is our endTimeRef.
So now, if for some reason our name equals null,
or our name equals an empty string,
we could do the exact same thing for all of other props.
Or if our startTime is equal to null,
or our startTime equals an empty string
or the same thing for our endTime if it's equal to null,
or if our endTime is equal to an empty string,
well, we just want to stop,
so we're gonna call return here.
So if in some reason
all of our variables here that are required
are not specified,
we want to do a quick return.
And actually we really shouldn't check
for our start or end time quite yet
because those can be null
as long as our all day is actually set to true,
because then obviously,
we don't wanna start time or an end time.
So let's just do our name check, very first thing,
then what we can do
is we can actually check to see
if we need to do that additional check.
So first, let's create something called commonProps.
And this is just gonna be an object
that contains all the properties
that are shared between the all day scenario
and the non all day scenario.
So our name is the same for all of them.
Our date here is just going to be our date
or it's going to be the actual date of our event.
So either one of these,
whichever one we parse in
is going to be what we pass to our date.
That's perfectly fine.
And then finally, our color
is going to be our selected color.
Every single event has these props, no matter what.
Then if our all day is checked,
what we need to do is we need to check to see
that we don't have a start date
and we don't have an end date.
So here, we can make sure that our new event,
which we're just gonna create as a variable,
say newEvent is equal to...
And we'll make this a UnionOmit of our event.
And sorry, that shouldn't be an equal there,
that should be our type right there.
So we have a UnionOmit between our event
and we're gonna get rid of that id.
So essentially, this is just what we're trying to create
as a brand new event.
Let's make this a let variable,
just like that.
Then what we can do
is we can say our newEvent
is equal to all of our commonProps combined together,
plus we wanna say that our allDay is true,
just like that.
So now we have a new event
that is essentially this exact type,
which is just exactly what we want.
If we didn't have this all day being specified,
we're gonna get an error
because obviously it's saying it must be specified.
We can do the exact same thing in the other scenario.
So in this scenario, we just want to specify
that our newEvent
is going to be equal to all of our commonProps,
just like before.
But here, our allDay is set to false
and we also wanna specify
that our startTime is equal to our start time
and our endTime is equal to our end time.
Also, before we do this is where we should check to see
if our startTime equals null
or if our startTime is equal to an empty string.
So we'll say startTime here,
and I wanna do the exact same thing with our endTime,
so it is equal to null or an empty string.
Then we should do a return here as well.
There we go.
So now with this code,
what we've essentially done is we said,
hey, make sure our name is defined.
If it's defined,
go ahead and define all of the common properties
we use for all of our events,
and then create a new event
that's either an all day event,
or if it's not an all day event,
check to make sure our start and end time are defined.
And if so, create a new event
that is an all day event or not an all day event.
Sorry, all day is set to false here.
Then what we can do
is we can actually call the onSubmit function
that's being parsed in.
We have this onSubmit,
let's use that onSubmit.
So we can come down here below all of these if statements,
we can call onSubmit and parse it in our newEvent.
Also we can close our modal.
So we can say modalProps.onClose to close out of our modal.
Now, if we give that a save,
click this plus button,
this should hopefully create a brand new event for us.
So we just type in something here,
we'll make it an all day,
we'll make it green,
we'll click add,
and it closed out of our modal.
So that means that everything should have worked,
which is great.
Now, we don't see our event
'cause we're not rendering them out,
but that's the next thing we can work on.
So let's scroll up your ways
and we'll just kind of minimize this stuff down
'cause we're mostly done
with the working of this kind of stuff.
So we'll minimize this entire function
and we'll just minimize down this type real quick
so we don't have all that code in our faces.
And the next thing I wanna do is render out our events
so I'm gonna obviously uncomment this.
And what I wanna do is if we have some events,
I want to actually render them out.
So to get to our events,
we're actually gonna pass them into our CalendarDay here
'cause we wanna get just the events for the particular day.
And we want them to be sorted
so we're gonna pass in something called sortedEvents.
And let's actually create a type for that.
So we're gonna say our sortedEvents
is just an array of event objects,
rather straightforward.
So now we can actually use that sortedEvents down here.
So we can say if sortedEvents.length is greater than zero,
well, then what I wanna do
is I wanna render out all of this code inside of here.
I'm gonna copy this,
paste this into there,
just like that.
So we have all of our different events rendering out.
Obviously, we're not looping through them right now,
but that's perfectly okay,
that's going to work.
Now, we can actually make sure we parse in
our sorted events.
So up here where we have our days
and we're rendering around each calendar day,
we need to have our sorted events.
Now, our sorted events
are going to come from all of our events.
So we can use our nice hook that we've already created
called the useEvents.
We can get our events from that,
just like this.
And then what we can do is inside of here,
we can just run a simple function
that's going to get the correct events for us.
So we can say events,
we wanna filter our different events
for each event where it is the same day...
Oops, same day as the day that we're looping through.
Let me make sure that we import that properly,
it looks like we are.
And we wanna make sure it's the same day as our day
and our events day.
So our event date
is exactly the same as the current day that we're on,
and that's going to give us an array of sorted events.
So now we have our array of sorted events down here.
We're looping or checking to see if we have sorted events
and if we do, we're trying to render them out.
So right now, it won't render the correct event
but if we have an event,
it should render this placeholder data.
So if I create an event here, for example,
let's just make it an all day green event.
And I add, you can see it's showing that placeholder data
because it says that there is an event
so it's obviously going to show us the event.
Now, one thing we do need to change real quick
is the events we're parsing in here are actually not sorted.
So we're just gonna call this events instead
'cause they're not technically sorted events,
they're just normal events,
we haven't sorted them yet.
We're gonna sort them inside of here.
So this again changes to events.
And instead we're gonna get our sortedEvents like this.
And we're gonna use memo for this, again,
'cause this is something that could be time consuming,
we don't wanna waste a bunch of time on this function,
we only wanna run it when our events change.
So if our events change, then we can recall this function.
So this sortedEvents function
is gonna be a little bit complicated.
The first thing I wanna do
is I wanna create a really simple function
that's going to convert
the actual time of the event to a number,
so we can do a comparison between them.
So I'm gonna say timeToNumber,
is going to be a function we create,
which is gonna take in a time string
which comes from our actual time,
and it's just going to be numbers that look like this.
For example, it's gonna be like 12:43,
or it's going to be something like 20:42.
That's what it's going to look like.
I'm essentially just going to replace this colon
with a period,
and that'll give us an actual number
as opposed to a string.
So we're taking in this string,
what I wanna do is I wanna split it.
So time.replace.
I wanna replace it by replacing the colon here
with a period.
And then I just wanna turn that into a number
by calling parseFloat.
There we go.
So this is going to give us a number from the actual time
so we can use that in our comparisons.
And then what I wanna do is I wanna just sort my events.
So first of all, I'm gonna say events.sort,
and the reason I'm converting this into a brand new array
is 'cause sort sorts in place,
and we actually want to sort the new array,
we wanna create a copy to sort,
so this creates a copy
and then we're going to sort it.
So we can sort through A and B.
And what we wanna do is here, first of all,
if A is an all day event and B is an all day event,
then what we wanna do is we return zero
because we're saying that they're both exactly the same,
it doesn't matter what the order is.
Else if, A is an all day event,
and that means B is not all day,
then we're gonna return negative one
'cause A should be before the B.
Same thing here.
If B is all day, we're gonna return one
'cause we want B to show up after A.
And then finally, if both of them are not all day events,
we wanna just convert the time to number
for our startTime of both of them,
and we wanna subtract them.
And that way, if our startTime for the first one is sooner,
it's going to show up first.
Same thing here,
timeToNumber, b.startTime.
There we go.
And let's make sure that we return that.
That should fix all the different errors that we have.
And this will actually give us our sortedEvents now,
which is exactly what we want to use down here.
And now we can actually render those events
instead of just this placeholder data.
So let's create a new component to do that.
It's going to essentially emulate
what these buttons look like.
I'm gonna create a function called CalendarEvent,
just like that.
For now, I'm just gonna return to div that says "hi."
There we go.
And inside of here,
we just wanna parse in whatever our event is.
So we can say that the type of this
is just going to be an event of the type event.
We can just type this in place
'cause it's so simple
we don't need to create another prop type for it.
Now, inside this CalendarEvent,
I essentially want to get the code up here.
And we have a few different types of events.
We have an all-day event and we have a non all-day event.
So we have two different things we need to return.
So I'm just gonna copy this code down
inside of my return here.
What I wanna do is I wanna render this out.
So in our case, we have a button
and this is an all day event
so it's gonna render out something specific.
So I can say our event.isAllDay,
I think I just called it allDay.
There we go.
If it's all day,
I want to render out this particular code.
And if it's not all day,
I want to render out the code that's inside of this button.
So I'm trying to copy this, I'm gonna paste that in here,
I'm gonna make sure I use fragments
so it can actually render out properly.
There we go.
So depending on if it's an all day event or not,
it's gonna render out different code.
And up here, we want to have different classes
based on if it's all day or not.
So here, let's just use that fancy CC function
we've created before.
So we know for a fact
we want to have the event class being applied,
then we want to have our event.color class being applied,
and finally, if it's an all day event
then we want to have this all-day-event class being applied.
So let's just get that in a string.
There we go.
Close that off.
We can say if our event.allDay,
then we want to render out that all day event as well.
So now let's go ahead
and actually show the correct information.
So here, this should say event.name,
just like that.
This is the name of our event.
Same thing here, this should be event.name.
Then we want to have the actual time being rendered out here
so we have that fancy formatDate function for a reason,
event.start time.
Now, unfortunately the start time is just a string
so we need to convert it to a date.
To do that, we can use the built-in parse function
which comes inside here of date-fns.
So we can get that import parse,
we can pass it in the startTime
as well as the format that this is in.
And we know this is in the format hours followed by minutes.
And we just need to tell it what day this event is for.
So we can say event.date.
So what this is going to do
is it's going to parse us out a brand new date.
If I just close that off real quick,
that's going to format us that date.
But the format is going to be incorrect,
we need to specify the format we want it to be in.
And the format we want it to be in is a timeStyle
and we want it to be in the shortest time style possible.
So all we've done here is we've taken our time,
which is a string,
we've parsed it into a date using this format,
and we've set it on this specific date
because our time doesn't have date information
so we need to tell it the date as well.
Even though we're not rendering that information,
we just need that to create a JavaScript date.
Lastly, do we need to get our class name up here correct.
So what we can do is we can say we have our color dot
and we're going to have our event.color,
just like that.
And again, we could've used the CC class or function
if we wanted.
This works just fine as well.
It should be pretty much everything we need
to get our actual event done.
It doesn't have any event listeners or anything,
it's just a static event
but that should work just fine.
So now here inside of this loop,
we can actually render out these calendar events.
So for our sortedEvents,
we can map through each event
and we can render out the CalendarEvent.
I can parse in the event
and I can make sure that I have a key
which is our event.id,
just like that.
Now if I save,
you can see our event is rendering just like it should.
And if we create an event
that has a start time and an end time,
and we give it a date of timed,
and I click add,
let's make it red,
you can now see we have that showing up right here.
And if I make it a little bit wider,
we can actually see the title.
You can see it says timed, it's got the red dot.
It's looking exactly like we want it to.
So now let's look at our README and see what we have left,
because we've actually, I think completed everything
in these next two steps.
So step two here, we've created the new event modal
and it follows all of these different steps.
We've also rendered our events and they are stored properly.
For example, if I create a new event,
and let's say that this one actually starts at 11:00 AM
and it ends whenever,
it doesn't really matter when it ends,
let's make it green,
and we should say we'll call this one later
'cause it should show up later.
When I click add,
you can see well, actually it's 11:55 AM,
so it's rendering before.
So we do know our sorting's working properly
'cause it's going all day events
followed by the ones that start earlier
and then the ones that start later.
Now, the next thing we need to do is step four,
which is essentially when we click on one of these events,
I want it to open up a modal so we can edit it.
And this is actually going to be rather simple
because we already have our event form modal done,
we just need to parse in the right props to it.
So instead of our calendar here,
we have this EventFormModal.
This is for creating a new event.
Let's copy that down
because for each one of our events
we want to also have a modal here.
So I'm just gonna place down this modal,
make sure everything is inside of fragments
so that React will let this actually render out properly.
So now we have a modal for each one of our events
and we just need to parse in the correct information.
In our case, we want to parse in the event,
which we have right here as our event.
We also want to pass in our onSubmit,
which is going to be for updating our event.
Our onClose is going to close this modal.
So let's create a simple state variable
where we can just say is editModalOpen,
setIsEditModalOpen,
useState by default, that's going to be false.
There we go.
So here we can say is open is equal to that
and we can close it by setting that.
Our onSubmit is going to take in our updateEvent,
which is going to be coming from that context.
So we can say update event equals useEvents,
just like that.
We don't have this function yet
but we will create it in just a second.
And then the final thing we need to pass in here
is our onDelete since we're doing an edit instead of an add.
And this will just take in our delete event function,
which again we're going to parse in up here
from our used events.
So let's go ahead and actually set that up.
So in events here, for our context,
we need to create a new context for editEvent.
And this is going to take in an id, which is a string.
For the event, we wanna edit
as well as essentially all of our new event properties.
So that's gonna be exactly the same as our add
and that's going to return void.
And then for our deleteEvent,
there we go.
This is just going to take an id, which is a string,
and it's going to return to us void
and that's going to remove an event for us.
So I'll just copy this add event,
paste it down
'cause our update is gonna be somewhat similar.
The one difference is here we're gonna have an id,
which is a string.
We'll call this our event details,
so we actually didn't have a better name for it.
Same thing here.
I'm gonna call this event details
just so it's a little bit more clear what's going on.
And in here I want to take in each one of the events,
that I'm going to be setting.
So for my set events,
what I wanna do is I wanna take my events,
and instead of adding a new one here,
what I wanna do is I wanna map through them.
So we'll say e.map each one of my events.
And inside of here I wanna take my event id,
check to see if it's equal to the current id.
If it is,
I wanna return essentially an updated version
that keeps the same id
and then takes all my event details and maps it over,
otherwise I wanna return the current event.
So what this does is it says, is this a current event?
If it is the current event,
essentially take all my event details
and replace them with this new event details,
otherwise just keep the event exactly as it was before.
Then I can just make sure
that this is all being returned properly,
which it looks like it is.
And I can pass down my update event here.
Now, our function for deleting an event,
it's going to be rather straightforward.
We can take in our id, which is a string.
And then what I can do is I can set my events,
gonna loop through those events.
So e.filter each event
and I wanna check with you
that id is not equal to the id I parse in.
Then we can parse in our delete event as well.
There we go.
Looks like we have an error with our update event.
It looks like some of our types
are maybe not quite a hundred percent matching up.
That's just because I called this event details
instead of event.
Let's change that there.
That actually didn't actually fix it.
If we scroll up a little bit more,
it looks like I called this editEvent.
That's the problem.
There we go, let's call that update event,
and that cleaned everything up so it's perfectly fine.
So now, let's go back into our calendar
and you can see that these functions are actually there,
now we just need to make sure we properly handle them.
So this onSubmint returns to us the event details.
So we need to pass along the event.id,
as well as those event details,
just like that, to get this to work.
And it looks like our id property is not being defined
and that's because our calendar event here,
it should be an event which should have an id
so that should be working for us.
And the reason is I called this event here,
so it's just overriding our name.
There we go.
So it should say e right there.
So this is our new event details
that we're parsing in along with our event id.
And this delete here doesn't take in any properties
but we need to make sure we parse in our event.id
so we make sure we delete the correct one.
So now if I give this quick save
and I add an event listener on my button
to make it so that it actually opens this mod,
or when we click,
we can say that we wanna set the isEditModalOpen to true
when we click on this button.
Now, if I click on this,
you can see that opened up the edit modal,
but it's not quite getting all the properties in
like we want it to.
So it looks like there's still a little bit of a problem,
but it is properly opening up this for editing,
which is great.
And some of our default values are being propagated
but not all of them.
For example, our name and our end time are not being set,
but our all day and our start time and our color
are properly being set.
The reason for that, if we scroll down here to our modal,
is that our name, wherever we have that, here we go,
and our end time
are both using refs.
So we need to set a default value.
So we're gonna say our default value here
is just event dot.
And we're gonna set that to our name.
And then down here, where we have our end time,
default value is going to be event.endTime.
That should fix those problems.
So now when I click on this,
you can see all that information is being populated.
Click here, all the information's being populated.
And same thing here.
When I click here, it's all being populated.
If I click delete, it's removing it,
which is great.
And if I update this to, for example, be an all day event
and we're gonna make it blue,
we should see that when I click edit,
you can see now an all day blue event.
Now with that done, if we open back up our README,
you'll notice we've completed every single thing
inside of the normal instructions,
and all we have left are the bonus instructions,
which are admittedly quite complicated.
Luckily, this first one is very easy.
We just wanna store our events in local storage
'cause if I refresh my page, everything is gone.
Not super ideal.
So inside our events context here,
we're gonna create this with a local state.
So we can say useLocalStorage,
just like that.
That's just gonna be a custom hook that we create.
So we can create a function useLocalStorage.
And the nice thing about this,
since we're only using this hook in one place,
we know that we're storing an event array inside of it,
we can be very specific with the actual information
inside of this useLocalStorage hook.
For example, I know that the initial value here
is just going to be an event array.
I don't have to worry about it being any other type.
Also the key here,
I know that that's going to be a string.
And normally if you wanted to create this hook
you would make it more generic,
but in our case we don't have to worry about that
'cause we only use it in one place.
So here I don't need to task in this generic at all
because I know that this is going to be that specific type.
And I can say that my key here
is just gonna say like events or something.
It doesn't matter what it is,
as long as it's something that we can use.
Now, if I give that a quick save,
we obviously have quite a few errors
'cause this is currently returning a type of any.
We need to make it so it actually returns something
and we know what that is.
So let's create a state variable.
We can say that we wanna have our value and our setValue,
and that is gonna be equally useState,
and that's going to be an event array
'cause we know we can hard code this.
And then we're gonna parse it a function
as the default value
'cause we want to get our information from state.
So we can say here that our value,
which is just gonna be our JSON,
it's gonna be coming from localStorage.getItem from our key.
So we're just gonna say jsonValue.
There we go.
Then if our jsonValue is equal to null,
we just wanna return our initialValue.
So we're gonna get our initialValue
and return it like that,
otherwise we're going to try to parse this as JSON.
So we're gonna say JSON.parse,
and we wanna parse our JSON value
and we wanna parse this as an event array,
just like this.
There we go.
The important thing
is once we actually convert this to an event array,
one thing that's interesting about our date
is our date is actually going to be a string
because when you convert it to JSON
and then try to convert it back,
it's going to be a string.
So we need to convert it to an actual date object.
So I wanna map through each one of these events,
and what I wanna do
is I just wanna check that if my event.date
is an instance of the date object.
So instanceof Date, that's perfectly fine.
I'm just gonna return the event as is.
This may happen, but most likely it won't
because it'll be converted to a string.
So in those cases, I wanna take my current event
and I just wanna replace the date
by calling new date with my date that is being parsed in,
and that should work just fine
and we can just return this information like that.
So now essentially what I've done
is if we don't have anything on the local storage,
return to me my default value.
Otherwise, what I want you to do
is I want you to parse our JSON,
make sure everything converts properly,
put all of our dates back into date format,
and that's all we need to do there.
Down at the bottom,
I'm gonna make sure I return my value and my setValue.
And to make sure that it's actually going to be an array
of those specific values,
I can use as const
and that'll fix all those type errors up there.
Then finally, we can set up a useEffect
and that useEffect is going to run
any single time that my value or my key changes
and I just wanna set my localStorage.
So I'll set an item at that key,
and I wanna JSON stringify our value,
just like that.
So now you can see everything is there.
Let's add a new event, doesn't matter what it is.
And now if I refresh,
that event stays there just like I expect
and the date is properly being formatted.
If I removed all that date formatting code,
you can see it disappears
because it doesn't actually have a date,
it just has a string.
So that's everything
for that first step in the README.
Now let's go ahead and we can move on to the second step.
But I'm actually gonna skip this one
and move to the third step first
'cause the third step's quite a bit easier
and then we'll come back to the second step
'cause it's quite a bit more involved.
So this is just saying add a close animation to our modals.
You notice when I click to open it,
it has a nice smooth animation in.
And when I close it,
it just disappears completely.
Not super nice.
So to create a closing animation,
I can add a closing class to the modal
and that'll actually give it a closing animation.
So let's go ahead and actually try to implement that.
We're gonna need to do that inside of our modal here.
And whenever we call this onClose,
we don't actually want to close our modal,
we want to set it up to start closing
and then wait for the animation to finish.
So what we need to do
is we need to have some type of state variable
to determine if we are currently closing.
So we're gonna set an isClosing variable and setIsClosing.
And we're gonna set that state by default to false
'cause obviously by default we are not at all closing.
Then what I wanna do is I want to come down here
and if we're closing,
then I want to add that isClosing class.
So what we can do is inside of our modal,
this is where we wanna add our class.
We can come in here, I'll use that CC function,
make sure that I import that.
We know by default we have a modal class
and then we're also going to deal with that isClosing.
So for our closing
we're gonna add in the class of closing as well.
So if isClosing is true,
it'll add that closing class for us.
So that we need to make sure we change
anytime we change our isOpen.
So anytime our isOpen goes from true to false,
we wanna set this to true,
wait for it to finish closing
and then we can actually remove our modal.
So I need to be able to track
what the previous value of isOpen is
to determine if we went from open to closed.
So I can say const previous is open,
I want that to be a ref,
and this is going to be a Boolean.
And by default, I'm just not gonna give it any value,
it doesn't really matter
because we don't have a previous value,
it was never open or closed at any point.
Then we can use an effect.
And in our case, we're actually gonna use a layout effect
'cause this is actually going to change
based on our actual layout of things.
So we're gonna be using a used layout hook
to make sure that this actually works properly
because if we actually change our isOpen
and our previous is open,
we wanna make sure that this actually goes into effect
before our component re-renders
'cause it would obviously hide our component.
So our used layout effect is going to come into play.
Inside of here, we're gonna call a function
and we're gonna call this every time isOpen changes.
Now what I wanna do here
is I want to just set our previous is open.current
to our isOpen.
So it's just going to get me the previous value
of whatever my isOpen is.
And I wanna do that
after I check what my current value for previous isOpen is.
So here, if we are not open,
so if isOpen is set to false,
and my previous isOpen.current is set to true,
well, that means we went from open to closed.
And in that case I wanna set my isClosing here to true,
and then we can just make sure we set the value down here.
So this means that only when we go from open to closed
are we going to set isClosing to true.
And again, the reason I'm doing this inside a layout effect
instead of a normal effect
is because if we didn't,
what would happen is my isOpen would be set to false,
which means we would render null,
which means we would render nothing at all.
Then my useEffect would run.
My useEffect would come in here
and say okay, now set my isClosing to true
and then it would try to do the isClosing.
So if I put this in the layout effect,
let's just make sure it's checking this first
and setting my isClosing to the proper value.
Here what we wanna do is we wanna add another check
that essentially says if we are not open
and we are not in the process of closing,
then we return null
'cause if we're currently closing the modal
and playing the animation,
we don't want to actually remove it from our actual dom.
So just by doing this,
we should have the close animation show up.
So we have the open animation,
and you can see we have the close animation.
But there's a problem still,
if we look at our elements inside of our modal container,
you notice we still have our modal with that closing class.
That's obviously not ideal.
We need to remove that modal
from our actual modal container.
So to do that, we have an animation on this.
So we can just say onAnimationEnd,
what I wanna do is I wanna set isClosing to false
'cause my animation is ended and we are no longer closing.
So now click on this,
make sure I refresh,
click on it,
you can see it works fine.
We got the closing animation.
And best of all,
if I inspect my page and I go all the way down,
our modal container is completely empty
and everything will work just fine.
So that's everything we need to do
for this isClosing effect.
Now, if we go back to our README,
we have the most difficult part by far this entire project,
which is the ability
to have an essentially, view more button
'cause if we add a bunch of different events,
I'm just gonna spam a bunch of events into here.
Add another one, give it a generic name,
and another one,
just so we can see what happens when we have some overflow.
Add another one.
There we go.
So now you can see
we are currently overflowing the container.
And what this essentially wants us to do
is determine when we're overflowing,
hide the overflowing elements
and display essentially a button
that says view the more elements.
And it'll allow you to open up a modal to view everything.
This is quite difficult
because this is going to require
a lot of more imperative style code,
which is more difficult to do in React
'cause React is really based around that declarative model.
There's a lot of different ways
you could go around doing it.
I'm just gonna show you one way of doing this.
I spent quite a bit of time brainstorming on this
and I found this to be probably one of the better ways
of handling this.
So the very first thing I like to do
with this type of scenario
is create a custom component for it
because this is gonna be some rather complex logic.
So I'm gonna call this an OverflowContainer.tsx,
just like that.
Export function OverflowContainer.
There we go.
And inside of this overflow container,
essentially I just want to return something
that's going to wrap our normal event section.
And it's going to determine
what the size of that container is,
and if the elements inside of it are overflowing it.
And it's going to allow you to do different things
based on that.
So by default, this overflow container
needs to take in a list of items
that we want to actually render out to the screen.
It also needs to take in what happens if we have overflow
and how to actually render each of those items
'cause these items
are just going to be each of our events, essentially.
So it's gonna give us a bunch of different events
that we want to render out to the screen.
We need to have a function
that allows us to turn that event
into an actual thing to render.
So we're gonna create a function or a property
called renderItem.
This is just going to be a function that takes in an item
and returns to us a new actual dom we can render out.
Same thing here.
If we have overflow, I'm gonna have a function
that's going to get us
what that overflow content will look like.
This will be the button that says like plus two more
or something like that.
Finally, I'm gonna add a simple class for className.
And that's just because
I may want to add some custom class names
to the div that wraps everything.
In our case, if we look over here,
and we actually go
to where we have everything being rendered,
so we just scroll up little ways.
You can see here that we're wrapping everything in a div
that has a class name of events.
So I just need to make sure I propagate that through.
Now, inside of here is where I'm gonna return all of my dom.
So first, I'm gonna have a div,
which I'm gonna give a className.
And that className
is just going to be whatever class name we parse into here.
Pretty straightforward stuff.
Inside of this div, I want to loop through all my items.
So I'm gonna loop through each one of my items.
And inside of here,
what I wanna do is I wanna render out a div.
And the key for this is just going to be the index.
So we can set this to our index.
And up here we can say index as well.
If we really wanted to,
we could just parse in a function that's like getKey.
There we go.
We'll do that instead.
So now this can be getKey.
We parse it an item
and it's going to return to us a unique key,
just like that.
There we go.
And we could even make it so that getKey is optional
and we would default it to the index
if we didn't have some way to get a unique key.
But for now, we'll just do this.
Then inside of here, we'll close off this div.
What I wanna do is I actually want to render out our item.
So we'll say render item,
we'll parse it in our item just like that.
Now, I'm gonna make sure
we actually return this as a value here.
So I'm gonna just come in here,
a little bit of extra code
so it'll return that instance of information.
Then I'm gonna have another div
that we're going to have inside of here,
and that div is going to be for handling overflow.
So at the very bottom here I'm gonna have a div
and we're just gonna make this be our overflow.
So we're gonna say renderOverflow,
and this is gonna take in our overflow amount.
So if we are overflowing,
then I want to render this overflow section as well.
Now finally, to make this work,
we obviously need to put this inside of some fragments.
There we go.
So now this will render out our code
but obviously we have a lot of type related errors.
So in our case, let's come up here and type this out.
We're gonna say type OverflowContainerProps
is equal to a specific type.
We have our items,
and we know that our items
is going to be some type of array.
We don't know what type it's going to be.
It could be an event array, it could be a day array,
it could be a completely different type of array.
That's a perfect use case for generics.
So we're gonna make this a generic type of T,
and this is going to take in a generic type of T.
So we have essentially just a bunch of items
that's an array.
Then we have renderItem function.
And we know this takes in one of our items
and we know that the items are of type of T,
and we know it returns
to some type of React we can render.
So we can use ReactNode
to say that we want to render out some type of React,
and I'll make sure that I import this ReactNode.
We'll just come up here,
import type ReactNode from React.
There we go.
And the reason that wasn't working is I spelled React wrong.
There we go.
Then we have our render overflow.
Oops, overflow.
This is going to take in the overflow amount,
which is just a number,
that's how many things are overflowing us.
And it's gonna give us a ReactNode.
And then finally, we have an optional className,
and that's going to be a string.
And then we also have our getKey.
So getKey is again a function.
It's gonna take in an item
and it's going to return to me a React.Key.
There we go.
We can just change that up here to key.
So we have that being imported.
Now what I can do
is I can make sure I type this to this specific prop here.
So we can say we want this to be typed here.
We know that this takes in a generic T,
which means that the overflow container
also takes in that generic as well.
So now really the only thing we have left to do
is the incredibly difficult part
of determining if our container is overflowing or not
and setting this overflow amount.
So let's create some state
for our overflowAmount and setOverflowAmount.
I'll say it's equal to useState of zero.
Let's do an import.
There we go.
So that's being imported properly.
The next thing we need to do
is get a reference to our container
'cause we need to determine
if this is large enough or too large.
So we'll say containerRef.
And then here,
containerRef equals useRef,
has a null as our default value
and we'll get that from React.
And this is an HTMLDivElement.
There we go.
So now comes the really, really complicated part.
Essentially, what I wanna do
is I wanna loop through all the children
to determine what child is the first one to cross the plane,
to cross the barrier of where my container ends.
So in our case here, we have this being rendered out fine,
this one's rendered out fine,
this one's rendered out fine.
And boom, as soon as we get to this bottom one here,
it no longer fits.
So we obviously have an issue.
So to be able to check for that,
what we need to do is we need to set up a useLayoutEffect.
And again, the reason we're using a layout effect here
instead of a normal effect
is I want this effect to run
before my actual dom gets painted to the screen
because if we have an overflow,
I want to show the button
instead of having it flash incorrectly with the overflow.
And this here, we want to re-render
every time our items changes.
So first, let's import this useLayoutEffect.
And inside here, I wanna set up a resize observer.
But before I do that
I wanna make sure our container even exists
'cause if our container is equal to null,
obviously we can't do anything
'cause we don't have a container
so let's just return right away.
Then what I can do is I can set up an observer.
So we'll just say observer equals new ResizeObserver.
And this will allow us to check whenever something changes
and then we need to pass it a function
that takes in some entries.
And this is all the stuff
that we want to worry about observing.
So for now, I'm not gonna do anything with that function.
Now we can go below that
and we can say that we want to observe something
with our observer.
So we're going to observe our containerRef.current
'cause that's the element we want to preserve...
Observe, sorry.
And then anytime that this changes,
we just want to disconnect our entire observers.
We're gonna say that we're gonna disconnect our observer
any single time that our items changes.
There we go.
So now inside here we can determine
what is the thing that's changing.
In our case, that's just going to be our containerElement,
which comes from our entries.
We wanna get the very first entry from here
that's going to give us our containerElement.
And I want to get specifically the target of that,
the thing that is being resized.
Then we can check to see if this actually exists.
So if it's equal to null, we can return.
This shouldn't be true,
but if for some reason it doesn't have a target,
then we're just going to return.
Now, the next thing I wanna do
is I wanna get all of the different items
as well as this overflow section
to make sure that I render these out properly as well.
So what I can do is I can come in here
and I can add them just some data attributes
so we can select them.
So I'm gonna give this a data item.
I'm gonna give this a data-overflow.
So now I can actually select those up here
inside of this resize observer.
This is where it really becomes less React-like
and more imperative JavaScript-like.
And it's really difficult to do this in a React way
because if I was trying to do a bunch of re-renders,
it would be less performant
and it could cause like infinite loops, for example,
which are definitely not ideal.
So here I can get my items.
So I'm gonna say that my children is equal to,
I wanna get my containerElement.querySelectorAll.
I wanna get all of these which are HTML elements.
I don't really care that they're divs or anything,
I just know that they're HTML elements,
that's all I care about.
And they have the data attribute of data item.
So I wanna get all the elements
with that specific attribute.
I wanna do the exact same thing with my overflowElement.
So this is my overflowElement,
and this is going to be a single query selector,
and this is data overflow.
There we go.
Now, the next thing I wanna do
is I essentially want to create a loop.
So we can just say
for let I equal children.length minus one.
And when I is greater than or equal to zero,
we're gonna subtract one on each iteration
and then we have a loop.
Essentially, I'm looping through my children
in reverse order.
So I'm starting at the very bottom one
and moving my way up the list.
So the first thing I wanna do
is I wanna get the child,
which is just going to be the children
at that specific index.
And then I wanna determine,
is this overflowing my container?
So if my containerElement.scrollHeight,
this is essentially going to give me
the height of the container
as if it could scroll to be the full height.
So if I have like 10 elements
and it overflows my list by 50 pixels
and my list is normally a hundred pixels tall,
my scroll height would be 150 pixels
while the client height would be 100 pixels.
So for some reason, our scroll height here
is less than or equal to my containerElement.clientHeight.
Well, that means that
we don't have to worry about any overflow
because my scrolling section
is smaller or equal to the actual size of my container.
So we know that it is not overflowing at all.
So if that's the case,
I just wanna break outta my loop and not do anything else.
Otherwise, what I wanna do is I want to hide this child
'cause it's overflowing.
So what I can do is I can hide this child
by saying child.style.display,
and I wanna set it equal to none.
So it's going to be hiding that element.
Also, I wanna make sure my overflowElement is showing up.
So I can say my overflowElement,
I want to make sure that this has a style
and I want to remove the display property.
So here I'm gonna remove the display property from it
because by default it's going to be hidden.
And if I remove the property, that'll make it showing.
So up here, before I actually do my loop,
I wanna make sure I default to hiding my overflow.
So overflowElement,
what I wanna do is I want to get the style for this
and I want to get the display property specifically
and I set that equal to none.
And to do that properly,
I just need to do a simple if check.
So if my overflowElement is not equal to null
and then I can run that code.
There we go.
Perfectly done.
So if my overflowElement is not equal to null,
I'm going to hide it by default.
And also I wanna take each of my children,
I wanna loop through each one of them
and I wanna just make sure
that they're all visible by default.
So my style.removeProperty of display.
So what this does
is it's making sure my overflow section is hidden
and all of my children are visible.
Then I'm going to check, are any of my children overflowing?
If this is not true,
then that means my child is overflowing.
So I'm going to hide the child.
And then again, check, are we overflowing?
If we're not,
I'm gonna hide the child again
and check to make sure we're not overflowing.
And the reason here
I'm making sure my overflow section shows up
is 'cause if we're overflowing,
I now need to show my child my overflowElement,
which is going to shrink the amount of space
that we have inside this container.
So it just takes into account
the amount of space that this button will take up.
Also what I wanna do
is I wanna track the amount of things that are overflowing.
So I can say amount here...
We will just set it to zero, it doesn't really matter.
So by default, we have no overflow.
And then what I can do
is I can change my overflow down here.
So I can say
my amount is equal to my children.length minus I.
There we go.
So that's the actual amount of things that are overflowing.
And then finally, I can set my overflow amount
equal to that amount,
just like this.
So after I'm done with my loop,
based on whatever amount I have,
I'll set that as my overflow amount.
And that actually should be all that we need to do
to make all of this work.
Let's just make sure we have everything set up properly.
We'll do a quick refresh,
and it looks like obviously it's not quite working
but I don't think we're using
this overflow container anywhere
so let's actually use that in our code.
So if we have our events that we're rendering out here,
instead of a calendar event being rendered,
I want to render out an overflow container.
So here I have an OverflowContainer,
just like that.
And inside this OverflowContainer
I wanna make sure my className is events
and let's make sure we import this container.
So now we can see all the different props that we have.
For example, here we go, our items,
that's just gonna be our sortedEvents.
There we go.
We have our getKey,
which is gonna take an event
and we're just gonna return the event.id.
Next thing is our renderItem,
that's just gonna render out one of these.
So we're gonna take in an event
and we're gonna render out our CalendarEvent,
just like this.
We don't even need the key on here anymore.
And then let's clear out some of this stuff.
Let's see what we all have left to do.
We have to render our overflow as well.
So our overflow is essentially just going to be a button.
So we're gonna have a button, just like this.
And if we look over here at our index.html,
we can actually find what that overflow section looks like.
I believe it's called the view-more button.
There we go.
So this is what the button looks like.
I'm just gonna copy this over
so we have exactly the same button.
I will paste this inside of here,
make sure this says className.
There we go.
And this is going to take in an amount
and here I'm going to make sure I use that amount.
There we go.
Now, if we give that a save,
you'll notice we have some errors.
That's 'cause this should say events instead of event,
that should hopefully fix all my styling.
And now as you can see,
it says that we have two more events that are hidden.
If we delete one of these events,
you'll now see,
it says that we have one more event that's hidden.
And if we shrink and grow this,
you can see that different events are being shown up.
If we now delete this again,
you can see,
it's now saying we have zero more events showing up.
So we have a little bit of a bug going on
with this actual section.
So let's make sure we fix that.
It looks like in our OverflowContainer,
we're not properly hiding our overflowElement.
So if we just scroll down a little ways here,
you can see that this is set to data overflow.
This is using data overflow here.
So this overflowElement should exist, which is good.
It should mean not null here,
which means our style should set it to display of none.
But it looks like that's not quite working
like we expect it to.
I believe the reason for this
is 'cause as you can see here,
our containerRef doesn't actually wrap our data overflow.
So we're checking inside of here for the data overflow
while we need to check inside of its parent
for the actual overflow.
So here what I wanna do, my container element,
I wanna get the parentElement of that,
that could be null,
and then I wanna do a querySelector from that.
There we go.
You can see that it's fix the problem with that overflow.
If I add a new all day event,
you can see that works fine.
And as soon as we add another event,
we're now gonna get something
that says that there's two more being hidden
'cause they don't have enough space.
If I delete an event, you can now see they all show up.
But if I shrink down my screen size,
you can now see
that this resizing is working exactly as we expect.
Really, the only thing left to do to make this actually work
is to make it so when we have too many events,
we'll just call this one overflow.
And we know if we give this
an actual start time and an end time,
it'll actually show up below the screen.
I wanna make it so when I click this button
I can see a modal that shows me all the different events.
So our overflow container is essentially done,
we don't need to worry about that.
This is all working fine,
we just need to make it
so this button actually opens up a modal.
So we wanted to render out a modal inside of here.
So I'm gonna render out a ViewMoreCalendarModal,
we'll say CalendarEventsModal.
Close that off just like that.
Wrap this inside of a fragment so it actually works.
And then we just need to create this function.
There we go.
Let's just return "hi" for now.
There we go.
That way it just doesn't have any errors.
This ViewMoreCalendarEventsModal,
it should take in
all the different events that we want to render,
which is our sortedEvents.
It should take in an isOpen prop
and it should take in a onClose prop,
just like that.
There we go.
So we have all three of those things we need to parse in.
So here we have our events, isOpen, onClose,
just like that.
And I want this to be our ViewMoreCalendarEventsModalProps.
So I want to take in some events which is an event array
and I also just wanna combine this with my ModalProps.
So we can just say our ModalProps.
And we want to Omit the children property from it.
There we go.
That way it just always has the same props
as we're getting from here.
And here I can just say modalProps.
There we go.
So this can render out a modal instead
and I can parse it in all the modalProps we need,
as long as we parse it some children
it's going to work just fine.
We just need to make sure we give this the correct type.
There we go.
Now, here we're just gonna gonna do a very similar thing
to what we did with our other modal.
So I'm gonna go out to our other modal
and we essentially wanna copy out
what this header looks like
so it's gonna be rather similar.
I'm gonna copy this up.
I'm gonna paste this inside of here.
There we go.
So we're gonna have our modal title.
The actual title of this
is just going to be essentially our date.
So what we're gonna do is get our date
and our date here is coming from our event.
And what I wanna do is I just wanna get the very first one
and I want to get the date from that.
Now, to make it so
I don't need to put this question mark syntax onto here,
I'm just gonna come up here
and do a quick events.length equals zero, return null.
Just so we know that if we have no events,
don't actually render out this modal,
it doesn't make any sense.
So this is gonna render out our date.
Here's our close button that's working just fine.
And then we just wanna render out
all of our different events.
So we can do that.
Give that a quick save.
Below that we can create a div className is events.
And for each one of our events
we just wanna loop through it.
So we're gonna map through each one of our events
and we wanna render out a CalendarEvent.
There we go.
Our CalendarEvent just takes in our event
and a key which our event id.
There we go.
And if I make sure I close this off properly,
give that a quick save.
We have one error showing up,
and we just scroll up to see what it is.
We need to set our isOpen and our onClose.
So up here, let's create a state variable for that.
So we can have isViewMoreEventModalOpen,
setIsViewMore...
There we go.
So we can just copy this down as open as that.
And this will be set isViewMoreModalOpen to false.
Now give that a save.
That should work.
And when I click on this, it's not opening up the modal,
that's because we need to hook up our button here.
So button onClick, set isViewMoreModalOpen to true.
There we go.
And there you go.
You can see that it's opened up that modal.
I need to make sure this parses in a function.
So now let's close outta that.
And if we open an event, that works fine.
We open our modal, you can see it shows everything.
We click on one of these, it allows us to edit it.
If we delete it for some reason,
you can see now we no longer have that view more button,
which is exactly what we want.
But if we added something else in here,
you now see our view more button is there
and it's working just like we expect it to.
And if we go ahead and look at our README,
you can see that should take care of absolutely everything
inside of this section.
Again, don't feel bad if you couldn't get this.
It took me a really long time to figure this out in React
and to get it in a way that was somewhat clean,
it's really difficult
and just definitely something
that doesn't work super well in React.
But hopefully, you were able to follow along with this.
And the really nice thing about this project
is it showed you a lot of different things
such as how to do generic components.
We talked about how to do context with different things
and there's a lot of different things with TypeScript
that we talked about in this project that I really like.
So if there's anything you got stuck on,
I highly recommend going back
and just watching through that section again
and really trying to understand
exactly how I used TypeScript in that section
to really elevate my programming
and make my code even easier to work with.