Let's Play With Realtime (Using Supabase), pt. 2
with Jason Lengstorf
Join Jason to have some fun with realtime coding. Let's finish the app we started together last time with Supabase.
Resources & Links
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
JASON: Hello, everyone, and welcome to another episode of Learn With Jason. Apparently, I got my deejay voice on today. So, I am excited to be doing another solo episode. We've got a lot of fun stuff coming up on the show, had a little schedule reshuffling, and that ended up with me doing this one on my lonesome, which is actually kind of a blessing in disguise, because the last time we did a solo, I was playing with a multi screen idea for what a realtime talk would look like, like what would it look like if I had a presentation going, and you could have a second screen and interact with that in a way that would lead to something happening on the main presentation. So kind of live polling, live reactions. Kind of like what we do with the stream here. The stream is actually a second screen experience. You interact with the chat window, and the second screen, the overlay for my stream, is responding to that. So, we just saw some boops drop. Love that. So, I want to try to build is blender still running? No, blender is not still running. Also, how fun was that episode? Did you all have fun with prints? Any chance I get to hang out with Prince is a great, great opportunity. So, you know, like I said, the overlay on this stream, what you're looking at, all the framing around me here is this is all a website. It's a web page, right, and that has a real time API whatever. It's got a reactive layer on it. And whenever events happen, like subscriptions, or using a certain emote in the chat, running a command, any of those things, all of that leads to stuff happening. You know, you get overlays, you get the boops dropping. If you use enough of the party corgis, you get a stampede. All of those are fun ways to interact with the stream. So, how can we move this forward and bring this into different environments? This is an idea we had, because we're starting to get back to in person conferences, and I love speaking in front of people. It's really fun for me to get up on stage and have a conversation with folks, but I'm realizing that I miss the interactivity of Twitch when I'm not on Twitch. I really wish there was a way to have input from the audience while we're speaking. I think it gets too chaotic to do call and response. It can be off putting to introverts. Nobody wants to yell if they are an introvert. Not everybody wants to put their hand up, nobody wants to get called on stage usually. So, you find yourself in a situation how do people participate without prioritizing those folks who don't mind being big and loud. I think the answer becomes how do we make your phone into a second screen, or your laptop, whatever device you're using, into a second screen that will allow you to participate by pushing buttons. So, you get to be part of it. You get to influence the talk, but you're not required to really put yourself center stage. You know, you can participate without being perceived. And I know for a lot of folks it is important not to be perceived. So, this to me is a really interesting opportunity to build out better interactivity into real world spaces. How do we make a conference talk more interactive? How can we make even internally, could we make internal AMA sessions with the CEO, or other leadership, could those be more interactive without requiring people to grab the mic and ask a question. Those are the sorts of things that I'm interested in, and I was having a conversation at Render ATL, which was a really fun event. I was talking to James Q. Quick and a handful of other folks about what would those second screen experiences look like. We had an idea it would be funny if you think about how the Nielsen ratings system works, or how focus groups in TV shows and other types of media, they will have people watch the show, and they have a device in front of them. And as they are watching the show, if they are feeling good about what they are watching, they turn it to the right, and that indicates I'm enjoying myself, this is fun. If they are not, they turn it to the left. It's kind of a dial. So, in this case, they do it by kind of setting an extreme. Oh, I hate that. They crank it all the way to the left, right. Oh, I love that, crank it all the way to the right. If they are saying this is boring, they crank it a little bit to the left. I would like to get there. I think it would be fun to get that level of nuance, but for right now there's a general reaction. If I make a claim, can people say I feel good or bad about that claim? I gave a talk recently, where my click baity setup for the whole talk is full stack developers don't exist. Then I spend the rest of the talk explaining how full stack developers exist. Can you do something where the audience can say, oh, you just made me angry with that, I want to disagree with you, and, you know, that's the sort of thing that I think is really fun. Learn With Jason Discord. I don't know. So, the thing about a Discord is that I feel weird making a space that's predicated on me. I don't know, this is an interesting relationship with attention, right. I feel let's get philosophical, chat. You thought we'd see a rabbit hole on CSS this time, we're going to rabbit hole on humanity. I have this sort of I like the idea of creating community. And I like the idea of building a collective of people who are working to improve themselves and learn more and things like that. And I love the general concept of groups of people coming around, coming together around common interests. This is what makes me love the web dev community, why I'm a part of the communities I'm a part of. Where I get squeamish is when it becomes a leader/follower dynamic, because it's a short hop from leader/follower to cult. And I've seen this happen a lot in even in the development community, where someone kind of emerges as a thought leader, and before too long, and chat, I swear to God if you start dropping names, I'm going to be upset, so please don't do that. If you find yourself in these situations where people kind of emerge as leaders, and then you see groups form around them that kind of take everything they say as a fact. And what I get uncomfortable with is that idea of like I don't want to be a mentor with mentees. I want to be someone who has like minded people who are kind of co generating things together. Yes, Ben, yes, that's actually I love that. I used to have this on a T shirt before I wore the T shirt out. Hero worship kills communities. And that's at the crux of all of it. I have a big also, Tatyana Mac, she say near me, I should say hi to her. At the bottom of all of it, right, the thing that I fear more than anything is becoming somehow separated from the community. And I think that is a really it's a weird position to be in. And you can see it with some of the folks like just feeling like they can't participate in the community anymore, because everything they say has so much weight that it gets dissected and used as a weapon, and taken out of context, so they feel they can't really talk, because if they talk, it's taken as too serious. They can't joke, they can't, you know, you can't make an off handed comment. You have to deeply consider what it is that you're saying and what the impact of that is going to be. And, so, I find it a little all of this getting around to the original point. I'm not opposed to the idea of a Learn With Jason Discord, but I am nervous about introducing spaces that are built around me. So, if it's built as a community thing, it's super onboard. If it's built as a Jason platform, I feel weird. I haven't figured out how to do it without feeling like it could cross that line to hero worship. And, you know, one of the reasons that I love this show is that it doesn't feel that way. It's very you know, we're not acting like there's a separation. I'm working with the community. Y'all are working with me. And that co creation is a big part of that, which is, okay, full circle. We're back to this idea of participation and creating things together. So, from my standpoint, the goal here should be co authorship, co ownership of what's being generated.
So, I find a community type Discord. Yeah. I mean, yes, I'm definitely open to the idea. We should maybe talk about this a bit and see how that could potentially work. Because I do I have a Learn With Jason Discord, but I currently use it to coordinate guests, and I work with Aiden, who manages a lot of the web properties, and I work with Chris, who does the editing and the highlight videos and stuff like that. So, I use it in that way, but I haven't opened it up as like a general public thing. So, if there is a way that we could do that, that doesn't feel it's centered around me as a personality, then I would be very happy to create a community space. But, yeah, let's talk about that, chat, if you're into it. Maybe, I don't know, send me a DM or hit me up on one of the other Discords I'm in and we can talk about it. Okay. So, let's talk about what we're trying to accomplish today. So, what we did last time on this is we built out a screen that is, like, a slide. And we did it kind of quick and dirty, centered some content, and the slide makes a claim, is a hot dog a sandwich. Then we have a second screen, which is buttons. How do you feel about what is on the slide right now. And one of them was a yum face, yeah, hot dogs are a great sandwich. One was an angry face. How dare you call a hot dog a sandwich. And what we're trying to build out is the ability for the slide that says is a hot dog a sandwich to reflect the input from the crowd. The way we'd done that was put a meter, and this is where we fell into a rabbit hole, having a meter or two meters, you know, was it a select, whatever. That maybe starts in the middle, disagree, moves to the left. If they agree, starts to move to the right, and that puts us in a position where we have some form of feedback. A good prototype, something we can start with. So, let's dive into I don't know, let's find out. Let's go into yeah, let's just go into the programming mode. All right. And from the programming mode, I'm going to pull over the homepage of the site here, and we'll start like we always do with a shout out to our live captioning. We have Ashly here with us today from White Coat Captioning, who is taking down all these words, and making it more accessible to more people, which I very much appreciate. And that is made possible through the support of our sponsors, Netlify, Nx, Backlight, and probably as of last week, New Relic, who we finally have everything lined up, and we're ready to go. So, we'll get that set up, and thank you very much to New Relic for coming on as another sponsor. That really helps do all sorts of fun and ambitious things. And today you're dealing with me. Let's talk about what we actually built. So, last time that we were here, we were working on this realtime box. And let's do just a bit of a code review. Get this up here. Make sure that we can actually see the whole screen. There it is. So, what we built is we built two apps. We have the main talk, which is I think I used veet and just kind of generated a really simple React app. That gives us our let's see. And, so, we put together a slide that says hot dogs are my favorite sandwich, and then it has the hot dog emoji, and then we have the responses. And, so, how the audience is feeling, and then we got into our range elements and what the current sentiment was, and it starts at 50 and it will drop if it's people are disagreeing, it will raise if people are agreeing. So, if we run this, let's see, I'm going to have to look at how we set this up. We're using Nx here, Nx is also one of the sponsors. If I look at how to run a package, because I always forget this, we want to run Nx serve, and I think we put the name of the package in, which would be main app. So, let's run npm start. Let's see what would happen if I just ran npm start. It runs Nx serve and goes to the main talk. So, starts with our default thing. That brings us to our local host 4200. Here's our default set, and what we want is for this to move back and forth. You know, there are a lot of things that we can do with this. I'm not going to try to style this anymore, because I don't want to waste anyone's time. I want to get to the realtime part, because we didn't get that last time. We're going to leave this good enough for now and try to use our second screen to get this moving left and right. So, to do that, what we ended up doing was we set up a Supabase database. And we're using that for authentication and for a handful of other things, so that we can get our sign in. Wait, this is the wrong one. This is second screen. Let's get into the main app. The main app is going to pull in the insert, right, and now I did some of this wrong, and Jon Meyers was actually really, really helpful. So, let's take a look at what he did to make this better. This is Jon Meyers. And he very kindly saw that I was having a little bit of an issue getting all the things I needed working, working on Supabase. And he put together a whole video on this, which was wonderful. And he also set up a he set up a thing, what's the thing I'm trying to say? He set up a pull request. And that pull request will let us do some really good things with Supabase, and I don't have to figure out how this works, because Jon told me. This is kind of like having a bonus guest. I'm going to drop this into the chat. I forgot to drop a bunch of things. Here's Jon's Twitter, that's in here. Here is the homepage, if you want to follow along with the live captions. And let's actually pull up the repo, as well, for anybody who wants to see it. This is the repo as it stands today. So, you can go and take a look at what we've built so far. And I'm going to go back to the repo and let's look at this pull request, because it's going to let us walk through what should have happened. So, a couple things, we target only the sentiment table, and I can go to Supabase and log in, so we can look at our database as well. So, here is Supabase, here is real time talks, and we have our database, auth requests, all those things all working. And if I go and look at a table editor, we should have sentiment, and sentiment has a bunch of oats in this. When John was testing this, he agrees on a sandwich. So, that should give us actual data we can work with. And if we look in here, that's this table. So, what we're trying to do is get the any time someone inserts, which is voting, you click a button, it's going to insert a thing, we want to be able to do something. But to start we want to make sure we're getting that payload. Then because we don't want this to register multiple times and have multiple subscriptions running, any time this component unmounts, we return this remove subscription. So, that's thing one. All right, mark that as view. Then let's go look at in the second screen, cleanup to our auth, which is good. We don't get a user session, because we're using third party providers. We have to get those separately. So, that is why Jon removed these here. And then we can check if there's an error, we will log that error message, otherwise we don't really do anything at all. And once we hit the use effect, we're going to load the user by using the Supabase auth object, and that comes in from the client that we create. We can look at that in a second. And when the auth state changes, we're going to set the user, or set it to null, and I'm just going to assume that's good enough. Looks like I forgot to include this dependency array as empty. That would have meant any time you use this component at all, it would re auth, which we didn't need. Then we are going to insert, and it looks like I was using an array. I didn't need to use an array. So, we just drop in the one value. Good, good. And that will give us our thing. So I'm going to approve these changes. And submit. And let's squash and merge. Supabase. All right, now, there's another thing I have to do, which is turn on replication. And in order to do that, I need to go and look at what let's see, where did he tell me how to do this? He told me let's see if I can remember. It was in here, database functions (reading to himself) was it in databases? Replication, yeah, yeah, yeah. And we're going to set up sentiment, good. And now it's set up. So, that should work. I think I could potentially turn off some of these, but I'm not going to stress about it. And then if I go out here, and I git pull, good. Then I am going to just run again. Okay, come back out here. Let's open up our console. Make this a little bit bigger. And now what I want to do is let's take one on this side. And let's take this one on this side. And I want to add something to this database. Let's see, that's not going to work. I have to make this bigger. So, I'm going to insert a row. And we won't create an ID. We won't mess with that. And I'm just going to put in let's go with bad, and we'll view data oh, do I need to I'm going to need to get a user ID. Actually, why don't I go out here, and I'm just going to confirm. I'm just going to get a user ID out of here. Can I just click it? I would like to click this, please. There we go. Now I can come back out. Insert. Bad. Drop in a user ID, and save. Okay. And, look, aha! We got new data, and it says that we went value bad. So, this is good, because what this means I guess this is a bad, ha ha! What this means, now whenever we get an update, we should be able to pull in one way or the other to just kind of show what the general sentiment is. Why does it call sign in twice. Good question. Can you clarify what you mean? Let me pull up our changes here. We're going to go to our second screen, look at the source, and we're looking at the app, specifically. So, this one here, the sign in with GitHub, is what we want to run when you click the button. And that will actually, like, do the auth flow with GitHub. I should probably just start this. We'll try it out. That starts the auth flow, we'll reload the page, and what that does, it calls the is this user logged in and sets that user. And then whenever the auth state changes, so if you log out, which we haven't implemented that or anything, so we wouldn't really get it. But, you know, in the future when we do implement that, this would update that. But we only call that sign in once, I think. Yeah, it was probably the diff. And my diffs look weird, because I use the enhanced color blindness pattern, so you can see my diffs are orange and blue instead of green and red. I don't have red/green color blindness, but I do find this is easier for me to differentiate, and I just dig it. So, that's why that's happening and why you see the sign in happening multiple times. Yeah, okay, so, we have the ability now to react to this. So, let's do it a little bit. I'm going to try to do something here whenever we get out of the second screen and into the app. So, what I want is I want our initial state how are we doing on time? 25 minutes in. All right, we got some time. We got some time. Let me see just a second here. Sorry, one second. I got to respond to this text.
How professional is this? Least professional. (Reading to himself) not until 11:00 a.m. Best five seconds of television I've ever made? Okay, sorry about that. So, let's do I want this to move. So, we've got a default value, and we're actually going to do a controlled component here. So, I want the value to be set, and that value needs to be current sentiment, and I think the way that we will do this is we'll get rid of the default value, we'll set this value, and then up here, I'm going to set current sentiment and set current sentiment as a use state, which we'll pull in from React. And we'll default that to 50. So, then what we can do is we can let's see. The payload that we got, hopefully it's still here. It is. So, we will get a payload that includes new and old. So, I'm going to check if it's new. Are we going to check the new? We'll do const value equals payload.new. So then inside of that we want to get the value oh, no. What if we just do that? That seems we'll whatever. You know what, we're prototyping. It doesn't need to be perfect, right? So, I'm going to get that value and say if the value equals wait, we can do it this way. Let's do set current sentiment to current sentiment plus equals is this too junkie? This is junkie. Don't do that. Even in a prototype, never do that. Don't be like me. Be better than me. Let's do an increment value. And that will be if the value is bad, we'll do 1. And if the value is good, we'll do a 1. And, so, we're going to do a value plus equals incValue. This should work, but just in case then we'll drop this one in here. Cannot set what? Okay, yeah, that's why. That makes sense. So, we're going to do a plus. So, it's either plus 1 or plus 1, which means it goes 50 or whatever. And the other thing we should probably do is just a quick check, where we make sure it's within range. So, we can do this is going to be gross. Math.min of 100, or math.max no, math.min of 0 in this value, and math.max of 100 in this value. Ta da! And that should put us at a it will be at minimum 0, at most 100. In line setter function, it could definitely do that. I think this will work. We can refactor later if we wanted to. So, let's maybe yep, yep, yep, yep, that could definitely be part of the problem. I don't think it will matter in this case, but let's run it. You provided a value prop without an onChange. If the field should be mutable I don't want it to be mutable. Or read only. I can set it as read only. So, let's set it as read only. And now it should stop yelling at me. Try that one more time. Good, okay. So, then what we can do, and basically what I'm assuming is any time you switch to this slide, you would want a new setup, right? So, this should be 50, although that definitely doesn't look like a 50, does it? Kind of looks like a 40. Value 50, though. Oh, min is 10. That's what I did wrong. Let's set these back to what they should be. I was trying to get clever last time. So, this is our 50. And we've got a min of 0, max of 100, that's set in both places. And we can now try to make this let's try to make this go down. So, I'm going to set let me go off screen, I'm just going to hit my clipboard. Come over here. Show the clipboard. All right, here is going to be our user ID, and here's our value. And I'm going to say bad. Let's save. Okay. So, it did kind of did what we wanted, but definitely didn't exactly work. So, why did that cause it to completely give up on us and go to zero instead? Oh, I know why! I did these backwards. Min and max should be the other way around, because we want it to be if zero is greater than, we want it to be zero. If 100 is less than, we want it to be 100. I got these backwards. That should fix it. Let's go back out and try it again. All right, let's do this one more time. I'm going to insert, and here is our saver. Got a little bit worse. One more time for just to double check. Go bad. User ID. Didn't do anything. So, it did get both things, but our value is still set at 49, which means something didn't get set.
If the value equals bad, we want the current sentiment, plus the increment value. Did I mess something up? Yeah, that's fair. Fair, fair, fair. What's up, Brittney? Thank you for the raid. Hello, hello. I'm bad at code. So, let's add some more console logs, because that's how I debug. We're going to go with value, new value, and let's find out what this thing says. Because the only thing I can think of is this is somehow completely re rendering, which would be weird, but it's not out of the question. So, we're at 50, right. Now, as I add things yep, I did. So, we're going to do a bad. So, if I add another one, we say bad. Should be 48, but it's not. Let's go to the console. We got a bad, an ink value, and we messed a thingy up. So, the function current sentiment is locked to first mount because of the bracket, but this subscription shouldn't be. Right, this oh, I see what you mean. Let's do that. Still a little confused by what try this one more time. Everybody bear with me. We're going to get this thing right. We got this in here, going to do one of these. Going to do one of those. Do one of these. Huh? Okay. Current sentiment is 50. Now, what I think y'all are saying, and somebody is probably screaming this at the computer right now, is that I am resetting this whenever it returns. Okay, okay! I get it. I get what happened. Let's see if I can explain what happened. So, the subscription being set up here is only happening on the first render, because we weren't passing this before. Now what I'm unclear on is why no, I still don't get it. Still don't get why that works. Hmm, hmm. Oh, y'all. Okay, I do get it. So, what was happening and what everybody has been saying, is it resubscribes each time it changes, which is not what we want. What we actually want is for the set current sentiment to be here. So, we want this to do it this is why everybody was saying that. Okay. So, let's walk through what just happened, because this is super confusing if you are not if this isn't clear to you what's happening, that's okay, because it's barely clear to me, and I've been doing this for 20 years. So, because we weren't tracking this, which is actually probably the right idea. We only want to register the subscription once. What was happening here is this is a closure. So, we were saying when you register this component, create this function. Now, this at the time of the first mounting, is set to 50. At no point after we change this value are we changing this setting to something other than 50, right. So, what's happening because we're not reregistering our subscription on every mount is we're basically saying use 50. We're hard coding it by putting this inside the closure. So, if we go here instead, and we say currentVal, and we just move all of this in here, right, and we, you know, we can do this however we want, but we're going to do it like this, and then we'll drop one of these. And that's it, right. So, we'll get that. Get that out of there. And then we need to return the new value, that's how that actually works. And we're going to use currentVal instead of current sentiment. Now it's not yelling at me, because we're not using anything that would be stuck in the closure, and we'll get the current value of the sentiment every time it goes. Okay, now we're setting up our closure correctly, using the set current sentiment. I'm doing this from memory, so this definitely might not work, but I think I'm in the right direction at least, so that, I guess we'll see. Let's try it out. No, my clipboard. This one. And then we need bad. Let me copy/paste this again, so we have it available. I'm going to save, and it worked. Okay. Let me refresh the page, because I think I might have just used a cached version of that. Did I save? I did save. And now drop this in, and new value is 49, current value is 50. And assuming I did this correctly, we will see the current value is 49 now. Okay. All right. So, that basically what we just did is we got all of that stuff put together, the closure is working correctly, and, yeah, as the chat is saying, this is the pitfall of useEffect, it is very smart, but you got to get your head around how this stuff works, and you need to have a good understanding of things like closures and what happens when you do that. But now that we've got this running, we can move to the second part of what I want to do, which is getting the second screen working. So, we can save this. I'm going to actually commit this now. Say get realtime sentiment update working. And I'll push that, in case anybody wants to look at it now. Again, that's all being pushed to this repo here, the realtime talks repo. So, yeah, yeah, yeah. It's a sharp knife. Yeah. That is true, Matt.
So, we've got our presentation side is doing what we want. I probably should have tested that it went up when we did good, but you know what, I'm going to trust I did do that kind of math at least. And now we want to get the second screen running. So, let's go into our second screen, which I think I can do like this. And so what this is doing, under the hood this is going to run the npx start, or npx serve, and I'm using this to escape npm, and passing an argument to the Nx that will run the second screen command if I'm remembering correctly. Yeah, Nx. So, now if I come out here and reload, we have this ability here. And, so, I need to actually, I might not need to do anything. I think John might have done this for us. Let me see. Apparently I'm logged in. So, let's see if I click on this. Is it entering anything? Refresh. It is. Okay, let me click this three times. Boom, boom, boom. All right. And then we're going to refresh. And now there are a bunch. Good. Jon just fixed this for us, it just worked. So, let's go and look at how that is functioning. So, that's under second screen. And we've got our app, and let's take a peek at how this works. So, inside here we haven't styled anything. If we get let's see, we got 45 minutes left. So, if we get this all the way running, maybe I'll get it and style this, but otherwise I'm going to get this deployed so you all can play with the sentiment and we can watch it happen in real time. And I'm realizing we should probably have some kind of a unique combination of, say, the slide would have an n ID. We'll make that a stretch goal. So, the first thing I need is the ability for the slide changing to update what the options are on the second screen. And after that we'll make sure people can only vote once so somebody can't set up a script and vote a thousand times and that kind of stuff. We can figure that out later. Right now what we've got is the Supabase auth. Let me clear the application cache so we can see the log in flow. I'm going to do all of it, I think. I can sign in, it's going to have me sign in to GitHub, and apparently I already authorized the app, so it doesn't make me hit the approval thing, but that makes me in, and now I'm logged in as me, so I'm using my own user name. If we look over here, this is my user ID, and this is Jon's, because he was doing the tests earlier. So, this is what we're getting as a unique ID. That way if I'm set up oh, that's a cool idea, Tony, to hook this back into the Twitch API and do an action like a stampede. Yeah, there's some fun stuff we could do. We could set triggers, where if the sentiment drops below a certain thing, it plays sad trombone and I'm forced to move on to the next topic, right. There's some really fun things that we could do to make this more interactive and stuff like that. But for now let's see if we can get this to run, which I think we can, and the way that I'm going to do that is how do I want to do it? Let's see, we got this part running, and that's adding things. Now we just need to get both of these running together. So, first and foremost, I think we can deploy it. So, let's get this running on Netlify. And the way that I want to deploy it is if we look into the deployment, I think, I think this will just work, because we're going to build. So, what happens when I do a build? I'm going to do npm run build is it called main talk? Main talk and we've got second screen. Let's build main talk. And it's doing a production build. And it apparently knows all the things that we need. So, it ran that, and it did it in Nx cloud, which is kind of cool, which means a lot of these resources are cached and stuff like that. Actually, we can see. Watch. Look how fast it goes the second time around. So, that's really nice, and that means everything got output to got output to dist/apps/main talk. That's great news, because what that means is I can then do one of these at least. No, here's how I'm going to do it. I'm going to do it like this. I'm going to the app.Netlify.com. And hopefully it's in mine, it is. I'm going to create a new site. Add a site, import an existing project from GitHub. Can you tell I always use the CLI for this? So, let's get into our realtime talks. I have so many repos. Here we go. Then we're going to deploy our main branch, that's the one. Our base directory is set, and then our output directory, what we're actually going to use here is the one that we found. It was dist/apps/main chalk. So, dist/apps/main talk. So, a little bit of config here, but I'm feeling pretty okay with it, and I'm going to deploy. I probably should have named this. I'll name it now. So, we'll say realtime talks then if we go back to the deploys, I think this will already be done. Let me go close to done. And while we're waiting for that, I'm going to set up the other one actually. Set up the second screen, and the way we'll do that is go back out here, go to sites, add a new site, import existing, go to GitHub, do the auth flow, come out here, search for realtime. Go through my 600 repositories. Let's get realtime talks. And for this one, we're going to do second screen, and that means it's going to be in apps second screen. So, that is how we deploy multiple apps from the same Nx mono repo. I should click that. That's actually like a question we get a lot, how to do that. That little section of how we just did this. Let's go realtime talks second screen. Save that. And we go out to here. We've got the realtime talks main screen is deployed. So, here's one. I just want the main one. Here's our main talk. And then go back to the second screen. So this I think should all be working, because we're using public keys. Do what we want. Then here we want the second screen. Here it comes, completed, building, and live, I think. Let me get back to the top and click on the main app name here. Okay. So, chat. At this point, you should be able to actually vote on this, right. So, let's give it a try. I'm going to make this one way smaller. Stick it over here. And I'm going to take this one and stick it over here. And let's log in. No! I got to fix my path. So, that means that I need to go back to my GitHub. Is it this one or Supabase? I think it's Supabase. Do I have another? This one out here, I'm going to go to authentication. Settings. Site URL, and I need to update this to be our actual site URL here. Oh, it's going to be second screen. That's the one, okay. So, now that I have that. Use as an allow list, is it possible for me additional URLs. Do I need to set a redirect in here? Feeling like a made a mistake. Configuration settings. Do I need to fix this in the code itself? Where's my log in. Let's go into the second screen. Source, app. And when you log in, we're not providing anything, so theoretically speaking, I think it just needs to be that default. For the sake of this, I'm going to put this up here, and we will, if we need to, post 4200. I'm going to save. Going to come back out here. Come back here. Reload. Sign in. There we go. Okay. So, now I have gotten these back. Oh, no, they always increment. Did I spell something wrong? Value good. Oh, yeah, we hard coded. So, that's why that's happening. So, then what we need to do is figure out. Okay, so, major hurdle here is overcome, which is that if you all go to this realtime talks endpoint and log in, you can now affect this thing. Yeah, I do have strappy content. Let's go here. I will show you my favorite thing. If you come here and hit command k or hit the search box. We have a suite of content on Strapi. I don't know which uses next.js, if any of them. That's a good question, but do have Strapi content. Strapi is very cool. I would definitely recommend taking a look at it. Very configureable, fun to use, very flexible. Chat, are you voting? Taking my hands off the screen, off the screen. Yeah, yeah, look at it go. This is great. We're doing realtime interactivity. Let's go and fix this to where, actually, this will be a good test. Won't go higher than 100. Is it throwing errors? Yeah, yeah, this is great. Won't go higher than 100. At least the basic math guards that we did, after I broke them the first time, work now. So, next what we want to do is not run this as a hard coded value. So, instead, we want to figure out which button was clicked. And to be completely honest, I don't know if this works, because I haven't checked for that before. So, let's find out, right, let's see what happens if we do this. Because what we could do is we could do like a create vote and then pass in a value of good or bad and kind of set these to be like on clicks. But I kind of like the idea of doing it on submit, but let's find out the form data. We're going to say data and get a new form data, event target, which is the submitted form. And then I want to why don't you like oh. FormData, then console.log of our form data. And I want to see if the button name comes through as part of our FormData. So, let's go back out here and I'm going to npm start second screen. How are we doing on time? 35 minutes. We're making a lot more progress this time. I guess it has something to do with me not just getting into the CSS. Okay, let's try this. Here we go. Now we got our form data. And that is prototype form data, entries, can I see I'm going to have to get values or something, right. Equals FormData.entries. ToArray. That's not going to work. Why don't you like this? Entries does not exist. Yes, it does. Form data, entries. Name is entries. Do it get all and see what happens. Also exists. That's the argument. Get all name. Boo. Let's see if I name this. If somebody clicks it, let's go with good. If that ain't programming, what is? Let's try again. Good, bad, array 0. Both of these come back empty. So, that's not going to do what I want. So, I think what I need to do instead is a kind of let's do this. We're going to function create good vote. And that will just do an event. One of these. Do one of these. Prevent default. And then inside of it we're going to we want to create vote good. Then we're going to change this to be value, to be a string, and then I don't need to be any of this anymore. And instead I can just set the value. Just going to clean this up a bit. Do all of those, do all of these. Now we've got the good vote, and let's create a bad vote. I'm going to do it anyways, just in case we want to wait at that. And then we'll get rid of our onSubmit, and instead we will do an onClick. And it's going to be create good vote. Promise void not able to type oh, I need to do one of these. Okay. I need to do another one here that will be our bad vote. Now, theoretically speaking, this will do what we want. So, let's see. That should have created a bad vote. So, let's go to our data sentiment. And that's a bad at 24. Maybe that's not working. Data, data. Bad. 6/23 at what time? 17:29:59. Now I'm paginated is why. So, bad, bad, at 17:29:59. So, that's working. Let's try good. Good, refresh. Here's our good at 17:30:23, 17:30:23 should be what we see here. Okay. So, now we are actually creating our data.