Build a Photo Gallery With React & Firebase

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] what's going on guys so i'm actually really excited for today's guest today we have sean aka the net ninja and he's gonna build kind of an instagram type photo gallery using react using firebase he's going to add some animations so really cool project and sean is someone that has inspired me a lot over the years and i've actually learned a lot from if you've been living under a rock and you've never heard of the net ninja definitely check out his channel links in the description and he does these really cool in-depth tutorial series that in my opinion are better than a lot of the paid courses out there and he does everything from front end web development to back end to mobile development with flutter and just all kinds of stuff it's amazing the the amount of knowledge that that he has and he's able to explain it in a very understandable way so definitely again check his channel out i'm sure a lot of you already know who he is but today's project is is really really cool i'm sure you're going to enjoy it so let's just get right into it all right then so first of all i want to say a massive thanks to brad for having me on this channel it really does mean a lot because brad's channel has been a huge inspiration not only to me but to countless other developers around the world and it truly is an honor to be here so then what is it exactly we're going to be creating in this video well basically it's this cheap instagram knockoff right here called firegram fire because we're going to be using firebase to power the back end and it's basically a nice simple gallery and image uploader with some subtle animation effects as well so i could upload new images right here let me choose one that i've not already uploaded travel three that's gonna upload it right here and we should see everything scoot around and that new image appear right here as well we can click on one of these images and we get a larger version of it in a modal pop-up so to build the front end we're going to be using react and framer motion for all those little subtle animations like the progress bar and this model right here and for the back end we're going to be using two firebase services firebase storage to store all of these different images and firebase firestore which is a database to keep track of image urls and to give us real-time image data so we don't need to refresh the page every time we add a new image in order to see it so before you attempt this i would suggest that you have at least a basic grasp of react if you don't then brad's got a ton of different react tutorials right here on his channel you can check out first of all so without further ado let's kick this off by grabbing the starter project that we're going to work from so you can find this at the following repo on my github it's called firegram and i'll ask brad if you can leave this link down below in the description so you can go ahead and download these so i've created starter files and also final files for this project if you want the starter files make sure you select the starter files branch from the branch drop down first of all then you can go ahead and download the zip or clone the repo to your desktop if you prefer i'm going to download the zip all right so now i've downloaded it i'm going to extract everything inside that and then go into that folder and then inside here i'm going to rename the project just so it's not as long to just fire gram like so and then i'm going to right click this project and i'm going to open it up in vs code so now i've done that i can see the project right here so then this is the starter react project which i'm going to walk you through in a minute but the first thing we need to do is install all of the dependencies listed in package.json right here so to do that we need to open up a terminal and then i'm going to say npm install to install all of those okay so once we've done that we can start this by saying npm start that's going to spin up the local server for us and we can then preview this in a browser which is opened up over here so let me just place this over here and this is the project so far very basic to begin with all we have is this title up here and a bit of text right here as well and that's all there is to it at the moment so let me just resize this a second so we can see the browser and the editor and i'm just going to walk you through the different files we've got over here now so inside the source folder we have the index.js which is the file that kickstarts the react application we have the app.js which is the root component and this is very simple all we're doing is nesting this custom title component inside that and that is found inside the comps folder that stands for components and again this is very simple this is all the content we see on the screen just a div with the class name of title then we have an h1 that says firegram that's this thing in the top left then we have an h2 your pictures and a paragraph tag under that and finally we have an index.css where all of our styles are kept there are some base styles to begin with first of all i'm importing a font from google fonts and that is noto serif so that's this font right here so underneath that we have some variables declared inside the root selector and that's just three colors one for primary which is this pink color secondary gray and then an error color which is kind of like a pinky red okay so in the body we just specify a font family and the color of that which is this right here then the app itself that's this thing right here that we say we want to have a max width of 960 pixels margin zero and also left and right so we get a central column which is why we have this column of content right here and then after that we have the h1 which is this thing in the top left the firegram bit we give that a color of the primary variable which is the pink font size of 1.2 rems letter spacing of 2 pixels font weight of normal and then the h2 and the paragraph tag we align the text to the center and finally the h2 itself we give a margin top of 60 pixels and a font size of 2.6 rems so that's this thing right here so the next step now is to hook this project up with firebase so then if you've never used firebase before it's basically what's known as a back end as a service and that means it offers server-side services like a database authentication hosting and file storage and loads more all of these different things here so that we can make use of all of these to act as the back end of our website and that way we don't have to worry about setting up our own server to manage these things ourselves now it's completely free to begin with for smaller projects which makes it ideal for learning so definitely sign up for a free account and then head to your accounts console by clicking this thing over here so this is going to list all of your different firebase projects that you currently have we're going to create a new one for this project and i'm going to call this ninja hyphen fire gram call it what you want it really doesn't matter then click continue i'm going to go down here and unclick this we don't need analytics and then we're going to create this project that'll just take a minute or so all right so when that's done click on continue and then it will redirect you to your project dashboard which is where you can manage all of the different firebase services that we're going to use for this project we're going to be using the storage and the database in firebase but first of all we need to create or register a new app so a new front end for this firebase project so to do that we're going to go to a new web app click this icon right here and then give this a name i'm going to call it fire gram and then register the app you don't need to check the firebase hosting unless you want to host this later on on firebase as well so then it's going to give us all of this code right here now all we're going to do is grab this stuff inside the script tag so grab all of that and copy it and then continue to your console so all of that code that we just grabbed we need to paste that inside some kind of config file inside our project so our project right here can connect to this back end this firebase project so what i'm going to do is create a new folder inside source and that folder is going to be called firebase and anything to do with the firebase config is going to go inside here now inside that folder i'll create a new file called config.js and i'm going to paste this stuff right here so we're basically creating a firebase config object and this is the information that our project is going to use to initialize our firebase app and connect to this back end over here so we've done that but we also need to actually install the firebase package on the front end as well because that's what we're using right here so to do that let's go to the terminal i'm going to open up a new terminal and say npm install firebase and press enter so now we've done that we need to import firebase into this file so to do that we're going to say at the top import everything that's what this asterix means as firebase from firebase forward slash app so that's importing firebase from the package we installed and now we can use it down here then i also want to import the storage sdk and also the firestore sdk so let me say import firebase forward slash storage and then also i want to import firebase forward slash firestore so these are the two different services we're going to be using the firestore is the database and storage to store our images all right so we have our config and we're initializing our app that's what then goes out and connects to the back end for us now we need to also initialize the storage service and the firestore service so to do that i'm going to create a constant called project storage you can call this constant whatever you want i just think it makes sense because this is our project storage and that's going to be equal to firebase dot storage and we invoke that function so that starts up our storage service and then anytime we want to do any kind of interaction with the storage service on the back end we use this constant right here and then also i'm going to say const and i'm going to call this one project firestore and set that equal to firebase if i can spell it correctly firebase dot and then it's firestore and invoke that as well so anytime we want to interact with the firestore database we use this constant in the future finally i just want to export those two things so that we can import them into different react components in the future and use them so project storage and then project firestore and that's all there is to it for this file at the moment so we're initializing our app we're initializing our two different services then we're exporting those services so we can use them in other files in the future finally we need to initialize these two different services the storage and the firestore that we want to use on the back into in our project dashboard so first of all let's go to the database over here and what we want to do is set up a firestore database there's two types of database on firebase firestore which is the new one and also the real-time database so let's create the firestore over here and what we're going to do is start in test mode and that means that basically we have access to read and write files from the database if you were going into production mode then you'd probably want to set up some rules so that maybe only authenticated people could read from the database and write to the database but we're going to start in test mode so we don't have to create those rules so i'm going to click on next and then click done again and that's going to create our firestore database for us so there we go and we have a blank database at the minute now we could start a collection of data right here and that's how firestore works by the way we have collections and each collection stores a particular type of data so we could have a collection for users and then inside that collection we store documents where each document represents a single user in our case we're going to have a collection called images which is eventually going to store image urls so we want to keep our database blank for now we don't need to create a collection manually we'll do that programmatically later on what we want to do now is also start up the storage service so i'm going to click on get started and again we have some rules right here what i'm going to do is click on next then we'll change these rules in a second so let's click on done to start up the storage service and once we've done that we can see this empty storage container right here so again we need to go to the rules and we need to make it so that we can easily upload images without being authenticated so what i'm going to do is get rid of all of this section right here so anything after right and then at the end just make sure you've got your semicolon okay so that basically means anyone can read and write to the storage now again if you go into production and you expect people to be using your website you want to make it more secure so that maybe only authenticated users can upload and read from the database so let me publish those rules make sure you click on that otherwise the rules won't save and then go back to files so right here we could manually upload files ourselves by clicking on this and that would upload them to storage but we don't want to do that we want to allow users to upload files from the website itself so next up we need to create an upload form to show on the home page right here which we can click to upload pictures to firebase storage okay so to create the upload form i'm going to make a new component inside comps so new file and this is going to be called upload form dot js okay so inside here first of all we need to import react and then we need to create the component itself which will be called upload form so set that equal to a function and then inside this function all we want to do for now is return some jsx now that is going to be to begin with just a form tag and then in there we need an input field which is of type file so this is the type that we use when we want to allow a user to select a file from their computer now i'm just going to export this at the bottom by saying export default and then the name of the component which is upload form and then we can import this into the root component app.js by saying over here upload form if i select this it automatically imports it at the top close that off and then if we save and preview this we should see it right here so don't worry we will make this look better later on but what this allows us to do is click on this button and then select an image and then we see that image name right here okay now at the minute when we actually select a file like this it's not doing anything it's not uploading that file anywhere and in order to do that we need to be able to react to this event of selecting a file now we can do that by adding an unchanged handler to the input so let's go to the upload form again and then right here i'm going to say on change and set that equal to some kind of function i'm going to call this change handler like so now we need to create this function up here so i'll say const change handler set that equal to a function we'll take in the event object which we get automatically and then down here all we're going to do is console.log and just say changed like so so every time there's any kind of change event on this input field then we're going to fire this function and log this to the console just so we can see when that happens so if i open up the console now by going to console and clicking on this and then selecting a file we can see now it changed at that moment we're going to do something right now if i do this again and click cancel we see changed again if i choose another file we see changed again okay so that's the event that we're reacting to right here now what we want to do here is access the file that the user has selected and we can get that information from the event object so i'll create a new variable right now called selected and i'm going to set that equal to the event then the target and the tag is the import and then we can use a property called files and that gets us all of the files that have been selected now we just want the first file so we pass that into the array format right here to grab the first file that was selected now the reason you could get multiple files is that some uploaders allow you to select multiple files we're just selecting one here so we want the first one okay so we have that file right now and what we could do is we could log that to the console to begin with to see what that looks like i'm going to say console.log selected and save this then let's go over here and select a file and when we do that we can see all of this stuff right here and it has a name property a size a type right here to say the type of file it is etc so we have that file now now what we could do is store this file in a local piece of state now to do that we're going to be using the use state hook so i'm going to say const and then we'll call this piece of state file and we need a function to update the file called set file and set that equal to use state press enter it imports it right here and then we need to set an initial value for this bit of state and that is going to be null so file is null to begin with because to begin with we don't select a file right it's only after we change this thing right here that we select a file so now let's store that in the states now we only want to update this to be whatever is selected if a file is selected because remember if we come over here and cancel then we don't have a file anymore and so at that point we don't really want to set the state so that the file can be updated with this so let us check first of all do we have a file i'm going to say if and then selected inside so this is going to be truthy if we actually have a file and if we don't then it's going to evaluate to false and we don't need to then update the states so at this point what i want to do is use the set file method to update the state of the file and pass in the selected file so whatever we grab here we're just assigning to this thing over here all right so now we have that file stored in local state now we also want to make sure that the file that the user selects is an image file and not something like a video or an mp3 now to do that we can check the file type remember over here we had this type property so we can match that against the allowed types so first of all let's create an array of allowed types i'm going to create that up here so const and i'll call this types and set it equal to an array now inside here i want to specify the different image types i'm going to allow png and jpegs so the first type is this thing right here image forward slash png that is an allowed type so let me paste it in there the second is the jpeg so image forward slash jpeg like so so these are the different types of files we're going to allow users to upload and we only want to then use this set file to apply this value to the file if the type of file they're trying to upload matches one of these things right here so let's add another criteria inside this if check right here so i'll say and and that is to check that the type of file they have is one of these types right here so to do that we can take our types which we have and then we can say dot includes which is a method and then passing the selected dot type which is this property right here so what we're doing is grabbing this and saying okay does that array include the type that the user has selected so if the user selects an mp3 or something else then this is going to evaluate to false if they select one of these it's going to evaluate to true so now we're only using this method to update the file if it's one of the valid types does that make sense okay so if this is not the case then what we want to do is maybe show an error to the user so i'm going to do an else clause right here and inside this else clause i'm going to first say set file to be null again just to reset the value right because they've uploaded something that's invalid or they've not selected anything so let's reset it back to null and then also i want to register some kind of error now again i'm going to store this error in some kind of state so let's create an error up here like so error and set error and set it equal oops spell it correctly first of all set it equal to use state and then to begin with the error is going to be null but at the moment that this fires then we need to say right here select a valid file type so let us say set error and the error is going to be please select an image file and then in brackets png or jpeg all right so we're registering this error if they select the incorrect file type right so now we've done this and every time we change over here then it's going to fire either this or this now what we want to do at this point is either output the file if we have a valid file or output the error so underneath the input right here i'm going to create a div with a class name equal to output and inside this div i'm going to either output the file if we have it or the error if we have it so first of all let's check for an error so i'm going to say error and then double ampersand and then if we have an error we're going to output a div with a class name equal to error and then inside that div we want to output the error itself does that make sense so if we save this now and go over here if we try to select something that is not an image then we're going to get some kind of error and it says right here please select an image file if we then go to choose an image however then that error is still going to stay there so at that point when we choose something that's valid we need to reset the error and that happens right here so let's say now set error and then let that be an empty string so let me save that now if we choose something that is not an image then we get the error file but if we then choose something which is an image then the error goes away okay so likewise if we have a valid file value then i want to show that as well so underneath this over here i'm going to do something very similar i'm going to check for a file and if we have that output a div with the file name right because remember on the file we have a name property on the thing that we select so if we save that and choose something like travel 3 we see this under here i know we have it here as well next to the button but when we style this we're not going to see this over here so i've added it here as well so a user can see what they've selected all right so now that is all working we can select different images we show the image there and if they select the wrong file type then we see an error message but if they select the correct file type again we see the file name instead so to communicate with firebase storage we need to use the firebase storage sdk now remember we already set that up and exported it inside this firebase config file right here we have the project storage there so we can just import this if we wanted to inside this file and use it in here to interact with firebase storage and upload that image but i want to keep all of my firebase logic separate from the component so what i'm going to do is create a custom hook to handle file uploads and firebase storage so to do that i'm going to go into the source and create a new folder called hooks and then i'm going to create a new file called use storage dot js so inside here the first thing i need to do is import a few things first of all a couple of hooks use state and use effect which we're going to use inside this hook so let me say import and we want use state and also use effect and that is coming from the react library so from react and then also we want to import the project storage from this file right here the config file so again let's say import and this time we want the project storage and that is from dot dot forward slash to go into the firebase folder and then forward slash config okay so now let's create this hook now hook and react is just a way to create a small chunk of reusable code and then those hooks can be used in whatever components need them now we've already seen and used one hook already this use statehook right here we use that to create state inside the upload form another hook is use effect which i've spelt incorrectly should be double f and we're going to use that hook inside our old hook right here as well so our hook is basically just a function as all hooks are and this function will be responsible for handling our file uploads and then returning some useful values about that upload such as the upload progress any errors and the image url after it's uploaded as well so let's create this and call it use storage and set that equal to a function and also that function will take a parameter and the parameter will be the file we're trying to upload and that parameter is going to come from this component right here it's the file a user selects so then inside the function the first thing i'm going to do is create three pieces of states and we use use state to do that so we have the progress and set progress and this will be the progress of the upload we have error and set error and that's going to be any errors from the upload and also we have url and set url as well and that is going to be the image url that we get back from storage after the image has fully uploaded okay so now what do we want to do inside this hook well we want to use the storage sdk right here to upload this file that we get inside the hook now once it's uploaded i also want to get that image url and store that in the database and that way our database will contain a list of all image urls and then we can use that data to load the images in a react component now the code to do all of this needs to run every time we get a new file value because that file value could change over time as a user selects different files therefore we're going to put all of our logic inside a use effect hook so what i'm going to do is create this then i'll explain it use effect and this hook takes in a function as a parameter and the second argument is going to be the dependencies now inside the function this is where all the logic to upload the file is going to go and right here the dependency inside an array is going to be the file so what happens here is this function inside use effect is going to fire every time this dependency changes so every time we have a new file value it's going to run the code inside this function to upload that file so the first thing i want to do inside this function is to get a reference to where the file should be saved so i'm going to do a little comment to say references like so and then underneath that i'm going to create a constant called storage ref and i'm going to set that equal to the project storage which we grabbed right here and then say dot ref and this is a method and inside here i'm going to pass in the file oops not in capitals the file that we take into the hawk dot name remember we have a name on that file object that we take in and that could be something like blueberries or travel3.png or something like that so what we're doing right here is creating a reference to a file inside the default firebase storage bucket now that file doesn't exist yet we're just saying that when we upload something using this reference the file should have this name right here inside the default bucket all right so now we can use a method called put on this thing right here and that will take a file and put it in the reference that location so what i'm going to do is take the storage ref then use the puts method and pass in the file like so and this sets about uploading the file to this reference right here okay now this thing right here is asynchronous it takes some time to do and we can attach a listener to it which is going to fire functions when certain events happen so what i'm going to do is attach that by saying dot on and the event we want to listen to is state underscore changed so whenever the state of the upload changes whenever the progress changes or whenever it's complete then we're going to fight a function and that function is the second argument and inside the function we get a snapshot object and that snapshot is basically a snapshot in time of the upload at that moment in time now this state change event might happen three or four five or six times during the cycle of the upload so we might fire this function several times while it's being uploaded now inside here what we'd like to do is figure out the progress of the upload and we can use a couple of the properties on this snapshot to do that so i'm going to create a new variable called percentage and this will be the percentage of the upload the progress if you like and i'm going to set that equal to in parentheses the snap dot bytes transferred which is the property on the snapshot that says how many bytes have been transferred at this moment in time when the function is fired and then divide that by snap dot total bytes and that is the total bytes in the file the total file size and then what we're going to do is times that by 100 so this is just the formula for percentage we take the bytes transferred divide that by the total bytes and times it by 100 to get a percentage and that will now be the percentage of the upload so what i'm going to do is set the value of progress right here to b percentage and that will basically be a number between 0 and 100 so i'm going to say set progress to do that and set it to be percentage like so okay so we also get a third argument right here after this function and that argument is also a function now this function will fire if there's an error with the upload and if that's the case all we want to do is set the error using this function up here and we'll pass in this error now eventually we're going to return that down here at the bottom of the hook so that in the component we use in we can do something with that error if we want to and finally we can pass in another argument and this argument is also a function which is going to fire when the upload is fully complete now i'm going to mark this as an async function because we're going to use a weight inside this function now what i want to do at this point is get the url of the image that has just been uploaded so let me say const url and set that equal to a weight and then we want the storage ref and then we want to get the download url which is a function okay so this is asynchronous it takes the storage ref so it finds the file we just uploaded and then it gets the download url and then we're storing it inside this variable right here now all we need to do is say set url and pass in the url okay now this url doesn't override this url because it's inside a separate scope inside this function we're only updating that value right here all right okay so that's all there is to it and finally what we need to do down here is return some values and those values are going to be the progress the url and also the error so all of these three values here and remember we set all of those inside this upload progress we set the progress right here to be the percentage we set the error here if there is one and we set the url right here after the image has uploaded all right and now we're returning all of those values at the bottom so if we use this hook inside another component then these are the values we're going to be able to access now we need to export this hook so export default and then the name of the hook which is use storage oops not use state use storage like so and just one more thing quickly at this point here once we have the url of the image that we've just uploaded at that point in the future what we're going to do is take that url and save it in a document inside an images collection inside our firestore database and that way we should have a collection of urls for our images and we can use those we can read from those in our app to display images on our website okay but i don't want to do that just yet for now what i'd like to do is use this hook inside some kind of component to show a progress bar using this progress value right here so how are we going to use this hook once a file has been selected inside the upload form well we could just use it in this component if we wanted to but instead what i want to do is create a new component for a progress bar which will show the progress of the upload and we'll use the hook in that instead so let's create a new component first of all and i'm going to call this progress bar dot js and inside here the first thing we need to do is import a couple of things we need to import react so let's do that and also we want to import the use storage hook which we have over here so import use storage from and it's inside the hooks folder and we want the use storage file okay so now let's create this component so const and we'll call it progress bar and set it equal to a function and inside this function let's first of all return some kind of jsx template so return and then inside there this is just going to be a div with a class name equal to progress hyphen bar so we can style it later on and inside that i'm just going to output the text progress so we can actually see on the screen okay so now what i want to do is nest this inside the upload form now before i do that i need to export it over here so export default progress bar like so and then inside upload form i want to output it right here inside the output so i only want to output this when we have a file selected so file double ampersand and then output progress bar like so and by the way the way this works is the right side of the double ampersand is only output if the left side is true so if we have a file then we'll output the progress bar if we don't have a file and this is false or evaluates to false then we don't show the progress bar okay now i want to also pass the file into the progress bar because we're going to use the use storage hook inside this component and we need to pass the file into that so i'm going to give this a prop and set that equal to the file that we have up here this file and i'm also going to pass the set file function so that when the progress is complete we can set the file back to null and then the progress bar doesn't show again so i'm going to pass through set file as well and that is the set file function okay so now if we go to this page over here and i'm going to refresh and if we add a file like travel three then we can see this progress thing right here okay now that inside that progress bar we need to use the hook the use storage hook to upload the file so first of all we need to get the props that were passed into the progress bar and we'll de-structure those so we need to file and also set file we pass both of those in and then we're going to use the use storage hook so i'm going to say const and we're going to get back from this the url once the upload is complete and also the progress of the upload so remember we returned those things down here we're not using the error just yet and we're going to set that equal over here to use storage like so and pass in the file so the minute that we do this this hook is going to fire this thing right here inside the use effect and it's going to take the file create a reference and try to upload that file and then at that moment in time we get values back for progress and then when it's done the url as well so i tell you what for now why don't we just output or log to the console the progress so console.log the progress and the url so let me save this and try to upload a file so i'm going to upload the desk one over here and we can see this value this is the percentage of the progress and then the value on the right is the url now to begin with it's null until the image has been fully uploaded when we have 100 progress and we can now see this thing over here this is the url to the new image which has been uploaded and i can open this in a new tab and we should be able to see that image that is now being hosted over here if we go to storage we should see that image right here so we have successfully uploaded it now okay now what do we want to do while all of this is going on what do we actually want to do with these values well with the progress we want to be able to create a progress bar so i guess this value right here this could reflect the width in percentage of the bar right so to begin with the width is going to be zero and then throughout it could be 39 in width then 95 in width and then 100 in width at the end so we can use that to style this progress bar so what i'm going to do is over here give this a style attribute and then inside pass in an object and this object represents the different css properties now the width property is going to be driven by the progress so that is going to be progress and then we need to tack on a percentage sign at the end so let's do that and now if the progress is zero it's going to be zero percent in width if it's 95 it will be 95 percent if it's 100 it's going to be 100 right so that's just the style of the width but at the minute that won't really do much because we've not styled this progress bar in any other way let me demonstrate this so let me upload something and again i'll upload desk and we can see yeah the progress goes over here but it doesn't really give us much indication to the progress instead it would be nice if we have a background color for the progress bar and that background color starts over here and makes its way over to the right so let's do that instead i'm going to close off that and then we need to style up this progress bar so let's go to index.css and at the bottom over here we'll just paste in this rule and i'm just targeting the progress bar div which is this thing right here and we're giving this a height of five pixels so a thin bar and a background color of the primary color which is pink and then the margin top is just 20 pixels so now we should have a very thin bar and automatically because this is a block level element it should span the whole width but remember the width is being driven by this thing over here okay so now if we save all this and refresh i'm going to upload something and let's just do desk again and now we can see that bar right and when it gets over here that means it's 100 uploaded now first of all we don't need this text anymore that's redundant so we can get rid of that and if we save this it's going to look a bit better like so okay so that's the progress bar but the thing is now we want to remove that progress bar once it's fully uploaded we don't want it to just remain there when it's at 100 now how do we remove it well we need to basically set the value of file back to null because remember we only showed the progress bar if file has a value now we can set the file back to null by using this function that we're passing right here set file but when do we want to do that well we want to do that when we have a url because at that point we know the file is fully uploaded right because we only get that value after the upload is complete so let's use use effect to fire a function when the value of url changes so let me say now use effect and press enter that imports it for me and inside here we need to pass in a function which will run when the url changes so make sure you place this url inside the dependencies okay now inside here we want to say if url so if we have a valid value for url and it's not null or undefined at that point we want to set the file and set file to be equal to null because then the progress bar won't show anymore because this is null right here okay now we also need to pass in as a dependency because we use inside this function the set file like so okay so now if we save that and preview over here if we try to upload a file again it's going to show that progress while it uploads and then once we have the url it takes that bar off and everything looks normal again and that is the kind of experience that we want the users to have let's try it once more with another image so i'm going to use pineapple and we can see the progress and then it should disappear when it's complete awesome okay so now if we refresh over here we should see two images the desk and the pineapple and now what we want to do is cycle through those images right and show them on the screen over here but in order to do that we need to store the urls of these images inside the database so that then we can use that data to show the images so now we need to go back to the hook the use storage hook and it's at this point right here that we want to then save this url to the firestore right because we've saved it now to storage and the image is there in storage we want to make a document which represents this image inside our database with the url of that image okay now then since we're going to be interacting with the firestore we need to import the project firestore from the config over here remember we have project firestore exported at the bottom so we need to import that so let's say project firestore as well and then next we need to make a reference to a collection that we want to save the document to so i'm going to say const and we'll call this collection ref and set that equal to project firestore which we use when we want to interact with the firestore database then we use a method called collection to reference a specific collection now this collection is going to be called images it does not matter that that collection is not yet made over here because when we try to save a document to it if that doesn't exist firebase is going to automatically create it for us all right so now we have that collection ref down here once we have the url we can say collection ref and then we want to add a new document so we say dot add and we pass in an object which represents this document and this object can have different properties now the first property is going to be the url property so we can say the url of this document is equal to the url we have right here now because these names are the same we can shorten this to just url and that does the same thing now i also want to pass through a created at property as well and that is basically going to be a timestamp which specifies the time at which this document was created so that later we can order the documents by this timestamp and show them chronologically on the screen so we need to create this timestamp now the best way to do that with firebase is to create a firebase server timestamp now i'm going to create that timestamp inside the firebase config file over here and i'll do it just beneath these two things and it looks like this we create a constant called timestamp and we set that equal to firebase.firestore.fieldvalue.servertimestamp so this is a special type of data a timestamp data that firebase uses in its firestore so now whenever we want to create a new timestamp we can just invoke a function this function right here because that returns a function and that creates us a timestamp so i'm going to export that right here timestamp like so and then come back to use storage and we can create that timestamp rather we need to import it first of all so timestamp and then i'm going to say const created at is equal to timestamp like so and that's all there is to it and then we're adding that property creating that to this document as well so now once this upload is complete we have the url we're creating a new document inside the images collection to match that image that's just been uploaded and we're storing the url of that image and when it was created okay that's all we're doing right here so let's give this a whirl now what i'm going to do is go back to storage first of all and delete both of these images because they don't have the documents associated with them inside the firestore so let me delete both of them and then i'm going to try uploading a new image so let me upload desk and then once this is complete let's refresh first over here to make sure we have the image and we do and then let's go to the database we should also now have a document inside an images collection that document has a random id which is auto-generated and we have these two properties the created app property and also the url for that image so let's try another one let's upload a second image suitcase and let's now go over to the firestore over here i'm going to refresh to see if we have that new document we don't yet but it should appear there in a second let me refresh again and now we see the second document as well we have both of those now so now we have a database collection of documents with image urls we can now listen to this collection from our website to get all of the urls in real time as they're added into our project now for that we'll be creating a new custom hook called use firestore okay then so now what we want to do is set up a connection between our application and this firestore right here so we can actively listen for documents being added to this collection and then we can retrieve those documents cycle through them in a react component and output an image for each one of them using this url property right here we can also order them by date by using the create.property2. now first of all what i want to do is create components to cycle through these documents and output each image so to do that let's create a new file inside comps called image grid dot js now at the top of this first of all we need to import react and then i'm going to create this component so const image grid is equal to a function and this function inside is just going to return a simple template to begin with so return and then i'm just going to return a div with a class name equal to image hyphen grid okay now inside here i'm just going to say images so we can see this on the screen now at the bottom we want to export this so export default image grid like so now what we could do is import this into our app.js root component so let's do that let's do it below the upload form image grid like so and that's also imported it at the top for me and if we save this and preview we should see images right here so this is where the image grid is gonna go now then we want to access the data from our firestore database so we can cycle through those urls and output images for each one of them and we want to do that inside this image grid components now we could import the firestore sdk into this file and grab the data that way but instead i want to create another custom hook to do all of the heavy lifting for us that way again we make our code more reusable anytime we want to get firestore data from a collection we could just use the use firestore hawk that we're going to create so inside hooks let's create a new file called use firestore.js and inside this file let's create the hook first of all i want to import a couple of things so let me just paste those in we're importing use state use effect and project firestore which is coming from the firebase config file now down here i'm going to create the hook by saying const use firestore is equal to a function and inside this function we're going to take in a collection that we want to get documents from and in our case that is going to be the images collection so we'll pass that in later and then to begin with i'm going to just set up a bit of state for the documents that we retrieve from the collection so const and it's going to be docs and set docs to update the documents and set that equal to use state and to begin with it's going to be an empty array because to begin with we don't have any documents and then at the end of all this we will be returning the docs once we have them now next we need to communicate with the database in order to get the documents and all of our database communication is going to go inside a use effect hook so that it can re-run whenever the collection changes right here so let's create that use effect hook first use effect and inside passing a callback function which is going to fire whenever the dependencies change and that is going to be the collection okay so inside this use effect talk we want to use the project firestore in order to reach into a collection and then listen for the documents inside that collection so i'm going to grab the project firestore and then say dot collection to get a collection the collection is going to be called images but we actually pass that through so we can just paste it in right here this will be a string that we pass in called images eventually and then what i'm going to do is use a method on this called on snapshots now this method fires a callback function every time a change occurs inside this collection and it also fires the callback function once initially as well now this callback function takes in a snapshot object and this snapshot object represents a snapshot at that moment in time of the database collection so it's going to look at this take a snapshot of it and see all the documents inside at that moment in time now like i said when we first do this it fires once and that snapshot is going to contain all the documents but thereafter every time a new event occurs like a new document being added then it gets a snapshot then with all of the documents on as well so essentially we're listening to real-time data updates every time a new image is added to the database then we're notified of that on this snapshot all right so what i'm going to do inside this function is create an array of documents which were eventually going to return so i'm going to say let documents equal to an array and it's empty to begin with then what i'm going to do is take the snapshot and i'm going to use four each on this snapshot and what that does is cycle through the documents currently in the database collection at that moment in time when we get that snapshot so we're essentially just cycling through all of the documents and we get that as an argument inside a callback function for each document so we're accessing each document and what i'm going to do is push data from that document into the documents array now the way we get data from a document is by saying doc dot data and then invoking that as a function and what that basically gets us is the data inside that document we also get access to the id property on a document as well by saying doc dot id now we want to store the data and the id of each document inside each document of the documents array so what i'm going to do is say take the documents first of all the empty array at the minute push a new item to that array and this new item is an object which represents a single document now for each one i want to get the data so doc dot data and we're using spread syntax in front of that which is three dots it gets all the properties from the data which is created out and the url and it spreads those into properties inside this object so we're going to have the url and the created.properties inside this object we also want the id which is doc.id and that gets us this big id randomly generated for each document as well so each document as stored in this array is going to have both of the properties from the data and the id associated with it okay and we're going to use that id for the key inside react when we output each image and we'll see that later all right so then after we've cycled through all of those what we want to do is call set docs to update the documents right here and pass in documents like so and that's this array which we push items to okay so now we've done that and we have the documents there's one more thing i want to do just after collection and before on snapshot i want to order the documents in this collection before we retrieve them and i can do that by saying order buy and then passing in whatever property i want to order by now i want to order by created at which is the timestamp property so i want them in date order and i also want them in descending order so basically the newest first are going to show at the top so we have this listener set up which is retrieving documents once initially and thereafter every time a new document is added to so the documents are always remaining in sync with whatever is stored in the database over here okay now what i'm also going to do is say const on sub is equal to this because this after it's done all that returns a function and that function is used to unsubscribe from the collection so at the moment in time that we no longer want to be listening to that collection to retrieve documents we can invoke this method now at what point do we want to do that well we want to do that if we unmount the image grid component which is what is going to be using this hook to get the data because if we're not showing that component if it unmounts then we don't need those images anymore so what i'm going to do is return over here what's known as a cleanup function so this function is then just going to invoke the unsub method like so and all that does is unsubscribe from the collection when we no longer use it that's all it does okay all right then so that is this pretty much done so now we can go ahead and use it inside the image grid component to get the data that we need so let me say right here const and then we want the docs is equal to use firestore like so and we need to pass in the collection we want to listen to and that is called images that is the name of the collection right here okay now we also need to import this thing so let's do that at the top import and we want use firestore and that comes from dot dot forward slash and then into the hooks folder forward slash and we won't use firestore awesome okay so now this is doing all of the heavy lifting for us it's going to go out listen to that collection in the database and return towards whatever docs we have so to begin with let's just log those out to the console so console.log and then the docs so let me save that and come over here and let's oops does not contain a default export okay so we need to export the use firestore hook export default and we want use firestore like so save that again and this time it should work okay cool and if we inspect now and go to the console we should see this is the data that we have it's an array of two items these are the two documents inside this collection right here and you can see on each one we've got the created at the id and the url okay so we can use this to cycle through those documents and output them right here but before we do that let me just show you something if i add a new item now let me try adding pineapple and once that's done we should get those new documents right here so now there's three in them and it's all real time right we didn't have to refresh or anything because we have this active listener set up inside the use firestore hook right here every time there's a change we're firing this snapshot function and we're updating the docs so it always kept in sync with whatever documents we have inside the database which is nice so now let's cycle through those items and output an image for each one so inside this image grid instead of just outputting this we're going to take the docs so we'll say docs and we want to check that they exist before we output anything so we say docs double ampersand and then say docs.map to cycle through these and output a bit of template for each individual document right here so inside this template all we want to do is return a div first of all so i'll do that first and that is going to have a class name equal to image hyphen wrap this is just so we can style it later and then it also needs a key property and that's something react uses to keep track of these different things we're outputting and we'll use the id property on the document that we have remember on these documents if we open up the console again we have an id property so this thing right here is unique so we're using that for the key and then inside this div let me just close it off we need the image itself so let me now do an image tag and the source is going to be equal to the document.url property we have on that document let's also do an alt so that react doesn't complain and just say uploaded pic if i was you i'd choose a better alt uh description than that but this will do for now so now we can see we're cycling through those and we're getting each of those images displayed on the page right here but it doesn't look great at the minute instead we'd like a grid so what i'm going to do is go back to styles or index.css and i'm just going to paste in a few styles so you don't have to watch me write out the css from scratch but i will explain this the image grid which surrounds all of the images that is if we go to this this thing right here that we display as grid right so that we're displaying the items in a grid and there's three columns each one with equal width one fraction of the grid and the grid gap between each column and each row is 40 pixels now the image wrap which is the individual items that surround the image it's this thing right here that we say the overflow is hidden so that the image doesn't appear beyond that the height is zero and the padding is 50 up and down and zero left and right so we're letting the padding control the height here and that way it's always going to be perfectly square regardless of the width of the page all right so 50 padding top and bottom means it's going to be perfectly square if we have no height then the position is going to be relative and the opacity is 0.8 now this is position relative because the image itself inside is position absolute and we give this a min width and mid height of 100 so it takes at least the full space of the square and also we say the max width is 150 percent so it might be a bit bigger than the square it might be a little bit zoomed in but overflow is hidden so it doesn't matter all right and then it's going to start at the top left so if i save this now the images should look a bit better and that looks pretty good to me so let's just try uploading one more i'm going to cross off this and upload another image travel three and we see the progress bar and when it's done it should appear first because they're all in date order and it does so next what i'd like to do is make a model which opens and shows a full-size image when you click on a small one so to make this model we're going to create a new component called modal so modal.js and inside there let's import react first of all and then create the component so const model is equal to a function and that function is just for the time being going to return some simple jsx and that will be a div with a class name equal to backdrop so this will act as the backdrop behind the model that's kind of like the semi-transparent sheet that's normally fading out the website itself so that the modal stands out and inside that we have the image the image is going to have a source that will be dynamic whatever image we click on so we'll fill that in later and also an alt which you should be more descriptive than me with i'm just going to say enlarged pick all right so that's pretty much it for now anyway we need to export this export default and then modal and now we can import it into the rootapp.js and place it below image grid so let's do that modal like so automatically imports it for me right here now if we take a look at this at the minute then it's just going to be an empty picture with this alt text displayed at the bottom and that's because we have no source inside the image right here what we need this to be is the source of whatever image we click on on the web page so if we click on this one it's the source for this if this is the source for this so we need a way to attach some kind of click event to this which updates that value of the source passes it into the root app component and then that passes it into the modal so we can output it in the source all right so what we're going to do is create a bit of state first of all inside this root component so let me say const and we'll call this selected image and we need a function to update that so set selected image and set that equal to use state press enter to automatically import that at the top and the initial value is going to be null because when we first load the page we've not yet selected an image now we need to update this value inside the image grid when we click on an image so let me copy that value and i want to paste it in here so we can pass it down as a prop all right now we can accept that prop inside the image grid so let me destructure it from the props up there and then down here i'm going to attach a click event to this div right here because we output this div for each individual image so i'm going to do this on the next line i'm going to say here on click is equal to something and that thing is going to be a function and inside here all we're going to do is invoke set selected image and then we can pass some kind of value into this and that is then going to become the value of selected image now we want to pass through the url of the image we want to show so whatever this url is so let me grab that dot url and pass it in to this function so now whenever we click on an image we're updating the value of this thing right here selected image with the url of that image so now we can pass it through here i can say selected image is equal to selected image and now we can access that inside the model over here so let me accept it as a prop by destructuring selected image and now the source is going to be equal to that selected image so if we save this now and come down here to begin with we don't see anything but if we click on one of these now we see that image bigger down there at the bottom now obviously at the minute this looks pretty pants so let's style that up so i'm going to go over to the index.css and come down to modal styles and i'm just going to paste these in it's only two rules and walk you through them so we have the backdrop first of all which remember is this thing that surrounds the image and that backdrop i want to be a faded blanket if you like over the whole screen so it fades the background of the website out and the image on top of that is gonna then stand out a bit more so what i've said is the position should be fixed top zero left zero so it's gonna start in the top left and then the width and height are both gonna be 100 so it's going to span the width and height of the browser and then the background is rgba so we have an alpha channel it's black and also it's going to be semi-transparent so 0.5 is the alpha value now on top of that we have the image inside the backdrop and i've said display block the max width of this is 60 so 60 of the width of the screen and then the max height is 80 of the screen the margin is 60 pixels at the top and bottom auto left and right and that means it's going to position the image in the middle with auto margin left and auto margin right and then we have a box shadow to give it some depth and then finally a border of 3 pixels solid and white so if we take a look at that that's what it's going to look like all right now at the minute we can't get away from that because once we're showing it there's no way to close it if i refresh then we can still see this thing at the top right here because it's automatically showing by default now we only want to show this when a picture has been clicked on so to do that let me go to app.js and right here we want to conditionally render this only when this has a value now to begin with it starts as null so it shouldn't show to begin with and then when we select an image by clicking on it it has a value and then it shows all right so let me say right here around this i want to check for selected image and then double ampersand and only if this is true will this then be rendered so let me close this off with my curly braces at the end so now if we refresh then to begin with it shouldn't show any image because selected image is null and therefore this model should not be showing on the screen but when we click on an image and this is updated then it should show on the screen so save that and to begin with it's not there if we click on an image then it shows but again we still can't close this model so how do we do that well what we could do is attach a click event handler to the backdrop over here so that when we click on that it closes the model now to do that we need to attach that event to if we go to the modal this backdrop right here right and then at that point we need to use this method over here set selected image and set it equal to null again because at that point when it becomes null then we don't show the modal so by doing that we're closing the modal right so what i'm going to do is pass in this function set selected image oops from app.js into the modal as another prop so let me paste that in here and set it equal to set selected image so now we can accept that inside the modal itself over here like so and now i'm going to attach a click event to this thing so oh not the picture the div so on click and set it equal to a function which i'm going to call handle click now i'm going to make that function up here const handle click and set it equal to a function which takes in the event object and inside that i want to basically use this function right here and set selected image to null because at that point now once we've clicked on this it's going to close the modal because when selected image is null we don't show it so if we try this out let me click on one of these things and click over here and it closes it click on this one and it closes it but there is a problem when we click on this or any image for example if we click on the image itself it still closes it now i don't want that to happen but it's happening because the image is inside this backdrop so by clicking on the image we are still clicking on the backdrop so what we want to do is actually check for the event target object and make sure it was this div that we clicked on and not this image which is the source of the events so to do that we can do a little if check and inside here i'm going to say e dot target that's the target element dot class list to get the class list and then use a method called contains and inside that i want to see if it contains the backdrop class because this does right so if we click on this if the target element is the image then this is going to be false so if we click on this it's going to check the class list of this target and it doesn't contain backdrop it's false and therefore we're not going to use this function to set selected image to null only if the target class list contains the class backdrop which this does then it's going to fire so let me save this and try it again and if we click on an image and click on the backdrop it still works if we click on the image itself now it stays open and that is much better okay cool so finally i just want to make this feel a lot slicker by adding some subtle animations and to do that we're going to use the framer motion package so frame emotion is an animation package for react and it makes it really simple to do a lot of really subtle nice animations between your elements so for example if we click on one of these we can see animate to a different size and a different position and we can see some more examples down here so as we scroll we can see this right here animate we can drag things around using frame emotion loads of different things we can do and that's what we're going to be using to create subtle animations in our project so the first thing we want to do is install that package over here so let me open up the terminal and type npm install framer hyphen motion okay so now that's installed how do we use it well first of all what we're going to use it on i'd like to use on a few different things and to begin with we're going to start with these images so that when we hover over them maybe we animate the opacity something really simple and subtle to begin with so what i'm going to do is go to the image grid to do this so the first thing we need to do inside here is import something from frame of motion and that something is motion so import motion from framer hyphen motion all right so now we have that we can use this to turn any element into a motion element so say for example i want to animate this div right here remember we output one of these divs for each image well if i want to apply frame emotion animation to this div i need to make it into a motion element and to do that i start at the opening of the tag and say motion dot whatever element we're using and same again at the closing tag so motion dot div now this right here is a motion element and we can use motion attributes on it now one of those motion attributes is called while hover and we can set that equal to an object and inside here we can specify some different css properties that we want to animate to when we hover over this element so i could say for example i want the opacity to animate to one now remember if we take a look at the css then the opacity to begin with is 0.8 now it's going to animate from that to whatever value we create here so let me test this out it's going to be very subtle but if we now hover over these images we can see they go slightly deeper and that's because the opacity is going from 0.8 to 1. all right another cool thing we can do which is really really simple is add the layout prop to this and what that does is basically whenever this element moves position on the page now this layout attribute will say hey i want you to animate to the new position so you know like when we add a new image over here and the image appears up here everything else has to rearrange now before we have this layout attribute let me just show you what it does save let's upload an image and that can be the cactus so once this is uploaded everything just kind of pops to its new position right this was over here but it just popped to there now if we add the layout attribute then instead of popping to that new position it's going to animate to it it's going to become more slick so if we add a new photo now travel one watch this pretty nice right it's really nice so it just animates to the new position and that was dead simple to do okay so that's all i want to do for these i also want to make this into a motion element because i want to fade in the new images when they first appear so again i can say motion dot image now there's no closing tag so we don't need to do it on a closing tag all we need to do is add in the motion attributes so what i'd like to do is say initial first of all and set that equal to an object and inside here we specify different css properties where we want the animation to start so for example i could say the opacity is going to start at zero that's going to be the initial state of an image now i'm going to add the animate property and set that equal to opacity one and that means i want you to animate to this so it's going to start here at opacity 0 and it's going to animate to opacity 1 and that's when the image first comes onto the page now also i'm going to add a transition attribute and i'm going to set the delay of this to be 1 meaning one second so it's gonna wait a second before it animates onto the page so let's just try this out we can see now there was a slight delay and they all animated onto the page and also when we add a new image let me add this one plain when it first appears hopefully it should wait a second and then fade in and that's a nice effect i think all right so there are more things we can animate we could animate the progress bar because at the minute it's a bit jumpy let me show you an example of that i'm just going to upload i don't know pineapple i think we already have that down here but what the hell and we can see that this progress bar was a bit jumpy i'd like to make that a bit smoother so what i'm going to do is go to the progress bar over here and this thing this style i'm going to animate so i'm going to cut this where we specify the width and get rid of the style attribute and instead i'm going to import motion from frame emotion so we can animate this instead all right and we need to make this into a motion div so let's select there and the closing tag and say motion dot div and now we can add the different attributes on so let's come down here and i'm gonna say the initial is gonna be equal to a width of zero right so the progress bar is gonna start off at zero width which is right and then we want to animate to whatever the progress is so we're gonna pass that in right here and remember this is updating over time so it's gonna animate to those different values over time so if i save this now and try to upload a new image what if we not uploaded yet blueberries i think let's try that all right so you can see it now looks a lot better all right so there's one more thing i want to animate a little and that's the modal because when we click on these it just kind of pops into existence i'd like to animate that a little as well just to make it look a little nicer so let me now go to the modal and again we need to import motion from framer hyphen motion and we also need to spell from correctly all right and then we can turn this into a motion element so let's select there and the closing tag and say motion dot and then what do i want to do for this well this is the backdrop and all i really want to do is animate in the opacity for that just make it subtle so let's say initial is going to be an opacity of zero and then we're going to animate to an opacity of one all right so nice and subtle let's try that out if we click on an image we can see the background kind of fades in a little bit and that's nicer but also it would be nice if maybe this image kind of popped in from the top downwards so it was off screen to begin with and it slides down pretty quickly so let's try doing that as well let's make this image a motion image and then all we need to do in this case is set an initial value and say that the y offset so this right here this is not a css property right most things are in frame of motion but this is not a css property it's basically an offset for the y axis and the y axis is the vertical one so i'm going to set this to be minus let's do this in quotes minus 100 vh and basically what that does is take it off the screen vh stands for viewport height so -100 viewport height means the height of the viewport itself so it takes it above the browser screen by this height so it's definitely going to be above here to begin with so we're taking it from that initial point and then we want to animate to an offset of zero to bring it back down to its original position and that's all there is to it so let me save that dead simple to do but we get a nice effect at the end of this and now if i click on one of these it kind of comes down from the top and it fades in as well the backdrop so that looks a lot nicer alright then gang so there we have it that is pretty much the project complete now there is a bit more you could do with this in fact there's a lot more you could do with this you could add authentication and separate users so that each user can upload their own pictures and maybe see each other's pictures you could also integrate some kind of like system so that we could like each other's pictures as well basically you could just make it into a fully fledged instagram clone now i do have all of the final code for this project right here you just need to make sure you select the final files branch and you can download it over here so if you want to add more to it then feel free and do share your projects down in the comments below so thank you so much for watching hopefully you've learned something of use along the way and i'm not just wasted an hour and a half or something of your time if you like this video then feel free to check out my channel i do loads of similar kinds of tutorials there and once again a huge huge thanks to brad for having me here it's been an absolute honour and a privilege thanks for watching
Info
Channel: Traversy Media
Views: 191,988
Rating: 4.9811392 out of 5
Keywords: react, react firebase, firebase, netninja, net ninja, instagram clone, react photo gallery, react framer motion
Id: vUe91uOx7R0
Channel Id: undefined
Length: 81min 30sec (4890 seconds)
Published: Wed Jul 29 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.