JohannLai / audio-to-text

Convert audio to text and summary just need to input the audio link.
GNU General Public License v3.0
9 stars 2 forks source link

🎬 -> πŸ“ Rebuilding Jotai State Manager for React in 20 Min... #59

Open leeguooooo opened 1 year ago

leeguooooo commented 1 year ago

https://www.youtube.com/watch?v=gg31JTZmFUw

github-actions[bot] commented 1 year ago

Hi! πŸ‘‹

Thanks for using my services! ❀️! πŸ€– I've received your request and currently working on it.

βš™οΈ It might take a few minutes πŸ™ You can check the progress here

In the meantime, feel free to chat with me ! 😊 I'll notify you as soon as I'm done! 😊

If you find my services helpful, you can support me by buying me a coffee β˜•οΈ

github-actions[bot] commented 1 year ago

Mission accomplished! πŸ₯³πŸ₯³πŸ₯³ Here's the result:

github-actions[bot] commented 1 year ago

Rebuilding Jotai State Manager for React in 20 Minutes!

Summary Text

Jotai is a global data system that allows users to create atoms, which are primitive values that can be used anywhere in the hierarchy. It also allows users to create atom getters, which are functions that take an atom and return its type. The value of an atom getter is not the getter function itself, but rather the value of the atom it is targeting. This allows users to create complex functions that can return different types of values.

Original text

Rebuilding Jotai State Manager for React in 20 Minutes!: Recently, I did a short on Jotai, which is a React state manager similar to Recoil. And one of you wrote in saying, wouldn't it be great if we dug in and figured out how it works? One way to do that is just to rebuild it. So challenge accepted. We're going to rebuild Jotai right now. Let's get right into it. Hey, sorry to interrupt. As it turns out, I showed this video to the folks over at Code Candy. And they were like, you do know that the entirety of the Jotai Atom code fits into a single tweet that Daishi put out a long time ago? And I'm like, really? I didn't actually know that. So I'm going to put a link to that tweet in the description down below. I still think there's a lot of value here. You're going to see how state management is done now in two different ways. They are also looking to redo the Jotai documentation site. So I said I would ask you all to contribute to that if you are looking for an open source project to contribute to. All right, enough interruption. Back into it. So if you're not familiar with Jotai, it's an atomic state manager. So what does that mean? Well, you define pieces of data as atoms. Those can be numbers, strings, booleans, arrays, objects, whatever you want. And then you can relate those atoms. And you can also use those atoms anywhere in your application that you want to. So the first thing I'm going to do is just show what it's going to look like to consume this. So I'm going to bring in our fake Jotai file. So instead of importing Jotai, we're going to import slash Jotai to bring it in from a local file. It's not there yet. And then I'm going to use it. So I'm going to say that we have a salary atom. And we'll start off our salary at $100,000. Now the way that I would use that is that I would bring in the useAtom hook. And then I would just use it just like any other use state. I just consume the atom by doing useAtom. And then I get the current value and I get a setter. The really cool thing is I can now take this atom, I can move it into a different file. I can use that atom in any location. And it will always be held in sync in all of the different parts of the tree. So it's truly a global atom. So cool. And let's just put a little UI on it. So we'll have an input that gives that salary as the value. And then we change it, we'll just set that value. This little plus here indicates that we want to convert that text to a number. So this is always going to be a salary number. Next step, let's go build that Jotai file. So I'm putting my file explorer on the right. So hopefully the code doesn't shift around as much. Let me know in the comments if you like it over there. So I'll create my Jotai TS file. And now we have to have two functions in here, right? Atom and useAtom. What does atom take as an argument? Well, it takes an initial value. And what is that type? Well, we want that type to be type safe. So we're going to use a generic for that. Now we've captured the type. So in this case, a number for our first atom as atom type. Now what's the return type? Well, the return type is going to be an object. It's going to have three methods. It's going to have a way to get the current value. It's going to have a way to set the current value. And it's going to have a way to subscribe. So let's build those out. So to get it, we're just going to have a function that returns whatever that atom type is. To set it, we're going to take a new value. And that's going to be of that atom type. We're going to return a void because no reason to return anything out of there. And then we're going to have a subscribe. That's going to take a callback. Every time the atom changes, that callback is going to get called. What's it going to get called with? What's going to get called with a new value? It's also going to return an unsubscribe. So that's the last part here is a function that when you call it, unsubscribes. So let's start out by storing our current value. And then we'll return our object. And for the get, we'll just return the current value. For our set, we'll take a new value. And then we'll just set that value to the new value. And then we need a subscribe. So okay. So what we need is we need a set of subscribers. So we're going to go down here and we're going to define our set of subscribers. And what is that going to be a set of? What's going to be a set of functions that take a value and return a void? Basically the subscribers. Why am I using a set here? Well, I want to make sure that the subscribing function is only listed once in the array of subscribers. And set helps me do that by being sure that any function reference or any value of any kind is only listed once in the set. All right. Let's continue on with our subscribe. That's going to take our callback. And with that, we are just going to add that callback to our list of subscribers. And then we're going to return a function that when you call it, deletes that callback. Now we have an ability to add a subscriber. So when should we go and send a notification? Well, that's any time we get set. So we will just simply iterate through all of the subscribers and then get the callback from each one. Call that callback with the new value. How cool is that? Now that we have our atom pretty much done, now we need to have a way to use that. We need to have a way to take this thing and connect it to the React ecosystem. So we need a custom hook for that. And that custom hook is useAtom. What is the type of that? Well, the type is going to be whatever this structure is here. So let's go and make that into an interface instead. And we'll call that atom. We'll take an atom type. And so the atom function is now going to return an atom, uppercase atom, of that atom type, which it seems to like so good. And now we can go and take that atom type and transport it down here to our useAtom. Of course, we don't have that generic. So let's add that. Now what do we need to do with useAtom? Well, useAtom has to have some type of state because in order for it to force a re-render in React, it needs to actually change some state. So let's bring in useState and useEffect from React. Now let's use useState to track the state. We're going to return out of here the value, but we don't want to use our set value to set the value because that would just set it locally. We want to go and send the atom setter as the setter. So now over in App.tsx, when you call setSalary, what you're really calling is this setter right here. So now how does that kind of create a loop? Because we need to subscribe to this so that we can then call that set value locally. So that's where that useEffect comes in. And what we're going to do is we're going to depend on that atom. Now inside the useEffect, we are going to subscribe to our atom and we're going to give it set value. So whenever a subscribe fires, it's going to call our local set value and that will force the re-render in React. And we're going to get back from that an unsubscribe, which we then return as our cleanup function. Okay, let's try this out, see if it works. All right, we have a salary of 100,000. Let's change it to 110,000 and there we go. Now this looks pretty good. Let's test this out a little bit more just to make sure it is a global atom. So over here in our App.tsx, we're going to create another display. And in there, we're going to get the salary again by using useAtom. And then we're going to return a div. This will call that salary display and we'll drop that down in there. And we'll, okay, it looks pretty good. Let's change that to 110,000. Ta-da! So simple. Okay, but there's actually a way that we can do this a little bit easier. And that's why I bring in a semi-obscure hook called useSyncExternalStore. So this is more of a library author hook, but hey, we are library authors now, rebuilding Jotai so of course, we can use our useSyncExternalStore. Okay, so what does useSyncExternalStore do? Well, and what useSyncExternalStore does is effectively all of this. It has a way to subscribe to an external store. It is a way to get the current value and it handles all of that for us. So let's go and replace all of this with just useSyncExternalStore. For the first item in there, it takes a subscription function, which is subscribe. And the second item is a getter or the get snapshot, which asks you to give it a function. Every time you call a function, it gives you the current state and that's it. Let's give it a try. Whoo! How cool is that? Awesome. Now, there is one more thing that Jotai does that I like a lot, which is to give us a useAtomValue hook and that just gives us the current value. So what we can do in that case is just return the useSyncExternalStore and now we have a value getter. So let's use that back up here to get the salary. Looks good. Okay, let's try it out. Awesome. So this alone is pretty cool. We have this new way to have these little bits of global data that you can use anywhere in your hierarchy and it's not a lot of code, right? And the code is pretty easy to understand. But here's where Jotai starts to get both fun and kind of tricky simultaneously. So let's create another atom. We'll call it bonus. It'll also just be a primitive value, so say in this case 10,000. And let's go down here and we will have our bonus and set bonus, target that at the bonus atom and it'll rebuild this section. This time it'll look at the bonus. Let's check that out in the browser. Looks good. But what we really want is we want a total compensation value. We want the total compensation value to be the sum of the salary and the bonus. This is really cool. So we'll call that total salary atom and what we'll do is instead of giving it a value, we will give it a function. And then that function will get a getter and then you can call that getter to get the current value of any other atoms. And you can return anything you want. So in this case I'm just going to return a salary atom plus a bonus atom. But you could have an array in an atom and this would be like a filtered version of the array or whatever you want. So now we need to support this, which is another core feature of Jotai that we want to see in our Jotai rebuild. So we need to say that we can take an atom type or we can take an atom getter of that atom type. So what does an atom getter look like? Well, let's make a type for that. And sure, it's going to be a function that returns an atom type. But it also takes a get function and that get function has its own atom type. Let's just use T for that. Where it takes an atom, that atom then has its type and it returns that type. So this is a little tricky, right? You may have an atom that you are subscribing to that has an array, but you may output, for example, a number if you are a summing atom and you take an array of numbers and then you sum those. So you need to track the different types individually. So the type of this atom is atom type and the type of the target atom in this case is T. Let's just call it target. Make it a little easier to track. Now let's start implementing inside of atom. So the first thing we want to do is what do we do with value? Well, we want value to not really be the getter function. We want it in the case of a function return to start off at say something like null and then get set to a value once we have computed that value because it's going to be kind of tricky to compute that value. So let's look at the type of the input here and see if it's a function and if it's a function then we'll set it to null. Otherwise we'll set it to the initial value. But now we have a problem with the getter and also with the callback because TypeScript thinks well you can either have null here or the atom type and that's not quite right because all we want is the atom type. So let's just coerce in this case the null to be an atom type. And now we want to compute that value because on startup we want to initially compute the value and then anytime it changes we want to compute the value. So that means you should probably have a compute value function which we'll initially call right away. So I'm going to grab this let up here because we're going to also look to see if there's a function. So if it's a function then we're going to do some magic. Otherwise we're just going to pass the value through. And what is the magic we're going to call? Well we need to call that initial value which is the function. We need to call it with a getter, that get function so they can get other atoms. So let's create that getter and then pass it on to our initial value. And that getter needs to get the target atom and then get the current value off that and then subscribe to it. And then when it changes we're going to call compute value again to get the new value. So we're going to get a target atom and that's going to be an atom of type target. And let's start by just getting the current value and returning it. And now we want to subscribe to it. And any time that that value changes we are going to take that new value, make it the current value, recompute our current value based on that, and then we're going to call our subscribers with our new value at the end of all that. One optimization I think we should make right now is to make sure that we don't recompute if we see the same value twice. So let's go and add a conditional end for that. So if the new value is equal to the current value then sure, just return or short circuit. Cool. All right. One last thing. Ah, this is giving us some guff. I think that's because, let's see, it's not callable. Oh, I see. So in this case that function isn't good enough for TypeScript to say that that is a function. So let's coerce this to a getter. Let's save it out and try it. So there we go. We're changing the numbers. But of course we don't have anything looking at the numbers. So let's go and go back to our code and then we'll add in a useAtom value for the total salary and we'll put that down there as the total salary. And let's see. So it's computing. Hey, whoa, that's nuts. Wow. I have literally read on most of the core of Jotai just like that. One more thing that Jotai allows you to do is create asynchronous atoms. Those are atoms that have a function. The return's like maybe a fetch and then it automatically tracks that and then returns, sets the value to whatever the return of that is. And then you can also depend on those atoms with synchronous stuff. It's mind-blowingly cool. So let's go and see how difficult that's going to be by first creating some data that we can then go fetch. Over here in public, I'll go and create a new file, create some basic data in here. Good enough. So now let's go back over here and create our data atom. And it's going to return a function that then does a fetch for that data and then gets us back the JSON. Awesome. Now, let's go back over here in our Jotai.ts and let's see what we need to change. So over here in compute value, what's going to happen is compute value is going to call that function and it's going to get back a promise as the value. So let's go and call that new value. Now we need to see is the new value a promise? And if it's not a promise, then it will just set value to that new value. So let's start off by creating that conditional. So that'll be the promise case. And otherwise, we're just going to set value to new value. So how are we going to decide whether it's a promise? Well, it turns out state of the art on that is to say one, is it a value? And then does it have a then key where that then key is a function? We're getting an error on this then saying that doesn't exist on that because it doesn't know that it's a promise. So let's just course that to any. And now we can start off by setting the value currently to null. Now we need to actually go and use the then to actually wait for the stuff to come back. So let's do a then. And that's going to come back with a resolve value. And we are going to set our value to that resolve value and let everybody know that we've changed. Let's give it a try. So we'll go back over here to our app and see that it's not blowing up, which is a good thing. Now let's go display our asynchronous atom. So again, we use atom value. And we'll call that data and get it from the data atom. Now we can go display our data. So we'll go over here and we'll hit say data. And because it's an object, we want to JSON stringify it. So we just get the output of the data. Oh my God. Okay, there we go. So there is our data. Whoa, how cool is that? All we had to do to go get asynchronous data was literally just, yeah, that's crazy cool. Now let's go and do one more. Let's go and have a keys atom that gets just the keys out of there to show you a synchronous atom depending on an asynchronous atom. So we'll go get the keys from that data atom. And the only potential gotcha is that null case. So let's say that in the case of null, let's just give it an empty object. That's it. Because it's going to be null initially because we don't have the data yet. All right, let's go and now depend on keys and see how that does. And this should give us foo, right, because that's the only key in our data. And there you have it. Let's go and change the data out. Let's make sure. Refresh the page. Now we have new data and we have new keys. Mind blowing. Okay, well, I hope this video breakdown of how to rebuild Jotai from scratch opens your mind about how some of these React state managers work and also just some really cool stuff about Jotai and Recoil and Nanostores and the other atomic state managers. If you're excited or perplexed or just want to say that that was an interesting thing to do, be sure to put that in the comment section down below. We don't do these kind of really complex videos all that often. It's kind of fun to get back into that. Of course, if you liked it, just hit that like button. And if you really liked it, hit the subscribe button and click on that bell. You'll be notified the next time a new blue-collar coder comes out.

github-actions[bot] commented 1 year ago

πŸ™Œ

If you find my services helpful, you can support me by buying me a coffee β˜•οΈ Thanks for using my services! ❀️