Best React Project Structure | TypeScript + Apollo Client + React Router

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey there today i'd like to go ahead and show you my favorite choice of architecture when building with react in 2020 so we're going to use react with typescript and then use apollo client to connect to a graphql backend and manage our state we're also going to use react router to handle navigation and you'll see how i set up my project and hopefully you can learn something from this and use it in your future projects so to begin i'm going to go ahead and create a new project using create react app you can call whatever you want i'm going to call it react apollo and i'm going to be using typescript just because it's kind of becoming the standard norm in development these days you can feel free to not use it if you'd like to follow um but i'm gonna go ahead and use it for this video so go ahead and let the project set up and once it finishes we'll continue okay so once you've finished setting up the project you can cd into the directory and just run yarn start to make sure that the project is running i'm also going to be creating a github repository with this project so if you want to follow along with me or look at the code at any point you can check it out in the description so once you finish running the project you should see the normal react starter page here so this is great so this means our project is ready to go and we can start coding okay so go ahead and open up project in a code editor i'm using visual studio code and to start i like to go ahead and just remove all the starter files besides the index.tsx so you can go ahead and actually what i like to do is just copy this code delete the source directory and create a new one this is just personal preference i like starting fresh okay we can paste that back in and i'm not going to use any of this web vital stuff right now so we can go ahead and delete that as well and we can delete the app component for now and the css which we'll be adding in a bit okay so now let's go ahead and add our app component so i like to have my index.tsx at the root level and then i like to create a separate folder called components that will host all the components for the project so to start i'll go ahead and create a new folder for each set of components so in this case we'll have one for the root app which we will use to bootstrap the app add our navigation and set up the apollo client so let's go ahead and create an app folder and in there i like to create two files essentially for every folder and it follows this naming convention so i'll call it app dot component dot tsx this just helps to keep things organized so i know which file i have open in the editor and then i'll go ahead and create a corresponding css file for it okay so now that we're ready to set up our app component we need to go ahead and install rack router so let's go ahead and stop our server and i'm just going to run yarn add react router dom so go ahead and add this package and once it's finished we'll go ahead and set it up okay so now that we've installed react router let's go ahead and set up our app component so we'll have to import react let's go ahead and set up the component it's going to be of type react dot functional component i'm not going to take any props okay and now we're going to go ahead and set up our return statement here now it's good practice to separate our components out so i'll go ahead and create a new folder in components one just for our header component and i'll follow a similar naming convention so i'll create a header dot component about tsx and a css file for it as well and while we're at it let's go ahead and import our css file here okay now let's go back to our header component and set up the component here and then we can actually build out a little header with just basic links so header is going to be that functional component so inside this header this is where we'll actually return the jsx for the header with the links and this is probably where you'd also include your logo anything else maybe a hamburger mobile menu um we'll keep it nice and simple and just include a couple links for now so we can use the nav link here and we need to import nav link from react router dom so let's go ahead and do that and it looks like we actually forgot to install the types rack router dom so if you're using typescript let's go ahead and add those now and then we can go back to the project and we should see this error go away and you see navlink complains because we're missing the two property but i just wanted to show you that this component does exist first um we can set up a bit more friendly html here so we'll have a header and then we'll have a nav element and then we'll have an unordered list which will contain each nav link item which will be a list item so let's just say for example we have two nap links one of which will go to the root it'll be our home and we'll use one more and we'll call it slash about okay so now that we have a basic header set up let's go ahead and export this from the file and if we go back into app.component here we can now import our header and i like to keep my imports my relative project imports separate from third party packages and the css files also like to separate them out so we have header component react and the css here and so now we have our header being displayed here so we can save this file go back and index that tsx and import app and i think we forgot to export it so let's go ahead and do that so if we save this now let's go back to our server and run yarn start and you'll see we're getting one error and the error is that we're using a nav link outside of a router so let's go ahead and configure our router so that we don't get this error so if we go back into the header our header looks okay but if you go into the app this is where the problem is we can't have nav links that live outside of a router the router is what react dom uses to be able to tell okay where are we supposed to go in the given page so when we import the router here we need to actually import the browser router as router and that should dispel the error that we're getting so now we have the header sitting between the router so that we've added that import if we go ahead and save the file and open up our page we can see that we are getting two links so home and about and notice you know nothing's being rendered on either page because we haven't included a component for either one yet but you can good to see that our components are listed here and we can see the url changing when we click so a little note about styles like i said i like to import the relative component style sheet for each component i make and i usually don't do this until i actually need to include at least one style so in this case i haven't included the header css yet so that's how we do it there but one of the things i like to do early on in a project is create a common global set of styles that will be shared amongst many different components in the app so we say we have styles just for layouts common typography css resets and so on it's a good practice to share that between different components in the app so things are more uniform and it's easier to maintain so how i like to do this is i go ahead and create a new folder in my source directory call it common and in the common folder this is also where i'll include interfaces util functions constants things that are shared amongst different places in the app so this is a great place to put a styles folder because we're sharing it and in here i create an index.ts file that will be responsible for exporting all of the other style sheets in this directory so we only need to import it once in our app component so to show you what i mean i'll go ahead and create a new css file here we'll call it layout.css and maybe you'd have one for typography and you know you could go on and on and create more global styles anything that's shared between components this is where you include it and in index.ts this is where i will import it so now any styles that we declare layout.css and typography will be exported by default in here if so to show you an example of this if we go back into the app component what i like to do is import this very file so we go into common styles and we don't need to include index.ts because this does that by default and now our common styles will be applied everywhere in our app because we're importing them in the app component so if we save this and go back we won't see any differences now because we haven't included any styles but if we go into common typography and we change our links and we give them a color of red and save we now see that these global styles are applied so now we don't need to change the color of a link in every individual component css file we can do it on a global scale so it makes it more maintainable one other thing i like to do in a project is to include a reset style sheet so all your styles are starting from scratch and we can do this very easily with create react app all we have to do is call import normalize in our app component css so if we save that now we are importing a css reset additionally one other thing that you could do is just apply some resets to the margin padding and box sizing to the document which is something i usually do in all new projects so in layout.css i'll go ahead and add a global selector and set margin to zero padding to zero and the box sizing to border box this just keeps everything on an even scale you can see we have no padding or margin anymore which makes it easier to apply styles where we actually want them okay so now let's go ahead and add some content components to our app so i'll go ahead and close everything we have so far just to clean things up and next in the source folder what i'm going to go ahead and do is create a new folder called pages and this is what i like to do just to keep things organized each page is going to have its own individual separate component outside of components because these components you can think of as just reusable pieces of jsx that you could essentially use in different parts of the app and on different pages so in my mind it makes sense to separate it out into this separate folder so we'll create two pages and i like to append a page nomenclature here just to keep organized so we'll have one for home and one for the about and go ahead and create two folders for these actually home and about and drag them in and similar to what we've done before i usually create a corresponding css file again you don't need to add this until you really need it but i just like to do it beforehand so i'm going to go ahead and set up the basic boilerplate jsx for both of these okay so just to make things a bit more clear when these get rendered i'm going to add a bit of text to each one so we can see the difference so go ahead and add that save and now back in our app component we'll go ahead and include these now we need to tell react router how to render each page based on the current route or path that we're on so in order to do this we're going to use the switch component provided by react router dom and in here we're going to render our routes so we'll have one route with a path of slash about and here we'll render the about page make sure you import both these i have to import this one manually that's okay so we have our about page and now we'll add one more route with a path of slash just as the root of our document or root of our web app and this will be our home page you'll have to change this to home so now if we save and go back to our browser we can see if we click on home we have the home page rendered and about has the about page because we can see the difference in text here okay so before we go ahead and set up apollo client i'd like to go ahead and just set up a basic uh component that will allow us to display a list of posts because this is uh the kind of data that we're going to be working with in this sample application so what i'll go ahead and do is i'll go ahead and create a folder called posts grid because this will be a grid of posts that will split display and inside of here i'll go ahead and create the posts grid component as we've done before and i'll add a css file just to stay consistent then i'll go ahead and create another folder inside of this one and this one will be the post grid item so our post grid will host the data and render each item and then the actual item itself that's responsible for displaying the markup we'll go ahead and include it here so this will help separate out the logic and keep our components nice and clean [Music] so call this post grid item.component and i'll create a css file for this as well and i'll go ahead and include some boilerplate jsx with the post grid item we can just copy the current markup we have and change it accordingly so now that we have our markup complete let's go ahead and fill this out with some logic so our post grid as a prop is going to take a an array of posts and so we need to define what that looks like and so what i like to do most of the time is if i have a an interface so in this case we'll need an interface to define the post and we know it's going to be shared throughout the app this is a good candidate for something that we can include in the common folder so in common i'll create a new folder called interfaces and we can create one called post.interface.ts here we'll export an interface called post so with the dummy api we're going to be working with the post is going to have three properties it's going to have an id which is going to be a type string and it's also going to have a title and a body and all of those will be strings as well now we can go back to our posts grid and we can define which kind of props this is going to receive so after our functional component type declaration here we can specify that the type is going to receive props and we're going to call these props posts and it's going to be of type post and you can auto import it i like to separate our imports out here and it's going to be a type post but it's going to be an array so that's how we go ahead and define it and we can destructure the posts here and then also define the same interface here so it's going to be post like that if you wanted to you could define a separate interface for the props post grid props so that would look like this and we would do something like this so post and then we can replace this object we have here with the props it's just a little bit cleaner helps stay organized especially if you have a lot of props so now inside the post grid i'm going to go ahead and with any of my components that has markup in it i like to include a wrapping div with a class name just to keep things clean and it's going to be the same name as the component so in this case it's going to be the posts grid and inside of here we're going to want to map over all of our posts and render a post item for each one and to accomplish this what we'll do is we will map over our posts so what we'll do is we'll post dot math and for each post that we iterate over we will return so open parenthesis we're going to return a new div wrapping div and to do this and the reason why i do this is because we give it a key property because each item in an iteratable list we want to give a key so in this case the key will be the id of the post and inside the div we will render the post item and the post item we haven't configured any props for it now so let's go ahead and do that so after we save this is what it's going to look like we're just mapping over and returning a posts grid item for each one of our posts so now if we go into the posts grid item component we can define the props that we're going to receive here and you probably guessed already it's just going to be an object that's called post and it's just going to be a type post because we're only getting one post for each post grid item so we can destructure that here as well so to keep things really simple right now i'll do is um we will output a paragraph tag and just output the properties of the post for now so post id i'll put the post title and then the body as well so the title and then lastly we can do the post dot body and that's body and just to keep this a little bit cleaner i'll wrap it around some parentheses so now if we save we can see the post grid is now complaining and i'll do the same thing that i did here or i'll just wrap this in parentheses to give it a little bit of space so we can save this and now we can see the post grid item is complaining because we didn't supply it the properties that it's expecting so we can go ahead and do that by supplying it the post that it's expecting so now if we save and go back to our app we won't see anything yet because we haven't rendered this component so let's go to our pages and let's say we want to render this component on the home page what all we'll have to do here is we can delete our home text and we can include the host grid post grid here and you can see the post grid is complaining because it expects an array of posts so i'm just going to go ahead and quickly fill out some dummy data for now just so we can see the post grid so now we can go ahead and supply the post grid with a post prop and if we save now and go back we can see that the output of our post is being outputted here so this is great it looks like our components all hooked up now let's go ahead and set up apollo client so we can fetch real data from a server using graphql and mutate it as well so let's go ahead and install the packages that we need for apollo client to work properly so i'll go ahead and yarn add apollo client and then graphql let those finish installing and then head back to our code okay so now that we've installed the dependencies that we need for apollo client we can go ahead and configure it so in source and then under common i'll go ahead and create a new file called apollo client.ts and in here we're going to import apollo client and in memory cache from apollo client and now we're going to actually instantiate the client and in this case this is where we're going to provide the api rl where our graphql server is located in this case it would be wherever you're hosting your graphql server i'm going to use a dummy sample api that i found online called graphql0 that is similar to json placement holder if you've ever used that just allows us to use dummy data query and mutate dummy data so i'll go ahead and paste that in here if you want to follow along you can use the same and then we'll go ahead and add in memory cache which is going to act as our state management so be able to cache our queries and the server won't have to keep getting overloaded when we just need to be asking for the same information over and over again and then lastly i'm going to go ahead and export the client so now that we have our client set up we can go ahead and configure our data fetching logic and so in order to do this we take advantage of the react hooks api which fits very nicely with this graphql apollo client setup so in order to do this i'll create a new folder called hooks and i like to create a folder for each type of data so in this case we have posts and you could imagine you could have another one for users and so on but in this case we're only going to have one just for posts right now and you can easily extend this here i'll first we'll first create a hook to query posts and then we'll worry about mutating it so i'll go ahead and create a file called views get posts yes and usually i like to separate each hook out so we have one for each file so in this case we'll have one for getting the post and we'll have one from mutating okay so the first thing we're going to want to do here is generate our query for getting the posts so i'll create a new column called get posts and we'll set it equal to gql which we import from paulo client and then we'll open up some ticks go to a new line and then our query will call it and get it posts now for this api it may be different for yours this api takes um an options variable that we tell it the limit and how many pages or what page of posts that we currently want so if we want to pass data to a query we do it like this so in this case this is called options and in our case it's called type page query options and the explanation mark means it's not optional so now inside of this query we can call the actual posts query this may be different for your api but this demo api it's just called posts and the options we're going to pass the options variable that we just specified up above and now we get to pull the data off of the response so in this case this dummy api returns date a data object and inside that data object that's where the id title and body of the post lives so now that we have our query set up we can go ahead and export a const use get post which is going to be the hook that keeps the query so this isn't going to take any parameters you could potentially add parameters here but in this case we'll always just use a fixed page and limit so we're going to return an array of posts from this query or undefined because if the query fails or the first time it executes it hasn't had time to execute yet it'll be undefined inside of here we're going to call const data we're going to destructure the data that comes back from the query and we're going to set equal to use query which we import from apollo client we'll pass in our query that we generated and as a second parameter we pass in an object and in this object we can pass in variables and this is where we specify the options object up here so like i said for this api it looks like this we have options and there's a paginate variable this may be different for your api but for this one this page name variable has a page property and a limit so we'll just specify these as values for now and then we can return in this case the data and in this case we want to extract the posts from the query and then we want to pull the data so it'll look like the list looks a little funny but it's going to be data posts and then the data itself which will include the information we really want which is the post itself so if we save this we need to do one more thing to be able to use this query elsewhere and what we need to do is we need to configure our apollo client on our app component so that we can use it anywhere in order to do this it's pretty simple all we have to do is go in our app component and we need to wrap our whole app with something that apollo provides us the apollo provider and we can close this the apollo provider takes a client and this is where we pass it in the client that we exported earlier from common apollo client so i'll keep this here and move this one up so remember from earlier when we created the client and exported it this is where we're actually including it so if we move the apollo client and wrap all of our app and save we can now use our queries anywhere so in order to do this let's go back to our home page and inside of here instead of passing in a hard-coded posts i'll create a new variable called use get posts and instead of passing in the hard-coded posts we're now passing in dynamic data and if we look what it's complaining about it's that the type can potentially be undefined because and we have the scenario where the server hasn't returned anything yet it's going to be undefined in that case we'll just pass in an empty array we don't need this type anymore we can get rid of it and if we save and we go back to our application we can see we're getting an error cannot read property posts of undefined and this is what i was talking about earlier potentially our posts can be coming back as undefined if the server hasn't completed the query yet so in order to remedy this we'll just use the ls operator here save that go back and now we can see we're not getting any more errors if we refresh and we can see that our posts have been dynamically pulled from the server so this is great we can see our query working in action okay so lastly i'd like to walk through an example of creating a post so a mutation in graphql before we do this let's go ahead and create a component that will allow us to take in user input to create a post so what i'm going to do is i'm going to open my components folder i'm going to create a new folder in here called posts and i'll put my posts grid folder in there and in the posts folder i'll also add and you can see it auto updated my import so i'll save i'll add another folder called posts form and this form will be really simple but it'll allow us to get some input from the user we won't do any validation or anything like that so we keep it organized under one folder posts so in post form we'll go ahead and create a component in a css file and we'll fill out some boilerplate jsxes and just for the sake of example we'll include this on the home page or actually on the about page because we already have our posts grid on the about page or the home page so let's go ahead and include it here i think i actually yeah i misspelled this so you want to post form here and this should be posts form as well the css and the component posts form so let's say update go to our about page and then we can add the posts form let's save go to our browser and if we go home or the about page we can see we have the posts component showing now okay so now let's go back to the posts form and we can actually fill out a real form so i'll link all of my components apply the class name here just call it the same thing as the component okay so to fill out this form i'm actually going to go ahead and use a third-party package called react hook form which is super lightweight and makes working with forms really easy nice and react so i'll show you how it works we'll just yarn add react hook form this is super lightweight handles storing your form data validation all that out of the box so it's really nice so start our server back up so now here in our component let's go ahead and destructure a const and we'll take register and handle submit out of use form and make sure you import from react hook form so now let's go ahead and open a form tag and on the on submit we're going to call handle submit and handle submit is going to trigger a function that will set up and we'll call it on submit so handle submit we'll call on submit so let's go and declare that real quick so onsubmit's going to get the form data itself and this is where we will submit our data to the server using graphql apollo client so let's go ahead and set up the rest of the form so we'll have an input and we'll call it this will be a type text and the name will be the title it's the title of the post and we need to pass the ref and call the register from craftbook form so then we'll do one more and we will call this one it should be a text area and the name should be not title but it should be the body and then lastly we'll have an input down here it'll be a type submit so if we go ahead and save and open up our app it's not going to look very great and we see typescript is complaining about data having type any so we need to give data a type and we can actually give use form a type so in order to do that i'll declare an interface up here and i'll call it form data and the form data is going to take a title and then the body which is going to be of type string now what we can do is we can provide a type here and use form and pass in the form data and then we'll replace our on submit here instead of calling handle submit directly we'll call on submit and then this data now instead of invoking a function immediately we're going to set on submit equal to handle submit we're going to call it and then pass in our function and then we can destructure the form data so we can take the title and the body and then we can open up a function here so now we have access to the title of the body and it's typed correctly now if we save and go back to the app we can see we have a very simple ugly looking form and just to see we can log out the title and the body and we should be able to see this in the logs so it looks like we have an issue with the body let's go check that out and i'm also just now realizing this text area should be text area and then we can pass in the ref so now if we save and go back we can see the text areas there here is it's a really ugly form but if we submit we get the data so this is great we have a simple little form set up now we can actually call our api let's go ahead and do that okay so let's go set up our mutation so inside of our hooks our posts folder i'll create a new file called use createpost okay and the first thing i'm going to go ahead and do is create the mutation that's called create post graph qr language open tick marks and then what we're going to do is we're gonna call this invitation i'm gonna call it create post and in this case the api you can take a look online by the way i'll put the link for this api in the description but this api it takes a parameter called input and subtype create post input it's not optional so the actual mutation is called create post we have to pass in the input and let's take the id title and body from the response so now that we have that set up we can create the function that will export that all of our components can use and it's going to return a function that is going to expect a property called create post input and it's going to be a type create post input so let's go ahead and set that up now we can do it in the same file call it create post input and it's looks it's structured like this in this case this is how the server expects it always takes variables object and in this case it's called all the input inside it expects a title type string and body type string so now that we have that set up we can come back down here we'll add a comma and this return type is just going to give any for simplicity right now you can be more specific if you'd like and inside go ahead and destructure a variable called create post which will be the function and we're going to call use mutation which we import from apollo client it takes the query so in this case the one we set up create post for the mutation rather sorry and inside of this we are going to create a function called update uh update takes the current cache as a parameter and the cache is our global store in our application so if you remember in the apollo client we set up an in-memory cache which is where all of our data is stored so that's what we're going to go and do when we create a new post we want to update the store so that everything is up to date and we don't need to query it to just get back the information that we just created so let's go ahead and update the cache after we create the post so the second parameter is going to be the data that we want to be structured in this case it's gonna be create post this is what we're gonna end up returning so let's go ahead and open brackets here our curly braces and then we're going to call cache.modify because we want to modify the cache and then we can specify the fields that we want to modify so in this case the fields are going to be posts because that is the type that we've already specified in the store posts so we want to update posts and it as a parameter takes the existing posts and we can specify default doesn't already exist it's an empty array and inside of this we're going to create a new const called new post reference and this reference is going to be equal to cash.right fragment because we're writing a fragment to the cache and you can read more about this process and in the apollo client docs i know this syntax definitely took a while for me to get used to it's a little confusing at first but it helps to read up on it so i'll include a link in the description as well so the data that we want to specify now is the create post that we just structured up here in the update call and then secondly we need to specify a fragment that we'll use to update the cache so similarly our cache uses the graph query to specify how we should update it so in this case we're going to specify a fragment we can call it new post and it's going to be executed on the post and we specify the fields that we want to get updated with the information that we got back so in this case we're getting a new id title and body back so that's what we want to update id title and body so now that we've done that we can add semicolon and let's go ahead and return the existing posts as well as the new post reference so that everything that we're returning is all the existing posts and then the new post that we've just written to the cache so lastly we just need to go ahead and return the create post function so that we can use it in our function in our application so in our components let's go back to our posts form component and in handle submit we want to submit our new data to create the post so i'm going to create a new const called create post and we're going to set equal to use create post so this use create post is a function to call itself that expects data to create post input so we can call it inside if on submit that's what just what we'll do so remember it expects as a parameter to create post input which looks like variables and then we specify the input and then we can destructure um the title and the body use es6 syntax here and now if we save okay so now let's go ahead and go back to our app and i've opened up the network tab so you can see what's going on under the scenes here and i'll go ahead and create a post and we'll add some more and if we go ahead and submit the post we can see we have a api response of 200 and we can see we're getting a response back from the server and it looks like our post was created successfully which is great and we can't see this on our app because um by default right now we're just getting the first 10 posts and this is a public api so there's a lot of them but if we were to get back a failure uh the api would have not sent back the response so this is good to see that we're getting a 200 response back and you can see it's a post so this is how we use a mutation so hopefully this video showed you something about how to structure your app with apollo client and or react router how i set up my apps you can really extend it and keep things really modular this way so include a different folder for each type of data you're working here and keep all your hooks and you can share them across components you know create new pages and components and keep things really small and modular and keep all your utility functions you can create a folder called utils in here for example and keep utility functions in here um and so on so the structure allows you to keep things really modular and using typescript is very safe so hopefully this um helped you out and stay tuned because my next video i plan on building out a nest js api a graphql api back end including authentication so stay tuned for that thanks
Info
Channel: Michael Guay
Views: 10,489
Rating: undefined out of 5
Keywords:
Id: lXtdDx9RGzM
Channel Id: undefined
Length: 46min 26sec (2786 seconds)
Published: Sun Nov 08 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.