
Build a Fullstack Music App with Next.js Player Controls State
A second version of this course released using Next 13+, but this course is great if you want to see another example fullstack project. We now recommend you take the Build a Fullstack App with Next.js, v2 course.
Transcript from the "Player Controls State" Lesson
[00:00:00]
>> Now we've got to start hooking up some of the handlers and keeping track of some of the state things. So there's a lot of things to keep track of but don't worry, I always wanna walk you through my thought process and why I think we need to keep track of certain things.
[00:00:14] There's also some gotchas going on with the state and functional components. That's some really advanced react stuff that I ran into, two years ago. I mean, I was just so stuck on it. I just did not get it. So I was really excited when I saw the same issue here.
[00:00:28] And I was, I get to talk about this, this is cool. So, let's do it. Let's hop into the code. So some of the things that we need to keep track of actually, let me go to the design first, is if we just look at the UI, this is how you can kind of figure out what you need to keep track of.
[00:00:45] We need to keep track of whether or not someone clicked the shuffle button. So we need to keep track of is shuffled true or false. Same thing for repeat, we gotta keep track of that. We also need to keep track of if you're playing or not that way we can either show the play button or the pause button, okay?
[00:01:00] We don't really need to keep track of anything when it comes to the next or the previous because they just do things when you hit them. They don't actually hold any information, they just respond to a click. We do need to keep track of the current duration, which ReactHowler, I think does a lot of that for us.
[00:01:17] We need to keep track of the value of the seekBar, so we can actually increase it, so it moves as we go. And we need to keep track of the song that's playing. So we know the duration of it here. Yeah, I think that's all the stuff we got to keep track of.
[00:01:31] So there's a lot of things going on there. And there might be some auxiliary state that we need to watch for, like the player load, did the song load, different things like that. But as far as the UI is concerned, those are the things that we need to keep track of, so we can actually represent them in the UI.
[00:01:47] So knowing that we're gonna set up a lot of that state. Next, so we'll just go to the top here of our component and we'll start setting some state here. So and also the other thing we wanna think about is what does the input for this player look like?
[00:02:06] Just to make it simple. I say, we should we should make a text list of songs. So like a playlist and then it also takes an active song, so we'll do that. So it takes the song that's currently playing that way you can click on a song anywhere in the app and it'll play that song, but it can also take a list of songs and it'll start from there and keep going.
[00:02:25] So, we'll do that. And then as far as the state goes, what we wanna do is we wanna keep track of whether you're playing or not. So, let's say playing or playing and then setPlaying. That's gonna be a state here. We're gonna default to true. So, when this component loads up, it's automatically gonna start playing.
[00:02:51] That means we're only gonna show this control if you have active songs. So you won't see the play bar at the bottom until you click on a player's until you click on the song and then it'll show up. So this thing is visual. It's always playing, you can pause it, but by default as soon as it shows up, it'll start playing.
[00:03:08] So we'll set that to true as a default. If you don't want that you can set that to false and you just have to click Play again which is annoying. You have to click play twice. You click on the song and then click Play. You don't want that.
[00:03:19] Or I guess you could just leave the controls at the bottom the whole time. But than you have to set up the play button to figure out what list of songs to play that, how would you know that? So, yeah, that's advanced, we're not gonna do that. Other next thing is we need to keep track of what song is currently playing in the list of songs that we got.
[00:03:39] So we can do that by just using an index, so I'm just gonna say index and we'll say setIndex. And we're gonna set that equal to use states here. For now with the save will always start off at zero, we'll start at the beginning of the Songs Playlist.
[00:03:52] It will play the first thing in the array.We'll go back and we'll change this eventually because if someone clicks on for instance, if I'm in here I clicked this third song. And this thing automatically defaults to zero even though I clicked on the on Start Song, which will send this whole playlist down to here, it's gonna immediately go back and play the first one cuz it starts off at zero.
[00:04:10] Instead, we want it to start here. So eventually we'll have to make this default to whatever the index of active song is, but we'll do that later. So just know that for now to start at zero if you run into that book, that's intentional. We also need to keep track of seek if you are seeking or not.
[00:04:28] So you have the ability to grab the thumb and seek. So we got to keep track on whether you're doing that or not, because if you are doing that we have to like dis disable certain things, so the UI doesn't freak out. So we'll keep track of that by saying cons seek, setSeek, And we'll say, use states.
[00:04:55] And this will be, 0, I'm sorry, this is the value we have to seek. There's another one that we're gonna use for setting whether you're seeking or not I'm way ahead of myself. This is literally where this is on the bar, like this right here. So it's gonna start off at 0 and then go all the way up to 100 or whatever the duration is, so we'll start that off at zero.
[00:05:17] I put 0.0, which obviously doesn't make any sense, but the reason I put that is because we're stepping by a decimal, by 0.1. So, but as far as JavaScript is concerned, I think zero is the same thing. So it doesn't really matter, just doing that. That way it looks obvious to an engineer when they come here, then ready to keep track of repeating or not, so I'll say, repeat, setRepeat equals you states.
[00:05:43] By default, we will not be repeating so, we'll set that to false. Same thing with shuffle, I'm just gonna copy to repeat, replace it with shuffle. Didn't mean to keep track of the duration of the song, so I'll say, cons duration, set duration. Not sure how much we'll use this, because the songs actually have durations on them but I think it might be helpful to keep track of it in a format that makes sense for the player and not the format that is saved in a database.
[00:06:22] So that's what this is for, that also be a decimal like that. And those are pretty much all the things we have to keep track of for now. Once we add the ReactHowler, we're gonna go down and add that and we'll add in some handlers. We'll have a bunch of other stuff that we need to keep track of as well as like res and things like that.
[00:06:42] But for now, this is pretty much all we need to get most of the UI done. So now I'm gonna uncomment this ReactHowler and we're going to start adding some things here. But this one's gonna require us to do a bunch of handlers. Basically ReactHowler, like I said, it's a JS live that's been wrapped by react, it doesn't have a visual output, doesn't show anything on the screen.
[00:07:11] It's just a library that controls the sound API built into the browsers. And because everyone in reacts loves components, they decided to give it a component interface. They could have also just gave us a hook interface and it would have been probably better. It would have made more sense.
[00:07:26] But it's a component interface and that's how we're gonna use it. I was gonna make a hook one and have that be part of the curriculum, but it was actually very complicated. And I was, we're not gonna do that. I'm that's just too much, we'll just use this thing.
[00:07:37] So props we're gonna pass in here is need to pass whether it's playing or not. Good luck, good thing for us. We have a variable for that now, it's called playing. So we'll, say, playing, playing. The source is gonna be active song prop, if we have it dot URL, songs have URLs according to our database schema.
[00:08:02] And then now we're gonna start adding some handlers to all of our things. So what we can do here is for ReactHowler we want to handle when a song is done playing, so we can do it on end. We can do all those different things. But instead, I think it makes sense to just start with some of the buttons that we created for ourselves down here.
[00:08:24] So let's go down to our icon buttons. Okay, the first thing we should tackle is the fact that we have a play and pause button here. So let's fix that. So what we'll do here is right, let's grouping the play and the pause button. Will instead do a conditional and we'll say, playing, playing is true, then that means we really wanna show pause, we want to show the Pause button.
[00:08:46] So I will put that here. If not playing, then we wanna show the Play button. So now we have that, if ReactHowler didn't freak out which it still is, so you can comment that out until we finish it, see what we got. You can see now the Play button is here.
[00:09:11] And we're good to go with that. Question?
>> Do you have use user reducer to eliminate some of the state?
>> The question is, do I ever use use reducer to eliminate some of the state? I've used used reducer once or twice ever. And it was because I had some amazing things that I needed to do and it wasn't so much that I had more state.
[00:09:38] It that updating the state was very complicated. It's typically when my state is not primitive values like Boolean of numbers. If my state is like objects, then I'm like okay, I need a reducer for this because updating it is ridiculous. So cuz that's when you start getting into immutability versus mutability, like setting a Boolean a true or false is easy.
[00:10:02] Setting a number is simple, but updating a nested property, that's really tough. So a producer actually helps with that. So that's when I would use use reducers. When the state is anything that's not primitive, and there's like enough that where it's to paint to update it. Okay, so we got that and now we can actually make the toggle work.
[00:10:20] So what we can do is we can say, well, when you click the buttons it'll toggle the state. So we need to make a handler for that. So I'm just gonna make one function that does both, and I'm just gonna say cons setPlayState. And it'll take in a value.
[00:10:40] That'll be true or false. And all it's gonna do is setPlaying to whatever that value is. So we got to setPlayState as it's gonna set playing. To wherever the value is that you pass in which will be a Boolean. Now we can go down to our play pause, and then on the Pause button, we're gonna do an onClick.
[00:11:04] When you click the Pause button, We want to say, setPlayState to falsecuz now you're not playing when you click it. I'm sorry, you must set it to true cuz you hit Pause. So you want to do the opposite of it, sorry, yeah. And then for the play, you wanna do the opposite of that.
[00:11:31] I got backwards there.
>> I flipped the the two values I set false and then true in the mind started working
>> Maybe I got it backwards again, backwards backwards today. Okay, yeah, you're right. I had it right the first time and then I was like, wait, this doesn't look right.
[00:11:48] There we go. Now we're toggling on our play and pause. Thank you for that. And I'm just gonna set that back to true for our defaults. Okay, so now we got our one button. The next thing we wanna do is we want to set up the state for the shuffle and the repeat.
[00:12:10] And basically all that's gonna do is just set those states to true and then it'll just change the color to be white cuz right now they're gray. And we know they're active when they turn white. So we're just gonna change the color when one of those are active.
[00:12:24] So let's go to the skip which is the, or the shuffle which is the first one. And what we'll do here is we'll basically just change the color of it. So we'll say color, and we can do something like this. We can say, if it's shuffle, is true, then we can say, make it whites, otherwise it'll just be whatever it was, which was great 600.
[00:12:47] So we'll do that. And we'll do the same thing for the last button which is to repeat except for it won't stay alert for the repeat state. And then to actually trigger that we just set us onClicks. So onClick for this button, we just want to basically toggle whether or not you're repeating and things like that.
[00:13:18] So we'll make some handlers. So let's say onShuffle and onRepeat. So const onShuffle, And then const onRepeat. They're basically the same thing, so all we're gonna do is just say setShuffle. And we could just come in here and say not shuffled give me the inverse of that. But setting state is asynchronous and you never are guaranteed to have the current state at the time if you're trying to use it to set the next date.
[00:13:50] So you have to put a callback in here, and then get the current state this way and then now you can just say, give me the not state of that. This guarantees that you have the true state at the time that you called it. It will probably still work the other way, but it's not guaranteed to work that way.
[00:14:09] So if you need the current state to set the next version of the state always do it in a callback, that way you get the true current state. And you won't be lagging behind a couple of render cycles of what the state was, which actually does cause a lot of issues.
[00:14:25] Same thing for onRepeat, except for we won't setShuffle will set repeat. Okay, onShuffle, onRepeat. So that will go down to our shuffle right here. We'll do it onClick, we'll say onShuffle. And we'll do the same thing for repeat. Except for onRepeat, there we go. All right, so now if I click these, you see shuffle highlights, turns white, and repeat, same thing.
[00:15:11] Good pretty good, good pretty good. So that was one issue, this wasn't the one that I said that I ran into we'll get to that. But yeah, always again, if you need to use a current state to set the next date, you need to always do it in a callback in a setter function, you will probably run into some bugs.
[00:15:30] If that components doing a lot of setting somewhere or you'll get like, some weird stuff happening and then you'll try to fix it with a use effects and you have a race condition and you'll eventually do a set timeout. And you'd like that salted. [LAUGH] But all you wanted to do was this.
[00:15:47] So if you ever do this set timeout 01 of your functional components, it's probably somewhere because of this. That's waiting on the next render, seems like the play state wrapper function is redundant. Totally redundant, you can do everything in a callback. But the problem with adding anonymous callback, like we did is that it's gonna be created every single time which is gonna cause renders.
[00:16:10] So typically, you wanna make a function yourself and use the use callback handler to prevent that from happening as optimization. This was just a precursor to that. So that's just a habit that I always do. Why you setPlayState but for the rest onRepeat I asked about the naming.
[00:16:24] Okay, so we used setPlayStates versus the two different things. That's because setPlayState uses the same setter function as far as the hook, whereas onShuffle and onRepeat, use two different setter functions. And I didn't feel like passing in a string to one function to determine if you wanted to set a shuffle or if you wanted to set a repeat.
[00:16:49] I just didn't feel like checking strings, so I didn't do that. Didn't have to do that with play state because it's always setting the same setter function.