5.1 Getting Employable with Firebase - Building your webapp with React
MartinJ
Posted on April 10, 2022
Last reviewed: Aug 2022
If you're a Firebase expert looking for a quick introduction to React, I'm afraid that this post will probably not be what you want. It is actually the final chapter of a long series designed to introduce IT students to Firebase. It assumes no previous experience and so spends rather a lot of time explaining concepts and techniques. It's a long read.
That said, you might find the post a refreshing change from the usual brisk tech dumps. Modern software architectures are quite magical constructs and it's nice sometimes to kick back and take some delight in their elegance and ingenuity. Then again, even if you're in a hurry and just want a quick example of how asynchronous Firestore code can be embedded in a React framework, you might still find it useful to skip ahead and check out the code snippet at Section 5.6. I struggled to find much relevant web-based guidance in this regard.
1. Introduction - putting away the roller skates
Still with me? In some respects, I write this post with a heavy heart. I've worked in the IT industry for rather a long time now and, overall, have enjoyed my work - but I have to say that it's sometimes been a rather tedious existence. My biggest complaint concerned the tools I had to use. They did the job but weren't exactly a lot of fun.
Last summer, when I encountered Firebase for the first time, it's no exaggeration to say that I was completely stunned. The sense of freedom created by that combination of local Javascript working seamlessly into the Google Cloud platform literally took my breath away. I don't think I've been so happy since, many years ago, someone kindly gave me a set of roller skates as a Christmas present. They had steel wheels. Sparks flew! I was mobile!
Unfortunately, I have to tell you that, when it comes to "employment" in the IT industry, employers don't generally look at all kindly on IT staff zooming around on roller skates wearing broad grins and showering sparks in all directions. They want control, discipline and reliable systems. They are oblivious to the fact that achieving this takes all the fun out of the job. But, because they pay your mortgage, one has to listen to what they say. Also, at the end of the day, discipline and reliable systems are in everybody's interest. No? Sigh ...
So, in time, those steel wheels wore down to nubbins and, with my knees covered in scabs, I put away the roller skates. I became useful (or at least began to do what other people were kind enough to say was useful) - and certainly a lot less noisy!
In the absence of a disciplined approach, freestyle, "roller-skate" Javascript can land you in a lot of trouble. Let me try and explain.
2. What's wrong with freestyle Javascript?
So far in this series, the code examples I've introduced have all been snippets designed just to illustrate particular concepts. Serious applications, by contrast, are of course long and complex. Eventually, and especially if the code is so long that a team of developers has to be deployed, strict discipline is essential to ensure that the work progresses systematically. Standards rule the working day. Sadly, too, individuals can find themselves pinned into specialist positions where repetitive, routine tasks are the norm.
Although the scripts I've introduced in this post series are very short, I think it's already possible to detect the beginning of cracks that will only widen and deepen on more complex applications.
- If you look back at Post 2.2 - Coding a simple webapp you'll see comments about the uncomfortable separation of the HTML code specifying the screen layout between the index.html and the index.js files. Of necessity, the dynamic bits of the layout (ie the bits that are generated from your database content) come from the .js file, but as I explained, this is not the best place to create a clean, comprehensible view of the design. So my advice was to put as much of the layout as possible into the .html file. You might easily accommodate this inconvenience during initial development but I can guarantee that you would find it represents a major obstacle to subsequent enhancement and maintenance.
- It's also possible to see that an arrangement where you routinely update the display by refreshing entire
<div>
s may become an efficiency concern. Updating the DOM is a heavyweight process for the browser. Supposing that you just want to update one element of a complex tree structure. To save the browser work you might take on the burden of locating and updating this tiny element yourself. But the cost of this would be felt in terms of the increased complexity of the application code. Wouldn't it be nice if you could give this to some clever bit of library code? Keep this thought in mind. - Moving on, I think Post 2.2 reveals another crack in the architecture. The post describes the difficulties that arise when, in order to use modular Firebase 9, index.js itself has to become a module. This means that buttons defined in the index.html file can't "see" functions in the index.js file because these are no longer in the same "namespace". Consequently, the links need to be created dynamically once the DOM has been loaded. In a sense the resultant loss of clarity in your code is just another manifestation of the "separation" issues already described, but here's another thought - if Google thinks that it's a good idea to write their Firestore library code as modules, shouldn't you be doing the same? Remember that the idea of "modular" code is to take advantage of modern Javascript arrangements that allow you to take a "component", "object-oriented" approach to your coding. The idea here is that you create a discipline in which the only things visible outside a component are the results that it explicitly returns. The internal workings are invisible. Once such a component has been tested independently, it can be deployed anywhere with total confidence that it will continue to run as intended and can be relied upon not to upset anything else. This paves the way for reuse. Keep this thought in mind too.
- The further you go with freestyle Javascript, the more you will start to appreciate the need for a more disciplined approach. You've probably already had just as much as you can take of this, but I'll just mention one more issue because it's quite important.
3. Javascript for grown-ups - libraries and frameworks
I think it's fair to claim that the 2020s will represent the IT world's "Javascript decade". The basic language itself has seen much improvement through the incorporation of concepts like the "import" statement promoting the use of modules and the "await" keyword that makes asynchronous code so much more readable. Javascript is now the dominant language for client-side development. But none of this by itself has made the problems I described in the previous section go away. So you won't be surprised to learn that many clever minds have been working busily on solutions
The solutions basically fall into two groups - libraries and frameworks. To quote freecodecamp.org, these are both just
code written by someone else that is used to help solve common problems.
The difference lies in the degree of control you have over the way you deploy this code. Again to quote freecodecamp:
When you use a library, you are in charge of the flow of the application. You are choosing when and where to call the library. When you use a framework, the framework is in charge of the flow. It provides some places for you to plug in your code, but it calls the code you plugged in as needed.
Popular libraries are React and Backbone. Popular frameworks are Angular and Vue. My apologies if I haven't fingered your personal favourite here - the list grows almost daily.
As an innocent observer of this chaos, I can't help feeling that this chaos represents a major problem in itself. There's a sort of Darwinian struggle going on here and while, generally, it's correct that "survival of the fittest" is the best and fastest way of ensuring that the software industry gets itself properly oriented towards the future, people are getting chewed up in the process. Despite the universal claims of "ease of use", for someone seeking to make a career in IT, choosing between of these niche solutions represents a big decision and a major investment. Nevertheless, if you're going to make yourself attractive to an employer as a Javascript coder/designer, you're going to have to get familiar with at least one of these systems.
It's a problem right now for me, too, because I'll make no progress at all with this post unless I make a choice myself.
Bearing in mind my natural inclination towards "free-wheeling" code I'm more in favour of a library than a framework. I like to be in control and don't take kindly to being told what to do. Then, looking at the take-up of library solutions, I note that right now (APril 2022), React seems to be getting the most support.
So - React it is.
4. A popular library choice - the React library
Because React is just another Javascript library (ie you just "import" its functions in exactly the same way as you load your Firestore routines) it retains more of the character of a pure Javascript application than its framework competitors - albeit that this is Javascript that will stretch your understanding to breaking point!. It also comes with an excellent pedigree - it was originally developed by Facebook to deliver their own web interface and is now used by many other major players in the IT industry (eg Netflix and Dropbox).
React is universally acclaimed as "easy to use" but I'm afraid it comes with a perfect blizzard of new concepts and terminology. However, as I said earlier, in the end it's just Javascript and, while getting to grips with React will be a struggle, you will at least be developing your basic skills. By contrast, the framework alternatives will be just as much of a challenge and will be teaching you things that really only apply to that particular framework.
5. A React webapp example
To put some flesh on this I propose taking you through a six-stage process that leads eventually to a cloud-based webapp featuring a simple interactive React screen layout feeding on a Firestore database. On the way I'll introduce you to key React concepts such as "the Virtual DOM", JSX, Components, Props and State. I'll also do my best to explain how I think they fix the problems I identified earlier.
I think that the nub of these problems lies in the index.html and index.js scripts' shared responsibility for defining and maintaining the DOM. React resolves this problem by making the index.js file alone the responsible party. It does this by giving you a language called JSX to specify your layout through its library functions and using this to create a "virtual Dom" to communicate between your JSX and the real DOM in the browser.
This sounds like a backward step - another language!? But stick with it - it's not so bad. JSX looks very much like HTML, so it's not a big learn at all and indeed introduces some improvements.
But you may also wonder how this "virtual DOM" is to be transmitted to the real DOM. Again, not to worry, this is the really clever bit. React takes complete responsibility for managing the real DOM, and, moreover, does it in a way that ensures optimal performance. When it decides that the DOM needs to be refreshed, it looks at the virtual DOM and transmits only the bits that have changed.
If you look at the React organisation's website you'll find that it makes much of the claim that its architecture follows a "declarative" model (as opposed to the alternative "imperative" approach). To paraphrase Nathan Hagen on stackoverflow,
a "declarative" language allows you to do something like getting your butler (as if!) to organise your evening's dinner by just telling him "a meal for two please". By contrast, an "imperative" approach would require you to tell him (or her) to "go to the fridge, get out the meat, etc, etc".
Well, this claim may be a bit of an exaggeration, but it is certainly true that React's design is going to eliminate a lot of tedious drudgery from your code and will enable you to concentrate your energy on the things that matter.
- 5.1: a quick introduction to the JSX language and React Components
So far, so good. Let's dive straight into the React code for a simple example. Let's say that I'm maintaining some sort of list defined in my application and I want a layout that displays a "Refresh List" button followed by the current content of the list.
Once we've moved on a bit, you may imagine that I shall want the content of the list to be derived from a Firestore collection. Then I'll also be looking to make an "onClick" on the "Refresh List" button re-draw the list with any changes that may have arrived. But just for now, I'm going to create a purely dumb button (ie it won't initially be capable of creating an "onClick" event) and the list content will be laid out explicitly in the list definition.
Here's how this might be specified in a React webapp
import React from 'react';
import ReactDom from 'react-dom';
import './index.css';
class ListButton extends React.Component {
render() {
return (
<div>
<button>
Refresh List
</button>
<ListContent />
</div>
)
}
}
class ListContent extends React.Component {
render() {
return (
<div>
<p>Current List</p>
<br />
<ul>
<li>Shoes</li>
<li>Ships</li>
</ul>
<br />
</div>)
}
}
// ========================================
ReactDom.render(
<ListButton />,
document.getElementById('root')
);
OK, there's a lot to take in here, so let me take things in order of priority.
The action really starts at the very bottom of the index.js script where I call a "render" method on a "ReactDom" object. If you look back to the very top of the script you'll see this has to be imported from a "react-dom" library - I'll come back to where this is located in just a moment. ReactDom.render() is the bit that will actually create the initial layout on your webapp page. This takes two arguments: a block of JSX code defining the initial layout and a pointer to an HTML element declared in the index.html file for the project. Here's my index.html code:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="description" content="Web site created using create-react-app" />
<title>React App</title>
<link href="index.css" rel="stylesheet">
</head>
<body>
<body>
<div id="root"></div>
</body>
</body>
</html>
Let's concentrate on the JSX code in ReactDom.render()'s first argument - the first example of JSX that you've seen. This just reads:
<ListButton />
Clearly, this isn't exactly HTML - but it's not a million miles away. Something calling itself "ListButton" is being invoked as a tag name. In this instance "ListButton" is being defined earlier as an object:
class ListButton extends React.Component {
render() {
return (
<div>
<button>
Refresh List
</button>
<ListContent />
</div>
)
}
}
ListButton is an example of a React Component - the most important element in the React design. The code above is defining ListButton as a Javascript object - "class" is an extension to the Javascript language by ES6 (an extension to the Javascript standard released in 2016). Objects have always been available in Javascript, but the original form of the language took a rather eccentric approach - at least it was quite distinctly different from the approach taken in Java, the first language in which object methods became popularised. Introducing the "class" syntax to Javascript was seen as a way of widening the appeal of the language and perhaps this is the reason why React chose to adopt it. Actually, it's very easy to follow. The code above is just a Javascript statement initialising a new class called ListButton that inherits all the characteristics of a REACT.Component class. Inside the class definition, the script adds a new render() method. In this case, ListButton.render()
returns another stream of JSX. As you can see, this stream looks a bit more like HTML than the bit you saw in ReactDom.render()
- at least there are <div>
tags and so on there. The only pure JSX bit, really, is the <ListContent />
line at the end, which you'll now recognize as a reference to a subordinate ListContent component. And indeed, looking down a bit further, you'll see that this in turn is declared as another Class with a render()
containing the JSX that displays the <li>
entries making up my list display.
At this point, I imagine your head is bursting with questions:
- These scripts won't run in the browser as they stand - the index.html doesn't even invoke the index.js script, and even if it did, I've not provided a source for the React libraries. How is this ever going to work?
- The JSX code won't do anything until it's turned into HTML - are the render() methods going to deal with this?
- If I do somehow get to a point where I've got something that will actually run in a browser, exactly where is it going to run - locally or do I have to worry about deploying to the GCP at this stage too?
- And, even if I can run the code locally for now, given that ultimately I will want to deploy to the cloud as a webapp, how exactly is this to be achieved?
With the experience you've had earlier in "webpacking" modular Firebase code (see Post 3.1), I'm sure you won't be too surprised to realise that something is going to have to "build" your script. This in turn implies that this is going to involve the use of "terminal" sessions and that, before you can start typing code in, there will have to be some sort of preliminary work to structure your project into whatever form that the build process expects and to install the React libraries that it will use. But you've been through all this with Firebase and actually, it's not at all difficult - especially so since, to answer the third question, the code you build at this stage will be launched directly into the browser using a local server created by the build process. I'll answer the last question and describe how you move on to deploy to the cloud when you've added Firebase code that actually needs this.
- 5.2: Configuring a React project - the default logo
Here's a React install procedure that you can follow to run the code above:
Assuming you're using VSCode, add a new folder to your workspace for the new project, open a new terminal session on the new folder and enter the following command:
npx create-react-app .
The . at the end of the command directs create-react-app to create a React folder structure in your new folder - check the session's output to ensure that this is the case. (In passing, if you ever find you need to abort a terminal process, pressing ctrl C key will usually enable you to regain control).
Since the structure contains a default React script, you can now proceed immediately to try to run this by entering the command:
npm start
If all goes as it should, this will start a server that will launch a page for http://localhost:3000/
in your browser displaying (after a slightly worrying pause - try not to panic!) the following image:
If you have problems with any of this, the online documentation can be found at Create React App. I've simplified the instructions you'll find there a little to suit present circumstances.
Incidentally, I'm unclear about the exact status of create-react-app
- it is described as "officially supported", which I take to mean that the open-source community that maintains it is linked in some way to Meta, the originators of React, in some way. Whatever, they seem to be the best source of "nuts and bolts" implementation documentation and create-react-app
works a treat. Thank you guys - you're doing a great job!
- 5.3: Building your first React app - Shoes and Ships
Hmm, that wasn't too bad! Now how would you go about replacing the default page with your own code?
If you look at the structure of the code that's been created in your project by create-react-app
, you'll see that the logo display iss coming mainly from a public/index.html
file and a src/index.js
file.
Replace the content of these files with the index.html
and index.js
source introduced above.
Finally, for good measure, replace the content of src/index.css with the following:
body {
font: 14px "Century Gothic", Futura, sans-serif;
margin: 20px;
text-align: center;
}
ul {
width: 10%;
margin-left: auto;
margin-right: auto;
text-align: left;
}
If you now look in your browser again, you'll find that the http://localhost:3000/
page has now magically been refreshed and is displaying the React output generated by your new code. The server launched by npm start
is still active and is monitoring changes to your source files - whenever one of them changes, the webapp is automatically rebuilt and redisplayed. This is what your http://localhost:3000/
page should look like:
If you try this when the terminal session is actually visible, you can see the process in action. The server is using webpack to perform a build. I understand that it is also using Babel to "transpile" the JSX into plain Javascript in order to save the render() function from having to perform this task repeatedly when the webapp is actually running.
- 5.4: Introducing React "props" - Cabbages and Kings
I think you'll agree that this is a pretty impressive start! But I'm still a long way from making my "List Refresh" webapp from doing what I want.
In the first instance, the "Refresh List" button isn't actually clickable. Where I'm going with this ultimately, is to have the onClick button run some sort of function that draws down the current list content from a Firestore collection. I'm still some way off this, but you can see immediately that this will create a requirement for me to take the list data constructed in the the ListButton component and pass it to the ListContent component (this being where it is to actually displayed - or "rendered" as React would like me to say).
Just to get the show on the road, I'm going to construct some fixed List Content in the ListButton component and use React's "properties" construct to past them to ListContent.
Properties (or "props" as React asks us to reference them) are essentially component parameters. Props can be assigned names and can be referenced within a component by a JSX reference of the form {this.props.parametername}
. In turn, a prop can be passed to a component by a JSX reference of the form <Component parametername = {"string"}/>
To see this in action, edit the two components in index.js so that they take the form:
class ListButton extends React.Component {
render() {
return (
<div>
<button>
Refresh List
</button>
<ListContent line1 = {"Cabbages"} line2={"Kings"}/>
</div>
)
}
}
class ListContent extends React.Component {
render() {
return (
<div>
<p>Current List</p>
<br />
<ul>
<li>{this.props.line1}</li>
<li>{this.props.line2}</li>
</ul>
<br />
</div>)
}
}
Note that here (and indeed at every stage of the development of this example), I'll change the content of the list so that you can check that you are really running the code that you think you are).
When you re-save index.js, the page at http://localhost:3000/ should now display the following:
- 5.5: Introducing State - Pepper and Vinegar
This is starting to take shape now, but I think you can see that there will be a problem when I get to thinking about building the onClick function. This will want to build the list content in a variable and it's not clear yet how you might use this as a parameter to ListContent (in the example above I'm using fixed strings).
To provide "working storage" within a component, React provides a concept that it calls "State". This is particularly useful because it's persistent working storage in the sense if I set a variable in the state for a component it will still be there should I come to re-enter the component.
The state for a variable stateVariableName
in a component can be referenced using syntax of the form {this.state.stateVariableName}
. In the code below, when creating an onClick function for the Refresh List button, I set this to an array of objects, one object for each line that I want to appear in my List table. The structure of each object looks a bit odd, consisting as it does of two properties: a numeric id
and a string item
. The list needs to be structured in this way because, now that I'm passing an item list of unknown length, I can no longer pass a parameter for each individual line. Instead, I need to use a slightly fierce-looking piece of JSX facility to take the array of list entry objects and chew through them with a Javascript map
function to produce an <li/>
entry for each. The id
field is required in order to enable JSX to keep everything in the right order. The "item" fields contain the individual list entries
<ul>
{this.props.propsListContent.map(listEntry => (
<li key={listEntry.id}>{listEntry.item}</li>
))}
</ul>
In order to use a state variable in ListButton I first need to declare it. I do this by adding a "constructor" method to the component. This takes the form:
constructor(props) {
super(props);
this.state = {
stateListContent: [],
};
}
Here I'm initialising a variable called stateListContent
as an empty array. Then inside the render method I create the onClick
function on the ListButton itself and use this to give the state variable a value:
<button onClick={() => {
let listContent = [{ id: 0, item: "Pepper" }, { id: 1, item: "Vinegar" }];
this.setState({ stateListContent: listContent });
}}>
The setState
function is used to update the state variable because this delivers a critical part of the React system's design - it triggers a React "render" - ie a review of the changes that have taken place in the virtual DOM and a consequent appropriate refresh of the real DOM delivering the webapp page in the browser. Here's the completed code:
import React from 'react';
import ReactDom from 'react-dom';
import './index.css';
class ListButton extends React.Component {
constructor(props) {
super(props);
this.state = {
stateListContent: [],
};
}
render() {
return (
<div>
<button onClick={() => {
let listContent = [{ id: 0, item: "Pepper" }, { id: 1, item: "Vinegar" }];
this.setState({ stateListContent: listContent });
}}>
Refresh List
</button>
<ListContent propsListContent={this.state.stateListContent} />
</div>
)
}
}
class ListContent extends React.Component {
render() {
if (this.props.propsListContent.length === 0) {
return (<div />)
} else {
return (
<div>
<p>Current List</p>
<br />
<ul>
{this.props.propsListContent.map(listEntry => (
<li key={listEntry.id}>{listEntry.item}</li>
))}
</ul>
<br />
</div>)
}
}
}
// ========================================
ReactDom.render(
<ListButton />,
document.getElementById('root')
);
Note that, since the stateListContent variable now gives me a firmer grip on the state of the list content, I've used its array length to suppress display of the "Current List" header line until the list actually has some content. So when you run the webapp now, all you will see is the "Refresh List" button.
When you re-save index.js, however, here's what you should see in your browser's http://localhost:3000/page
after you've clicked the "Refresh List" button:
This has all been a bit fierce, and I know that I've not explained everything completely - what's the super
" reference in the constructor for example, and, for that matter, what is the this
in this.state
. However, this post isn't intended to be a complete guide to using React so, if you don't mind, I'll refer you to React's own training material at this point:
You'll find that React provides a very useful online tutorial that should help explain and reinforce the concepts I've introduced above.
However, the "tic tac toe" application used by the tutorial is another example of local operation (ie like the one you've just constructed above) and it's hard to find anything that explains how you might deploy your webapp to a remote server or how you might incorporate asynchronous code.
Fortunately, this turns out to be perfectly straightforward.
- 5.6: Adding asynchronous Firestore code - Butter and Oysters
This is a bit scary because the last time you created a Firebase project in this series (see Post 2.1 you started by installing Firebase tools and configuring your project folder using the "init hosting" procedure. However, your project folder has already been structured by create-react-app
- are the two systems going to collide? Create React App's documentation at Deployment suggests not, so on we go.
Start a new terminal session for the project in VSCode and (unless you've already done this using the -g flag for an earlier project and installed globally), install Firebase tools:
npm install -g firebase-tools
Log into Firebase
Firebase login
and then configure firebase hosting with
firebase init hosting
Confirm that you require "hosting for a current project" and select the Firebase project that you've created for the project as the host.
Configure "build" as your public directory (super important), select the "single-page app" option and decline any offer to have Firebase overwrite your src/index.html.
Note that there's no need to install webpack explicitly as I did in post 3.1. Although we'll be using firebase ECMA libraries and will therefore need webpack, you'll find that you get this automatically courtesy of create-react-app
.
Once the firebase hosting configuration completes, re-enter your project in the Firebase Console and create a collection to provide some ListContent content. The code we'll be using below assumes that you've called this "list" and that it contains a couple of records with a single data item in each called "item". In my example, I've made the items' string content "Butter" and "Oysters" respectively.
You now need to edit the src/index.js file in your project folder and turn it into a Firebase app. First, you need to add the usual Firestore import
commands and then the Firebase configuration code for your project (which, you'll remember, you can copy from your project's settings page in the Firebase console
The main task now will be to supply the code to make your "Refresh List" button's onClick function populate the listContent array with data from your firestore collection. This is achieved by simply replacing the synchronous onClick function from stage 5.5 with a straightforward piece of boilerplate Firestore CRUD code reading the entire content of a collection (see Post 2.3 for a reminder). In other words, you need to replace the synchronous function in
<button onClick={() => {
let listContent = [{ id: 0, item: "Pepper" }, { id: 1, item: "Vinegar" }];
this.setState({ stateListContent: listContent });
}}>
Refresh List
</button>
with an asynchronous Firestore version
<button onClick={async () => {
let listContent = [];
const listCollRef = collection(db, 'list');
const listSnapshot = await getDocs(listCollRef);
listSnapshot.forEach((listDoc) => {
listContent.push({ id: listContent.length, item: listDoc.data().item });
});
this.setState({ stateListContent: listContent });
}}>
Refresh List
</button>
The reference to listContent.length
in listContent.push({ id: listContent.length, item: listDoc.data().item });
is just a convenient way of creating a numeric sequence for the values of the id
property.
As before, when the asynchronous code completes, the setState function, as well as updating the stateListContent
variable, will also trigger a DOM refresh.
Here's what your completed code should now look like (though with the content of firebaseConfig
replaced with the keys etc for your project:
import React from 'react';
import ReactDom from 'react-dom';
import './index.css';
import { initializeApp } from 'firebase/app';
import { getFirestore, collection, getDocs } from 'firebase/firestore';
const firebaseConfig = {
apiKey: "AIzaSyCT4Gv1k9P --- VDDep8NCrdFMHRoq8", //"obfuscated" code
authDomain: "react-expts-app.firebaseapp.com",
projectId: "react-expts-app",
storageBucket: "react-expts-app.appspot.com",
messagingSenderId: "666442781633",
appId: "1:666442781633:we --- cfae6f9540" //"obfuscated" code
};
const firebaseApp = initializeApp(firebaseConfig);
const db = getFirestore(firebaseApp);
class ListButton extends React.Component {
constructor(props) {
super(props);
this.state = {
stateListContent: [],
};
}
render() {
return (
<div>
<button onClick={async () => {
let listContent = [];
const listCollRef = collection(db, 'list');
const listSnapshot = await getDocs(listCollRef);
listSnapshot.forEach((listDoc) => {
listContent.push({ id: listContent.length, item: listDoc.data().item });
});
this.setState({ stateListContent: listContent });
}}>
Refresh List
</button>
<ListContent propsListContent={this.state.stateListContent} />
</div>
)
}
}
class ListContent extends React.Component {
render() {
if (this.props.propsListContent.length === 0) {
return (<div />)
} else {
return (
<div>
<p>Current List</p>
<br />
<ul>
{this.props.propsListContent.map(listEntry => (
<li key={listEntry.id}>{listEntry.item}</li>
))}
</ul>
<br />
</div>)
}
}
}
// ========================================
ReactDom.render(
<ListButton />,
document.getElementById('root')
);
So, you're getting close now. But obviously, you can't run this code using the local server in the way outlined in section 5.5. The aim now is to create a webapp that will run on the web accessing a Firestore database on the Google Cloud Platform. How, exactly is this to be achieved?
Once again, Create React App - Deployment reveals that the procedure is actually perfectly straightforward.
First of all, you now need to run a React "build" process using the software installed earlier with create-react-app. So, back in the terminal session for your project, run
npm run build
Wow, that was scary. Here's the output from the build run on my own project:
> temp@0.1.0 build
> react-scripts build
Creating an optimized production build...
Compiled successfully.
File sizes after gzip:
112.16 kB build\static\js\main.e4fb0c28.js
173 B build\static\css\main.f0ad7978.css
The project was built assuming it is hosted at /.
You can control this with the homepage field in your package.json.
The build folder is ready to be deployed.
You may serve it with a static server:
serve -s build
Find out more about deployment here:
https://cra.link/deployment
It seems then that this process is the equivalent of the explicit webpack run that you saw previously in Post 3.1, except that the output has been placed in the project's "build" folder. Is the project configured in such a way that a Firebase "deploy" command will pick it up from here? Let's find out. Enter the following command (I've included an --only hosting
flag to save you waiting for function and rule deployments that would be launched by a naked firebase deploy
command if your project contains code left over from experiments with previous posts in this series )
firebase deploy --only hosting
and observe the resultant output:
=== Deploying to 'react-expts-app'...
i deploying hosting
i hosting[react-expts-app]: beginning deploy...
i hosting[react-expts-app]: found 12 files in build
+ hosting[react-expts-app]: file upload complete
i hosting[react-expts-app]: finalizing version...
+ hosting[react-expts-app]: version finalized
i hosting[react-expts-app]: releasing new version...
+ hosting[react-expts-app]: release complete
+ Deploy complete!
Mega wow! When I tried my webapp at its project address (this was https:react-expts-app.web.app
in my project but I guess will be something different in yours) I saw, first, my "Refresh List" button, and then, when I clicked the button, the List content from my project's List collection.
Time for a cup of tea, I believe!
6. Wrapping up
I suggested back at the beginning of this post that the problems with freestyle Javascript were caused mainly by the potential for confusion over the responsibility for defining the HTML specification for a webapp's screen layout. I think its fair to say that React's "virtual DOM" succeeds pretty well in restoring clarity here. I think it also lays down a clear component structure for the layout aspects of a webapp's design, paving the way for systematic testing and code re-use.
I've not shown how React would handle the single/multi-page issues described earlier (this post is way-overlong already) but I think you can readily imagine that the architecture can be easily extended to provide the necessary support. React refers to the individual pages in a multi-page layout as "Routes" and arrangements here will be described in a later post
There is a cost attached to these gains. If you want to get serious about React I think you need to spend a bit of time rehearsing your approach to webapp design. For example, how exactly do you choose appropriate components for your application? I found React's Thinking in React document extremely helpful. In fact I think,once you've paddled around a little and got some measure of what React is all about, you really need to read this document. It contains some excellent advice that will save you a huge amount of time.
One of the points that the React document makes is that you should start your development by building a "static" model first, ie one without any interactivity and state-related logic. To that, I would add the suggestion that you should also avoid any use of Firebase code initially - feed your system from hard-coded data because this means that every run starts from the same base data. That said, when you do come to add your Firestore code you may be both alarmed and delighted to find that your collections are accessible while you're still running locally.
Alarmed because this makes one appreciate the the imperative need for some strong Firebase rules. Delighted because when you're running locally via the server launched by npm start
, your webapp's localhost page is refreshed automatically as soon as you save your amended source file. I think this is probably the best development experience I've ever encountered. By contrast, building and deploying to Firebase with npm run dev/firebase deploy
takes an age.
An issue arising from the React document is how you will configure your components within your project's filing structure - should you store each in its own file or would it be better to group them up in some way so as to flatten your project's filing hierarchies? I tended to the latter view initially because I though that it would be difficult to locate code elements in heavily-populated folders. However, modularity aims are best achieved if each component is allocated its own file and, once I discovered the "sapling" plugin for VSCode - a tool allows you to access code from a graphical display of your project's module hierarchy - I have given each component its own file.
When each component is stored in its own file you'll also find that the React Developer Tools extension for Chrome also swings into action to allow you to display the state for each of your components.
You'll no doubt be relieved to find that the browser's debugging tools work much as before. You have to spend a bit of time digging in order to find your mapped index.html and index.js files, but they are there. Stepping through code doesn't seem to work as well as it does in unmapped code but I found the answer here was to use lots of breakpoints.
You need to be aware that React is still a very "young" architecture and remains under active development. If you look at the latest documentation you'll find that it strongly recommends what it terms "component functions" as replacements for the "class" components that I've used in the example code above. These avoid the use of the "this" keyword (a somewhat slippery concept) by giving you access to "webhook" functions that enable you to interact with "state" more directly. Just out of interest, I reworked my example code using the newer functions, as below:
function ListButton() {
`
const [listContent, setListContent] = useState([{ id: 0, item: "string" }]);
const getListContent = async () => {
let localListContent = [];
const listCollRef = collection(db, 'list');
const listSnapshot = await getDocs(listCollRef);
listSnapshot.forEach((listDoc) => {
localListContent.push({ id: listContent.length, item: listDoc.data().item });
});
setListContent(localListContent);
};
useEffect(() => {
if (listContent.length === 0) {
getListContent();
}
});
return (
<div>
<button onClick={() => setListContent([])}>Refresh List</button>
<ListContent listContent={listContent} />
</div >
);
}`
I'm not sure that you will see this as much of an improvement, but stick with it. When you come round to coding more complex interfaces you'll find that Hooks provide a great way of simplifying and clarifying your code - and Hooks are only available in function components.
So, will I be using React myself in future? If I was looking for a job, I'm pretty sure I would. Whatever, I hope that the above may have been helpful in guiding the progression of your own career. Best of luck luck to you!
For a full list of the posts in this series, see 1.0 An Introduction to Google's Firebase : Post Index
Posted on April 10, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.