The reason that I wrote it in Reason was it's a much faster way for me to prototype. As I was new to say, egghead's API and a few other modules, I was able to just take notes using Reason about my assumptions and then build this thing out quickly.
Anytime it turns out that I had made a wrong assumption, I could change that in one place and it would tell me everywhere I needed to fix it. It really saved me because I stepped away from this project for a couple of weeks. I just finished it up a couple days ago.
Coming back to a project you haven't worked on for a while, especially when it's in a prototype phase can often be a very painful experience. I was able to just dive right in. I was pretty vicious and just changed a bunch of stuff and it's just worked.
John: Yeah, this is not practiced or anything, so the final product after an hour or however long we spend probably won't be done, but it'll show the process. Lots to learn here.
John: Yup, awesome.
Sean: Go ahead and share this. This is a Gatsby site, so it's a static site that basically is pulled out using AKed's API. I'll list out all of the courses, their lessons and their transcripts and whatnot. Maybe we want to look at Nick Graff's graphical data in React. You're going to open up this course and you need to be logged in with both GitHub, for reasons we'll see, and AKed as well.
Now I'm in this course and we have a list of lessons over here, and I can just click on them and it will download the entire transcript, I can see it, and I can edit it. If I see something like a title or something I'd like to change, I can option click on it, and it will actually highlight it down here in the Monaco Editor.
I can say, maybe I want to add a title. Once I'm happy with my changes, and I can see the live edits here, I can go ahead and say, create inaudible . I'm going to say add a title. Create title. I'll go ahead and submit this. This should have some more feedback right now. It's still in the prototype phase.
What it's doing right now is actually forking the Egghead repository on GitHub into my own account. It's then creating a new branch for this change. It's uploading the changed file with the changes I made and then creating a PR directly.
If we go over to Egghead, we can see that it's already created a PR here with my changes, including some front matter about what was actually changed. If we go into files change, we can see this is the change that was actually made.
The idea here is that Egghead can have this nice GitHub flow where there's a PR workflow. They can review changes and see what needs to be merged in, while also keeping attribution for who changed what. That's not really exposed to any of the people who are editing the transcripts. No one has to worry about the GitHub part.
You come in here, you change the parts that you want, and then you can see the changes. If I come down here, I can see that I have two PRs for this lesson already. I can add comments here. Those will, of course, go over into GitHub.
Let's go ahead and start. The thought was we have lots of individual components here. I thought maybe what we can do is just start with the some of the GUI parts.
I wrote some bindings for React Monaco. Let's see. Where do we actually edit this? Inside of here, we have our editor class. I'm just going to remove this initially, entirely. I'm going to just add a string, hello world, run that through the compiler, and make sure that this is going to do what we expect it to do.
Let's go ahead and check in our compiler if it's OK. You can see we have hello world down here. Actually, I meant to edit this one up here. That's why laughs I thought we should test it beforehand. Let me go ahead and do that.
I'm going to look for a no lesson transcripts, see where that string comes up. Let me know if the font is too small or anything like that. We have right here. We're going to render our login guard, which then is going to, if we're logged in, it's going to render our editor module. This is probably all we need to start with.
Let's get it so that we can render a React component that has a hello world. I think you just do something like const export component. That's going to be a function that takes component to import React.
John: Pretty sure it's export const.
Sean: Const, yes. Thank you. laughs
John: Our key word.
I'm going to comment out this suspense bit here. Let's go ahead and render the JS editor.component. I think we need to call this make. It's like a special word. Let's go ahead and call this one make. Let's do that. Now if I just say JS editor and let's do...We have labeled arguments. One second. This is what I want.
John: to make this the naming convention that...?
Sean: Yeah. This is just like a Babel transform when you do JSX. Whenever you do, for example, my component and name equals Shawn, what this really compiles out to is my component.make and name is Sean.
John: Got you.
Sean: It's just like a little bit of sugar so that it looks familiar for JSX people, which is pretty nice. I'll copy this one over here, which is what I should have done beforehand.
Comes from editor JS, a React component. Let's make it take a name, and we'll do that. Does that work? OK. It takes a name, and this is going to be a string. It cannot take any children for the time being. We'll call this inaudible . If we did export defaults, then we would call this one default.
Let's see if that worked at all. We undo that real quick. I'm afraid I might have messed up the syntax here. I don't know where this one is. This is probably fine.
I'm going to look at the compiled output. Just like I said, we have courseeditor.re. That's always going to translate to a courseeditor.ps.js.
You can see that we have this make here, jseditor.make. Let's see if it's actually going to render it now. First, we'll check the compiler and see if there's any issue. There is a warning that we define React but we never use it just fair inside of our editor.js.
We still have some type safety, but whatever we do inside of here is free for all. We told here Reason that we have this module, it takes the name string, and it returns a React element. It's possible that we could return false or something like that.
Let's go ahead and take a look at our editor. This is the real editor module. We have a couple of types here. This is what I mean by I have this assumption about the data that I'm working with. I say, all right, I have a payload that I'm trying to edit, which is going to be a transcript, whether notes or the entire edited.
The original transcript, the edited transcript. Then the SHA of the original file. We'll see why we need that a little bit later. That's whenever we actually commit to GitHub. It's really nice that we have these notes that we can keep along the way.
I have some helper functions. It's a relatively large component. Let's see. This may be all we can actually switch over today, but we'll see if we can get to it. To start with, we actually want to take a couple of these functions here.
You allocate this object at run time and this is how you get named arguments for a function versus just positional arguments. R2, R3. There are a couple of things that are a little bit dangerous about this. One is you're allocating an object on every functional call, which is a little bit wasteful but probably not the end of the world.
The really big problem is it might actually be firstName and you don't realize it. It's very easy as this gets up to...This is very often something like options.
When you do your options.firstName, it's very easy to have this part become out of sync with any of the callers. In Reason, you have this first-class concept of labeled arguments, which are with this tilde here. For example, if I have a function like this, whenever I call it...You can call this with any order.
I can say, my client is NewClient and jwtMe. We also have punning, which is nice. I'll go into that later. The idea is if I ever get this wrong here, it will tell me, "Hey, I don't know what jwtMe is." It has to be actually that. It always keeps it in sync. It also compiles up to the most efficient possible representation, so you don't allocate an object. You get the best of both worlds there.
Let's go ahead and undo all of this. I see that these are the options that it's passing right now, excuse me, the arguments that it's passed in. I'm just going to write a little Emacs macro to find the argument, fill it. Do that a few times. I'm going to copy this over here.
When you have to give it the types...What I'm going to do to get the types real quick is go to the existing editor make and ask, what is the type of this function here? You can see down in the lower left it's going to give me the type. I'm going to go copy that out and let's see if this will work. We'll paste that in.
John: Yeah, Desmond, good question. Simply because Sean wrote a project for us that he wrote in Reason because he excels at it and is very productive with it, but our dev team at Egghead are not Reason developers.
Sean: OK, I think we're making good progress. React is inaudible used and it doesn't like...ESLint is another one that is a little bit tough for me because a lot of that is built into the Reason compiler. You get all this stuff in the editor. You're always aware of it, so it doesn't have to block the rendering of the thing.
Let's go ahead and get this here and that. Now we should have all of the stuff that we actually care about. Let's go ahead and console log a few of these things and make sure that the data's coming through as we'd expect it. inaudible . Log a course as well.
This looks like we're able to pass stuff into the JS editor pretty nicely. Now what we're going to do is now that we have it calling and passing the data in, we're going to go look at the guts of the editor and try to port that over bit by bits as well.
I'm just going to attempt buffer. I'm going to paste this in. All of these style up make, let's see how many of these we have. In Reason, everything has to be typed. There are escape patches, but it's really hard to do something like in INI, like in TypeScript.
Even your styles ultimately have to go through this function called ReactDom style make, which has all these. If we ask it, for example, "Hey, what's the type of make? What are the arguments that it takes?" you can see that it has all of these...every CSS property is actually listed inside of here as an optional thing.
Then I'm going to search for a make. I'm going to kill that. I'm going to go to the inside and then jump back right here. I think that that will be probably the most the Emacs macro can do for us. Now I'm going to wrap this in that.
Enough, I jump to the next one. Cool. It worked. Same idea. If I were doing this on a bigger project, I would automate this in a better way. For the time being, this will work just fine. You can see that it's Emacs macros doing the rework.
John: I have no experience with Reason, but it's still pretty easy to navigate visually for me.
Having a different syntax doesn't gain you a whole lot. It can really scare people away. It looks different for no discernable advantage.
The other thing is I'm going to search for the js.log. I'm going to replace that with a console.log. We actually have...I guess I only have one of those. The switches are going to be a bigger deal. So many things in Reason come down to pattern matching, which is where the switch comes in. Imagine, for example, you have a function or a component that...Go ahead.
If I come down here now, I can say lets name is...I'm going to switch inaudible if I can call a getName. It's going to take an action, and based off of the action, if it is a grett, we'll get the name out of here and we'll return that. If it's a log in, we'll say no name. inaudible .
There's something wrong. Let's see if I can fix this. inaudible . I just want the switch here. I'm not sure what I'm doing wrong. inaudible chat is probably screaming at me. Oh, yes, switch action. laughs I need to switch over the thing, obviously.
The next here is it's going to tell me that hey you're missing one of the actions, which is one of the first issues. It knows that these are the three possible actions, and it knows that I forgot it. I can say while I'm logged out there's no name.
The cool thing is I can say, for example, I can assign this a value to a variable. Switch is an expression versus a statement, which means a lot of the code where you're doing an if else this and you have a let beforehand, and the if/else will change the value of that up there gets really shrunk down into this much easier to read form here.
Translating the switches where we're switching over a lesson which is knowable. We might not have a lesson. It's going to be a little bit more challenging. I might see about how to do that. Get rid of my comment.
Let's see. I guess we'll just something like let lesson. Then if there is no lesson, then we'll say that lesson is going to be equal to the lesson selected. If there...We're going to do it the other way around. If there is a lesson, then we'll assign it...I'm not sure about the truthy values. Let's do, if there is, then we'll say, "Lesson is inaudible selected."
Let's go ahead and copy this part over to start with. We'll close this. We also need to return this. We don't have a lesson right now. We'll put this here, we'll say, "This is a lesson." I'm not sure where did we get the lesson from.
Let's go ahead and look at the...I can jump this, ask where is this defined. It looks like, in our component states, we have the lesson ID that we're looking at. Then we get inside of our hashmap and say...We need to build a hashmap of lessons from our courses. To start with, maybe we can dump the structure of the course, so to be a little bit more clear.
I'm going to say, null. Here, we'll do a pre tag, I'll do a JSON.stringify of the course. Did I get that wrong? Yes, I need to do that, and React must be in scope. That's fair. We have no lessons selected, which makes sense. Let's go ahead and look at the component states up here. I'm going to copy this over into...Here, we'll make our first light default state, based off of this.
We have a list of PRs, where this is a map of lesson ID to a PR. This is Boolean. This is a map of lesson ID to a lesson edit, inaudible do the lesson edits. This is, likewise, pretty important to have. Reason? Watch my back, because in a month, I've forgotten what a lesson edit is. I can come in here and I see, "Oh! A lesson edit is this."
John: I agree.
Sean: It's going to be our lesson edit. This is our current lesson that we're looking at, which is potentially none. We might not have selected a lesson. This is the current PR that we're looking at, which also might be none. Inside of here, this is going to be one of these lessons right here. I don't think we need to worry about that too much. Then, the edit payload is here.
Finally, at the bottom, we have our transcript, which is the original transcript we edited, and the sha. This is at the bottom.
Now we want to look at let lesson. What this is doing is just taking the current lessonID, which is in our state, and looking it up inside of the map. Let's do defaultState. Our prs are going to be an empty object. ChatOpen will default to false. Lessons will be an empty object, lessonID will be null, and the prID will be null.
We're going to start porting over the reducer so it can capture all the actions that our editor does so far. Bring that in.
Sean: Let's see. I think one of the actions that we allowed was select lesson. We can just start with that. We'll say it's select lesson. In that case, we want to return our previous state, but with the lesson ID set to the action.lessonID. Otherwise, I guess we'll throw an error.
Let's make a button. I'm going to cheat to start with and just look at the lesson ID. We'll say, "3845." Let's also do the same thing here, where we'll actually take a look at our state as we're debugging it. Do it here, where our initial state...We call it default state. We'll just keep their conventions. We're using reducer.
Sean: We'll selectLesson. We're going to set the lesson ID to be 3845. This is where it's going to get a little bit scary for me. Now I need to remember that selectLesson has a field called lessonID on it. It's possible I may think it's ID.
My mind constantly makes up good arguments why it should be a different name in the middle of a programming session, and I forget what I actually called it originally. I always think it'll be so logical. I'll always remember it.
Now we have this set up. Hopefully, if I...That's not quite what I was hoping for. Let's see, selectLesson. What did I do wrong? Oh, I need to pass in the state first. I see. It's state and then...Let me see how this one works. We dispatch. We don't need to do that. Maybe useReducer's supposed to already pass in the state for us. What did I do wrong? These match up. These match up.
Sean: I see. This needs to be nested in another thing. Thank you. If you don't have the Reason type system to back you, then you probably want to have the Egghead crowd watching over your shoulder.
John: No, still wrong. Dispatch action type.
Sean: I see, OK.
John: The payload's probably another object.
Sean: Cool. inaudible . laughs Small gains, small ones.
John: Since we're 45 minutes in, I know I would love to see more about the authentication, the flow. Just how it all works and hits the endpoints.
Sean: Sure. Maybe we can...
John: GraphQL and authentication and stuff on that...
Sean: All right. See if this will come in. What we do is just show what it looks like to...We haven't logged in with Twitch at all. We can log in with Egghead and GitHub. We can still see what it might look like if we also want to bring in some Twitch information. Someone could post it here with the Twitch info. The way this works, I have this one over in inaudible bindings.
. Or just go to our docs. We can just copy this over, which is how we're going to do auth. I just need to get the app ID. There we go. Then we'll do a string-streaming case. Cool. Now we have one graph off setup. To log in a user, we can just do this. We'll copy this.
What we're going to do is make this button, so that's on click, it's going to log you into Twitch. We say button and click. Throw that in there. Then just do Twitch. Let's see if that works. Going to need to do a login. We're writing it there. Let's see if this works now.
This is going to go through the flow. I was already logged into Twitch, so it already sent me back here. Now I should be logged into Twitch. If I wanted to, for example, let's make a quick API call to see my information about me in Twitch. Let me go into Explorer.
We'll go find out about me. If I'm logged in to Twitch, I want to know my display name, my email, my name, and my ID. We'll go ahead and run this. I'm going to call this find me on Twitch. I'll log in. This is what I should expect to see for my user whenever I'm logged in inside of the app.
In this case, I'm exporting as fetch rather than a React component. I'm just going to copy these two functions over. Now it should have find me on Twitch. If we are logged in, I'm going to call wait, find me on fetch. We'll just console.log.start.
John: You'll have to make the function async.
Sean: Oh, thank you. Hopefully, I can just do that right there?
John: Inside of the inaudible .
Sean: Inside of here as well? Probably not here. We'll open this up and log in. I logged in. I need to add, I guess...We have maybe a CORS origin issue. Let me see what happened. You can see I'm logged into Twitch here, but then this didn't allow it for some reason. Let me see if there was some issue on my side.
Copy this as Chrome. I'm going to paste this in. I find CORS errors are really hard to debug. What I tend to do is just copy them into a batch script here and save them. Now I can just run chmod +x.
John: Do you want to make an Egghead course on debugging CORS errors?
John: People would watch it.
Sean: Looks like I used the wrong auth or app ID. That was my bad. Just do this. Right here, I was using the wrong app ID. I logged it into an app that's not going to allow this. Let's go ahead and paste that in and clear this out one more time.
There we go. Now I have all the information about me from Twitch. We added login with Twitch. We have all their information that we need from them for this app right. That's what it looks like to add login with a service. We could similarly change that to Egghead or Salesforce or Spotify or whatever. Was there other parts you want to see?
John: We also mentioned some inaudible point out the GitHub API parts and how that...
Sean: Yeah, sure.
John: Is that running through OneGraph at all, the calls or is it accessing...
Sean: The GitHub API is really well-designed, but it's still split between their REST and their GraphQL. Most of the functionality is on GraphQL, but not all of it. We pull in their GitHub GraphQL API, but we also extend it with the functionality that's available inside of the REST API.
For example, if I wanted to come in here and if I wanted to create for fork a repo, we can come in here. We can look for...
John: Just real quick. This OneGraph graphical, once you sign up and create an app, this is just available...
Sean: Exactly, yeah.
John: Sorry. You could explain it better than I could. I signed up for an account. All this stuff was there. There's no fancy thing of getting to this. It's just create an account and create an app, which is clicking two buttons.
Sean: You can do some pretty cool things. I'll just make a quick Egghead inaudible . I just created a new app. Now I'm inside of the full OneGraph one. I'm going to add a mutation. For those of you who don't know, GraphQL has three concepts.
One is query, which is where you're reading the data. The other is mutation, as where you would write such as GitHub posts in an API, and then subscriptions, which are kind of a live updating data. Mutation is going to do something. It's not just going to read data. It's going to do something.
I can come here in GitHub and see here are all the actions that are available to me. For example, I want to maybe create a repository. I'm coming here and say egghead live and I'm making this public for everyone. Having created it, I want to get the issues, the ID and maybe the URL. We'll call this one create github repo. When I run this, I'm not logged into GitHub, so it'll ask me to log in.
We'll run and now I have a GitHub repository that I was able to create. It's empty, obviously, but if I wanted to, for example, then upload a file, I could add another mutation here.
Let's do create or update file content. This is the commit message. Then, we'll do it at readme.md. The repo name is egghead demo live and I'm the owner. We'll do the plain content, which is inaudible .
This is actually going to make a git commit, so we'll get all this information out. I'll run that. I'll have to come over here. Hey, we have the hello from GraphQL. I think that this creator update file content, it doesn't exist in the GitHub GraphQL API, but people still want to be able to do that from the browser, right?
For example, in the Egghead example, we're actually creating new files and modifying them directly from in the browser and we don't want to have a server to actually do that stuff for us. What we do on OneGraph is we pull in that functionality, and we append it with OneGraph.
The idea here is, this way, you're always guaranteed that even if they introduce an action called creator update file content, we'll guarantee that this one will always work, and will actually map this one to the new one, and you'll never lose functionality. You can commit to this one API version, and then you're good to go.
If you want to get this into, for example, this is hard-coded, but we can actually do that export where instead of exporting to fetch, I'm actually going to create a React Apollo application. You can see here that it's analyzed all of the operations over here.
We do best practices. You can see here, a lot of people don't know you can name graph QL operations. We say, "Hey, we noticed that you haven't given us a name. It's probably a good idea to. We won't stop you, but probably you should think about it."
Then inaudible has this full Reacts Apollo application will create a CodeSandbox that actually has it running right now. If I click this, let's do a live demo two and create another CodeSandbox. I feel a little bit bad for CodeSandbox, it would just throw a bunch of stuff at them. If I run this...
Sean: You can see that login is already built in. It said, "Hey, you weren't logged in." This generated app already does all for you. There we go. Now whenever I go here, I should have demo live two. This is a full React app that I can just copy and paste that will actually interact with GitHub and create repositories, or upload files, or whatever.
John: It's such a great starting point. HelloWorld is now, it requires inaudible or almost every app. It's just the way the world works.
Sean: Yeah. Whenever you're getting started, you're just desperate to get anything to work. You traverse the wilderness where nothing's working for a very, very long period of time. What you want is just like, give me an example that works, and I'll start tweaking it. I'll nudge it closer to what I want.
That way, it's always successful. If anything breaks, you just go back to the thing you were doing before which works, and you can keep going. I'm pretty excited about the future of the graphical tooling that we're going to be able to build out.
Sean: Yeah, I find that one to be actually significantly easier because what happens is you start with...The process whether you're using Reason or not is going to be the same. You come to some code base, you're trying to think what are the data structures that are passing through here?
This function takes some arguments and returns something, and I have to keep that in my head. You work on it but every function that it calls, you also have to keep in your head. When something breaks, you have to dive all the way down.
I think of Reason as this notepad. I keep the notes over here while I'm saying it takes these types and it returns these types. Then I port one module over the Reason and I make sure that everything is right. If I get anything wrong, then it will tell me, "Hey, these things actually are wrong. Fix it up."
If you do something like use genType, where it will actually take the...This is a story from messenger.com, which is now I think almost 100 percent on the front-end Reason. They actually had this problem because they were using Flow.
John: Very cool. All right. If the chat doesn't have any questions, I think we'll wrap up so Sean can get back to his day job.
Sean: Thank you very much for having me. This was a lot of fun. I hope it was at least somewhat interesting.
John: The other thing I would note, we do have a course on Reason on Egghead by Nik Graf. Check that out. Just get on Egghead and search for...I guess I can paste the link.
Sean: Nik Graf is great. He also has a course on GraphQL schema design, which is phenomenal. He's a machine.
John: I'll just drop that link in here real quick. Any one Graph plugs you want to make?
Sean: No. If anyone wants to try anything, we have a bunch of interesting examples. Just basically ping me on Twitter, and I'm happy to talk about any app ideas for implementation or whatnot. Maybe a fun one I could...There's a super long talk I gave that is just full of a bunch of GraphQL inaudible that we're working on that's almost all open source. That's a plug I'll give.
John: I pasted your Twitter handle.
Sean: Thank you.
John: All right. Thanks, everyone.
Sean: Thank you.