There's a lot to learn with React, but some basic ideas can make building apps simpler, with code that is cleaner and easier to understand and debug. Those ideas are:
- Use modern functional JavaScript style everywhere. Avoid assignment-heavy state-mutating code.
- Build tiny vertical slices of end-to-end user value.
- Implement static data display first, static state display second, interaction last.
- Style from the start.
Even if you know React, do this activity. Very few students have yet learned how to use React with Hooks well. If you're not sure, take a look at this first code sample.
Prerequisites
Brush Up on Modern JavaScript
You should be familiar with modern JavaScript coding. Much has changed in the past few years. A good reference is Modern JavaScript. A good one-page resource for React is the React Handbook.
Install your Tools
Install NodeJS. If you already have, be sure it's up to date. You need npx from the new versions of Node to run the scripts.
Install the React Devtools for your browser. React does a lot of repackaging of your source HTML and JavaScript for deployment. The HTML and JavaScript the browser gets is very different from what you wrote. The devtools read metadata generated by these steps so that you can interact with your original source in the browser debugger.
Do the Quick, React! Tutorial
Do the Quick, React! tutorial. This introduces the essential JavaScript and React programming concepts needed to get a React app going on a clean solid basis. It includes functional components, stylng, fetching data, interactive UI state handling, working with the Firebase real-time database, and authentication with Firebase.
It follows the Agile approach of implementing applications in very thin end-to-end vertical slices, using the React development process described in the Thinking in React tutorial:
- Create the data or state.
- Implement and test static display of the data or state.
- Finally, implement user interactions.
A slice is a thin bit of your app, including user interface and back end. Do not do all front-end first, or all back-end first. What you choose to do in each slice is largely up to you, but it should something that either provides the most value for the end user or reduces the most risk on the technical side.
The App
This activity challenges you to build a moderately complicated shopping cart app.
If you are doing this in one of my courses, I will provide report spreadsheet for reporting progress, via Canvas. This is where you will enter links to your repo and the various commits along the way that commplete each subtask.
Your goal is to build an app very similar to this shopping cart app.
Play with this app briefly to see how it works. Some parts of it are obvious. Some parts are not, e.g., how to remove an item from the cart, and what text like "9 x $1.21" means.
Backlog
Here is the prioritized backlog of user stories for your app, broken down into user-testable releases.
Release 1: Catalog page
- Shoppers can see a catalog page of all available T-shirts with names, prices, sizes, and pictures.
Release 2: Basic shopping
- Shoppers can add a shirt to their shopping cart by clicking on
one of the size buttons.
- The same shirt and size can be added more than once.
- Shoppers can see what they've selected so far and the total cost.
- Shoppers can remove a shirt from their cart.
Release 3: Persistence and Inventory
- A shopper can log in so that their shopping cart is automatically saved and available from any device.
- A shopper always see what is currently in stock.
Create your app repository
Follow these steps to create an empty shell for your shopping cart app.
Test that your empty app runs.
cd new-shopping-cart npm start
The React web page should open in your browser.
Stop the server.
Create a repo for your app on github. Commit your code to it.
Put your name and a clickable link to the Github repo in the Learn React Report spreadsheet (link on Canvas).
Commit often to your local repo. Commit to github when something new works, but never break main.
Go public
Even though you don't have anything real yet, set up a public host for your project, so that you can user test and show your client, e.g., me., where you're at.
There are many web hosting services for dynamic web apps running NodeJS, such as Firebase, Heroku, and AWS. We’ll give instructions for Firebase. It provides three services for free that we need here: hosting, authentication, and a database.
Create an account at Firebase, if you don’t already have one.
A Northwestern Google account won't work. Any personal Google account will. Create one if necessary.
Create a Firebase project at Firebase. This will be the back-end for your app. You can call it anything, but choose something the same as or close to your app name.
Add a database to the Firebase project. Be sure to choose the real-time database, not Firestore. Create it in test mode, to avoid permission issues.
In a terminal window on your machine, install the Firebase CLI globally with
npm install -g firebase-tools
Switch into your local app directory and initialize your app's Firebase configuration:
firebase init
Choose both hosting and the real-time database.
More on setting up Firebase apps.
When initialization finishes, build and deploy
npm run build firebase deploy
This will assemble your app and deploy it to Firebase.
If deployment fails, it's probably because you are not logged into Firebase. Do firebase login and try to deploy again.
Open the URL for your app generated by Firebase. Verify that the app is working, images and all.
If deployment fails, see these notes on debugging Firebase.
Edit the description of your repo to have a link to the public site.
Create and commit your working app to github. Put your public URL on the report spreadsheet.
From this point on, your process for closing each task will be
- Code until the app works locally, using npm start.
- Build with npm run build, deploy with firebase deploy, and test your public version
- Commit to Github.
- Enter the link to the commit into the progress report spreadsheet.
Implement a static catalog page
Always start an app by building the static view of realistic data. This will give you something you can test and show to users, and provide the basis for designing the interactive parts. There are several steps to creating a static catalog page.
- Create the data to display.
- Sketch the user interface, in terms of logical components.
- Implement the components as data-driven React components and HTML
Get and test your data
Always use realistic data. No dummy "lorem ipsum" stuff. Keep the amount of data small but complete enough to demonstrate the user stories you want to implement first.
Organize your data into a logical JSON structure.
Put the data in an easy to access and deploy place. For early testing of static data, you can use a JSON text file in the application's public folder.
To kickstart this project, get the file products.json. It's a modified version of the one in Ribeiro's shopping cart.
Put it in public/data/products.json in your app directory.
Replace the entire contents of the default src/App.js file with
import React, { useEffect, useState } from 'react'; const App = () => { const [data, setData] = useState({}); const products = Object.values(data); useEffect(() => { const fetchProducts = async () => { const response = await fetch('./data/products.json'); const json = await response.json(); setData(json); }; fetchProducts(); }, []); return ( <ul> {products.map(product => <li key={product.sku}>{product.title}</li>)} </ul> ); }; export default App;
If you don't know what every step of the above code does, re-read the React notes.
Run your app. If everything is set up correctly, instead of the React logo, you should see a plain list of the product titles. When this works, you're ready to do a real display of that data.
Build, deploy, and run your app on the public host. It should look the same as your local version.
When your app is working on the public site, commit the code to github, and update the progress sheet.
Outline your user interface
Create a sketch of the UI, comparable to that in the example app, using pencil and paper, a sketching tool, or an annotated screen grab of the current UI.
Study the example on Thinking in React:
On your UI sketch, outline what you think would be appropriate containers and components.
MVP 1: A static catalog page
Your first testable product would be a page showing all the T-shirts, including pictures, names, prices, and sizes. Assume for now that all sizes of all shirts are in stock.
Put the images from the example app repository under the public folder. For example, if you put them in public/data/products/, then <img src="data/products/12064273040195392_2.jpg"> will be the thumbnail image for the T-shirt with the product ID 12064273040195392.
There is a lot to build here, especially if you are new to React. The way to get things done is to be agile: work in tiny testable slices. The following would be good slices. They can be done in almost any order:
- Show a grid of "cards" with the product titles
- Show the pictures of the shirts
- Show the descriptions of the shirts
- Show the prices of the shirts
- Show four size buttons, S, M, L, XL for each shirt
See the React notes for tips on styling and layout.
As soon as a slice works on your public host, commit to github, and update the report spreadsheet.
Add interaction
Adding the ability to add items to a cart means adding what React calls state. Read carefully what Thinking in React says counts as state. State has to do with the local user interface, not the state of the application. State in React does not include static data, nor anything that can be calculated from other bits of state.
The trick to implementing state is to first create the state variables with different values, and update your static display code to show the state appropriately. Don't try to implement state change until static state display works.
Identify the necessary state
In this case, there are two pieces of UI state:
- Where the shopping cart is visible or not.
- What's in the cart
Implement static shopping cart diplay
As with the catalog page, start by implementing code to display a shopping cart, open and closed, with and without items in it. Don't worry about methods to change any state.
Work in tiny slices, e.g., first do the state variable for whether the cart is open or closed. Test by initializing the variable to true and false, and make sure the shopping is visible only when it's supposed to be.
How you visually open and close the shopping cart will depend on which style framework you are using. Some provide a sidebar component that will slide in and out, depending on an open/closed attribute. If not, someone else has almost certainly created one. For rbx I used react-sidebar.
After that works, add the state variable for the list of items selected so far. Be sure to be able to handle more than one of an item. Initialize with different lists and verify that the cart shows the right list, and, eventually, the right total cost.
Choose data structures for states that are easy to change. A flat list is better than a complex nested object. It's easy to add and remove items from a list. It's also easy to calculate an organized summary of that list for display. It's hard however to update such a summary incrementally.
For example, I implmented both the inventory and shopping cart as just simple lists of selections, where each selection has an SKU and size. I organized by SKU and calculated quantities as needed when rendering. That made it easy to write code to add, remove, sum, and so on.
Add state using the useState() hook. Define the new state in the smallest containing component possible.
More on state.
When your app is working locally and on the public site, commit to github, and update the progress sheet.
Implement interactive state
Once the static display of the state of catalog page and shopping cart are working correctly, you are ready to implement interaction with the cart. There are three primary user actions: viewing the cart, adding items to the cart, and removing items from the cart. That's a good order to do them in.
Open and close the cart
Do opening and closing the cart first. Clicking on the cart icon or any "buy" button should open the cart. Click on the close button should close the cart. Both should just need to set the boolean open/closed cart state variable.
When your app is working locally and on the public site, commit to github, and update the progress sheet.
Add items to the cart
Now implement adding items to the cart. Adding an item should automatically open the cart and update the total.
When your app is working locally and on the public site, commit to github, and update the progress sheet.
Remove items from the cart.
Add controls next to items in the cart for removing those items. If more than one of a specific item is in the cart, remove just one of them.
When your app is working locally and on the public site, commit to github, and update the progress sheet.
Fetch from a database
Up to this point, all the data has been static. Most interesting web applications need to keep dynamic data on a server, whether it be game scores, comment posts, or whatever.
For this project, two examples of dynamic data would be
- the inventory of shirts in stock
- the user's shopping cart
Start with the inventory, since it doesn't require implementing authentication.
Show availability in your user interface
Use useState() to add a state variable for inventory, with an initial small inventory of just a few shirt sizes.
Copy some of the data from inventory.json.
Update your product display code to only show size buttons for available sizes of each shirt. If no sizes are available, show a non-clickable "out of stock" text message.
Update your code to take into account the items in the shopping cart. Verify that when you select the last available size of a shirt, that size becomes out of stock. If you remove that size shirt from the cart, it becomes available agint.
When your app is working locally and on the public site, commit to github, and update the progress sheet.
Add a database
The Firebase Real-time Database (Firebase RT) is a very simple cloud database. Everything is stored as one big JSON object. Every subitem should be indexed under a stable unchanging sequences of keys. Both products.json and inventory.json are Firebase-compatible.
Initialize the inventory
Initialize the database with inventory.json file, using the Firebase console import command.
Importing a JSON file completely replaces all data. Only do this to initialize or reset your database.
Fetch inventory from the database
Add code to get the inventory data from Firebase and store it in your local inventory state variable.
See the React notes for information on using the Firebase Realtime database.
Verify that all shirt sizes not listed in the inventory file no longer appear on the catalog page. Verify that adding and removing items from the cart still updates the available sizes appropriately.
When your app is working locally and on the public site, commit to github, and update the progress sheet.
Add authentication
Many developers implement user accounts and login first when building an app. That's a terrible idea. There is nothing to be learned about your app idea from implementing login.
But once you have a working prototype that suggests your app is a good idea, you will probably want to keep track of users. Shopping is clearly an application that needs to know who someone is when they checkout.
Add user identity to your app
Add the ability to sign up and log in.
Firebase supports multiple authentication options, e.g., email, Facebook, and Google. Add at least one form of authentication to your app. Google and email are quite simple, thanks to Firebase.
If the user is not logged in, your interface should show buttons to log in or sign up. If the user is logged in, your interface should show who is logged in and a logout button.
See the React notes for information on Firebase authentication.
When your app is working locally and on the public site, commit to github, and update the progress sheet.
Add database updating
Add persistent shopping carts
Change your app so that the shopping cart for a logged-in user is automatically saved under the carts/userid key in the database, whenever the cart changes.
Verify that you see the data appear on the Firebase console.
Add code so that when a user logs in, any saved items are added to the current shopping cart. Don't replace the current cart. The user may have selected some items before logging in. The old and new selections should be merged. The user can always delete old items if they want to.
Note that the saved cart items plus what the user has selected may exceed what is in the current inventory. Your rendering code should check for this and
- display notes next the affected items, e.g, "only 2 in stock!"
- show an "update cart" button instead of a "checkout" button
Clicking "update cart" should reset the quantities in the cart to the maximum valid values. Alternatively, the user could choose to remove items manually. Either way, when the cart state becomes valid, the normal checkout button will be displayed. Notice that we are not using any kind of modal dialog box here. We are simply displaying a particular state of the app.
When your app is working locally and on the public site, commit to github, and update the progress sheet.
Add stock updating
Change your app so that when a user checks out, any items in their cart are removed from the inventory on the database.
Careful! Two users might check out at the same time. This is a common problem with shared user state. To handle it, databases provide transactions. Transactions are a way to handle concurrent updates safely. With the Firebase function transaction(), you can make sure the current inventory has all the items the user wants, and cancel the operation if it does not.
More on transactions with Firebase.
Checking out and changing the inventory on Firebase may cause other carts to contain items no longer available. The code in the previous slice should already handle this situation, changing "checkout" to "update cart" in carts where inventory has fallen too lows. Verify this by logging in as two different users, on two different browsers. Set up the shopping carts such that when one user checks out, the other user's cart has to be adjusted.
When your app is working locally and on the public site, commit to github, and update the progress sheet.