 Hello everyone! In today's video, we'll be going over Firebase sub-collections inside of our Cloud Firestore SDK. So we'll be going over how we can access our sub-collections and how we can work with them. So here you're going to notice that I have a few documents called Windows and macOS inside of my console. You can see those two documents and we have a children sub-collection inside of those documents for each of these operating systems. And then we can add children to this sub-collection. So for macOS, we have Big Sur, Catalina, and Monterey as our children. And what I can do is go to my web application here and add an option called Linux, and it should show up here inside of my backend. So you can see Linux, it doesn't yet have a children sub-collection, but the moment I come here and create a child called Ubuntu, you can see that when I refresh the page, we should be able to see a collection called Children inside of our Linux document. There it is. So we have our sub-collection and inside of that sub-collection, it's going to have all of our children. So you can add more stuff like Arch Linux and it should show up here inside of our sub-collection. Now, before we jump straight into coding and all of the syntax, I want to tell you about a few theoretical concepts that you have to understand about Cloud Firestore sub-collections. So first of all, you don't have to create or delete collections because the moment you add a document to a collection that doesn't yet exist, Firestore will automatically create that collection. And likewise, if you delete all documents from a collection, that collection will automatically disappear, so you don't have to create or delete collections. And also, the next thing is a collection can only contain documents, so you can't have a collection inside of a collection. You have to have alternating patterns like a collection, a document, a collection, and then a document when you are making references. So the example is this, we have a reference to a document and the path to the document is rooms, room A, messages, and message 1. So you can see it's alternating between collections and documents. So collection, document, collection, document. And you can't do collection, collection, document, document. You can't just have to alternate between the different types. And also this is one of the syntax, one of the ways to do it in version 9. You can use commas to separate the path for each of the collections and documents. Or you could also use the slash notation. And that's more similar to our Unix or Linux file system references. So that should look a bit more familiar. And next, if you're looking for a way to list out all of the subcollections of a particular document, you should know that it's currently not possible to do that with the current version of mobile and web client libraries. And that's because you're only supposed to do that in trusted server environments. And also I just want to point out that the Firebase subcollection syntax for version 9 is superior compared to version 8 because this is like the syntax in version 9. You get to do all of that in one document method, one document function, calling the rooms and whatever path it is. You can even do that in one string using the slash notation. But if you look at the version 8 syntax, you can see that you have to repeat yourself and you have to do .collection, .doc, .collection, .doc. You can call that method multiple times. And over time, that just becomes clunky code. So if you're not already at version 9, I strongly suggest you to migrate over from version 8 to version 9 if it's possible. All right, with all of that out of the way, we can now get straight into the coding. So the first thing you want to do is go and create a React app and remove all of the boilerplate code. So what I've left here is app.js with a div that says operating systems. And then I'm going to have my index.js here. So this is just a clean boilerplate for a create React app. The next thing we want to do is link our Firebase back end to our React application. And if you're totally brand new to this process, I would strongly recommend you to watch my React Firebase tutorial playlist and come back to this video after you're done with that. But for now, I'm just going to link my React application to an existing Firebase back end. So I'm going to go to my React app here and I'm going to just copy this and paste it in my client file here called firebase.js. Again, I'm going to recommend you watch my React Firebase videos if you haven't on how to link and create a brand new Firebase project from scratch and link that to your React application. But for now, I'm just going to quickly initialize Firestore using Get Firestore. We have to import Get Firestore from Firebase slash Firestore. And then we have to make that DB handle using Get Firestore. And you can put the app inside of our arguments. And of course, let's export that handle. The next thing I'm going to do is add two libraries. So yarn add firebase as well as react dash firebase dash hooks. So this is the hooks library that I'm going to be using for my firebase hooks so that I get a simpler syntax from the real-time database handle. So you can just go and look at the documentation in the NPM, the GitHub repository here for firebase v9. This is a very good library for firebase and really simplifies your code syntax by a lot. I'll be showing you how to use this library later on or you could also go and read the documentation here under the GitHub repository by yourself if you want to. But for now, I'm just going to go and look at our React application and this is what we have right now, operating systems. And that is our code here in app.js. So the first thing I want to do is go to my firestore database here in this project shortcut section here. If you don't see it, you can go to builds and you can click on firestore database here. And let's create a new collection here called OSs. So that's just a plural for OS. And when you're creating a collection by default, you always have to create the first document to be placed inside of that collection. So let's create windows. And the name is just going to be windows like that. And right now we don't create that sub-collection of children first. We create the first document and then we create that sub-collection afterwards. So here we have the option to create a new collection here. I'm going to call this children. So this is our sub-collection and inside we're going to have windows XP. So that's the name. It's windows XP. And then we can create more children here. So let's go ahead and create windows vista as well. The name is just going to be windows vista. Let's hit save. So now let's review the structure of the database that we've created here. So we have a root collection called OSs. And then inside of that collection, we have a document called windows and windows is basically a document. And it has a sub-collection called children and all of the different windows versions inside of the children sub-collection. We can of course create another document here. We can name it macOS. And then we just have the name be macOS. And then hit save. So this is our second document and we can have another sub-collection here called children. And then we can have a different macOS versions. So let's add bigsir. So name bigsir. So this is the first child inside of the children sub-collection of macOS. And then now that we have all of this structure, we're going to go and print out all of this data inside of our React application from Firestore. So I'm going to use the React Firebase hooks now to get the real-time data from Firestore. So I'm going to head over to the documentation here. Look for use collection data. I'm going to copy this line here. And I'm going to go back to my VS code and paste it inside of my app component. So I'm going to get rid of the TypeScript because we don't need TypeScript in this project for now. And of course we have to import use collection data. So let's go up here and look for that line and then let's copy that and put that up here as well. That imports use collection instead of use collection data. So I'm just going to change that to import use collection data. I'm going to change the name of values here to be docs just because I find it more understandable that way. So now that we have all of that, you can see that it's going to ask us for a query as well as options. So let's go to the documentation back again. You can see that use collection data says that the options is optional. So we actually don't have to specify any sort of options for now. I can get rid of that. And for the query, I'm going to make a const here and we're going to refer to the collection that we want a query for. For now, let's just go and query our root collection, which is called, let's see, it's called OSS here. So that's our root collection. Let's go and query that. So the first argument for collection is the database handle and I have to import collection here. Import collection to help us generate that collection reference from the firestore SDK. And then I'm going to have to import the database handle as well as import db from dot slash firebase. And then the second argument of the collection function is going to ask us for the name of the collection and it's called OSS. So now that I have that reference to the collection inside of firebase generated and stored in this query variable, I'm just going to go ahead and print out the documents that were returned. So console dot log docs. And then I'm going to go and refresh the page and we should see two, we should see two objects returned in an array form. We have Windows and we have macOS. And those are the two documents that were in our queried collection, which is the OSS collection, Windows and macOS. But what you're going to notice very quickly is that these objects don't return us the subcollections inside of our objects. So let's say I'm going to look at the different versions of Windows. I won't be able to see it for now because it just returns the surface level documents. So let's go ahead and modify the code to return all of our Windows versions. And to do that, I'm going to modify the query here. Instead of just doing it from OSS, I'm going to do OSS. And then Windows is the name of the document. And inside that document, we have the children subcollection. And I can save this and let's refresh that. And you can see that we get our Windows Vista and Windows XP returned to us. Now the alternative way to do this is to use the slash notation, which I prefer because it's more readable. And we can just return a whole string as an argument. So let's save that and see if it still works. And it should still work. And we should get a Windows Vista and XP as well. Cool, but what if I wanted to print out the children of Mac OS instead now? So let's go back to the code and change Windows to Mac OS here. And I can go ahead and refresh this and you should see Big Sur show up here instead of Windows XP. Okay, that's great and all. But now let's go and finally work on the user interface in which we can add new items to our order list right here in React. So I'm going to head back to VS Code and I'm going to get rid of the console log and let's implement. Let's put our loading state to use. I'm going to do loading. If loading is true, then just print out loading dot dot and I'm going to come and refresh this. So we see our loading state and it should disappear after we're done loading. And then let's go ahead and actually make that an order list here. And then we're going to map through the documents that we're getting from Firebase and make a list item for each individual document. So let's do docs dot map. And for each individual document, I'm going to return a list item list item. And then actually I'm just going to wrap everything in a div so that it's easier for us to implement the key for React.js. So I'm going to just use method random for the key for now. I'm not going to use any fancy libraries for simplicity sake. Inside of this div, I'm going to have a list item that says hi for now. So we have hi there. And I'm actually going to change our query from macOS children back to the root OS collection. And you're going to get an error here that says cannot read properties of undefined. So docs will be undefined when this is loading in the loading state and it's fetching data from the API. So I'm just going to put a question mark here. So it only does the dot map method when docs is not undefined. And when it's undefined, it's just going to leave it alone. So let's do that. Refresh this. And we should see two highs because that's what we have here. We have two documents inside of our firestore backend. Instead of just printing out hi, I want to make it print what it's actually getting from the API. So let's go ahead and do doc dot name. That's the name of the property we assigned earlier in the console. So that's refresh and we see windows and macOS. So now all we get is the surface level documents inside of our collection. But what if I want to get the children inside of the windows document? Let's say I want to look at the windows XP and the windows Vista and the different versions of windows. So let's go ahead and come down here. And this is our list item. And right under our list item, I want to actually print out all of the children for this particular document. And in this case, it's windows and macOS. So I'm going to make a component here called children list. And then I'm going to have to make that component children list.js. And then I'm going to do a react functional component called children list. And let's save this and import children list. The reason I'm going to make a new component for every single operating system is because we're going to have to query and make this use collection data hook a lot of times for each individual child. And it's easier to have each component handle their own queries instead of just chucking and clogging up the component with all of the queries for the children inside of this one parent component. So what I'm going to do is I'm going to take this and copy that and paste it inside of our children list component. And then I have to copy the imports as well. And then put it here. And instead of just querying from the default root collection, I'm going to get this query from the path property from the children list component. So let's do path here. And then we have to pass down that problem from within the call here. So path equals two. So let's think about what we have to put as our path here. So the query is going to take in the database. It's going to take in the path. So first of all, our path is going to have to start with OS. So that's the name of the root collection. And then the next thing is going to depend on which operating system we are currently in. So we're going to map through all of the different operating systems here. We're going to loop through Windows, Mac OS and Linux. And we have to change that based on what operating system we are currently in. And I can use the format string here and type in doc.name and then slash children. So slash children is our sub-collection reference. And we are going to pass this path down to our children list property here. And we're going to query our collection based on the path we're in. And then once we have that, we can go ahead and print out our children here. So let's make an unordered list. And here we're going to have a list item. We have to have like a... Actually, we're not just going to print out a list item like that. We have to do something like docs.map. And then let's close that. And then we need, for each document, we're going to return a list item that has doc.name. And we have to wrap that in curly braces, so doc.name. And then we have to give this a key. I'm just going to use math.random here. Math.random. All right. And one thing I almost forgot is the question mark that we need to add in at the back of docs because docs will be undefined when it's loading and initializing for the first time. And then here, let's also use the loading component here. I'm going to return, let's say, if loading is true, then just type loading.dot.dot. And for now, let's save that and check if everything works. Let me refresh here. And I can already see that it's going to load and show us Windows, Vista and XP for Windows and Big Sur for macOS. And now the last thing that we are left with is the add button down there where we can add new fields to our subcollections and as well as our parent root collection. So I'm going to go to my VS code here and make a new component that contains all of those input fields and the add button. So add new.js. And it's a React functional component. And then inside of here, we're going to have, let's say, a list item, right? And inside of this list item, let's have a form. And inside of the form, I'm going to have an input field as well as a button of type equals to submit. And let's call this button add. Okay, so this should be the UI that we need. And then I'm going to go back to my app.js here. And let's go all the way underneath our map function. And let's just do add new, right? And let's see how that looks. All right, so that's our add new component. And that looks great. And I'm going to go to my children list and make an add new component for every child as well. So let's go ahead and do add new. Let's put that there. And then we should see add new for Windows, macOS and whatever we have down here. With our UI components now done, let's go ahead and implement the functionality. So these two add components should add a new document to the children some collection of Windows and macOS respectively. And this add component down here should add a new type of operating system to the root OS's collection. So I'm going to go to my VS code and go to add new.js. And let's go to the form here and let's do on submit equals to a function that can handle submit. And here let's do function handle submit. All right, and in here I'm first going to take the event here so I can do e.prevent default so that the form doesn't refresh the page and prevents the default behavior. And then once we're done with all of the API calls here, once we're done with that, we can go ahead and do e.reset. So this will reset the form and get rid of all of the input that we've typed into the input fields. So let's test that. I should type something new here. Let's hit add and it's going to give me e.reset is not a function. e.target.reset maybe. Let's go ahead and refresh this page. I'm going to type test here again. Let's hit add and yes, it's e.target.reset. Here in my VS code, I'm going to write in the API calls to actually add our documents to our Firestore collection. So the function is called set doc. We have to import that from firebase.firestore. This is an API call, so we have to make it await and we have to make the function asynchronous. And then set doc is a function that takes in two arguments. The first argument is the location in which we want to put the document in. So it's going to be a document reference. And the second argument is going to be the actual data object that we're going to insert into our collection. So let's go ahead and make that document reference first. So const doc ref equals to a document which we have to import from firebase.firestore as well. And then the first argument for our doc reference is the database handle. And then the second is our path. So we have to get this path from our props as well. So this is the location in which we are going to add this document. And there's actually going to be a third argument here and that is the ID of the document. Now, if you didn't want to add your ID and you want firebase to automatically generate a new ID for the documents, you're going to add to this firestore collection. You could just leave this empty and change this set doc to add doc. And it will automatically generate a new ID for each document that you create. But here I'm going to just put the ID in and I'm going to make the ID the same as the document's name, the name of our child here. So where am I going to get that information? I'm going to get that information from the input field that the user has typed in. And to do that, let's go ahead and get the data, and get the value from the input field using use ref from react. The constant name equals to a use ref hook, and we can assign that ref to this input field. So ref equals to name. And then here for the ID field, I'm going to just do name.current.value. That's how we're going to get the value from this input field using refs and react.js. And here in the data object that we're going to push to the document, to the collection, I'm going to have a name property, and the value for that property is going to be named.current.value as well. So let's save that and go to our browser. Let's refresh this, and I'm going to add linux here. So let's add linux, let's hit enter, and we get the doc cannot be called with an empty path. If my suspicions are correct, the error we're getting is because this path property is undefined, and true enough, we forgot to specify the path part inside of our add new component. And in this case, you can just set path to path, and we can just pass down the path from the children list component because it's going to be adding the document to the same collection as the children collection. And here in add.js, the add new is just going to get a path of OSs, and that's the root collection because we're not adding this new operating system to any sort of sub-collection, we're just adding it to the root collection of all operating systems. So if I save that and come here and refresh this page, I should be able to add another unix here, let's hit add, and it should show up. There we go, unix is added, and ideally, we want to sort our query by time added, and we're going to add a server time step to all of our queries here so that we can order it and put the newest at the last location here. And finally, let's test out the sub-collections and see if those are working as well. So we can add Ubuntu, and we can add Arch Linux, and we can add Debian, and sure enough, it's working. I can go to my Firebase console, I can hit Linux, and we should see a children sub-collection, and we have all the different types of children inside of here. With that, we've come to the end of today's video tutorial. If you have any questions, please feel free to put them in the comment section. I'll do my best to help you out, and if you want to refer to the code, I'm going to link the GitHub repository to the code in the video description. And see you all next time.