For this activity, you'll be building out the UI for a new financial application that Codepath has been prototyping. It's a simple banking app that helps users keep track of their finances and payments using an Express API and a React UI.
Your job is to wire up the React UI to interact with the already built Express API. Data in the Express API is persisted using a JSON file that will store all user activity.
By the end of this lab you will be able to...
- Make HTTP requests from React applications using
axios
- Handle asynchronous functions using
async/await
- Leverage the
useEffect
hook to make API requests when components mount - Store data pulled from a remote API locally with the
useState
hook - Create
onClick
handlers for buttons - Manage the internal state of a form input with use state and an
onChange
handler. - Create
onSubmit
handlers for forms - Use React Router to navigate between pages
- Create dynamic routes with React Router
- Craft HTTP requests that use route parameter from the
useParams
hook
- Users can add new transaction to bank: Takes in description, category, and amount. Be sure to specify what unit of currency the amount is in (i.e USD, cents, etc.)
- New transactions will be updated in the activity section with most recent at the bottom.
- Allows users to search in activities based on key words or phrases.
- Display the current total balance of the user's bank account on the Home Page.
- Implement an
AddTransfer
component that allows users to add a transfer to their bank account. - Create a
TransferDetail
component that displays info about an individual transfer. - Ensure that the
FilterInput
filters both transactions and transfers. - Add an API endpoint that allows users to indicate that they've paid off certain transactions if that transaction took money out of the account. Create a button on the
TransactionDetail
component that lets users record this information.
:::info
💡 Note: The API has already been provided and is fully functional as is. Feel free to explore the backend to see what routes are available and how the Bank
model and Storage
class stores and accesses data from the db.json
file.
:::
There are two things to be aware of when implementing code for this lab:
In the main.jsx
file, the core <App />
component isn't being rendered as it usually is.
Instead, we see this:
const renderApp = () =>
ReactDOM.render(
<React.StrictMode>
<App />
{/* Leave this here for live test environment */}
<InstantNoodles RootComponent={App} tests={tests} config={config} />
</React.StrictMode>,
document.getElementById("root")
)
/* Comment out this next line to run against live Express API */
prepareMockServiceWorker().then(() => renderApp())
/* Uncomment this next line to run against live Express API */
// renderApp()
The actual rendering of the React app is done in a function so that the test environment can be setup with a mock service worker that will intercept axios
requests for testable and reproducible API behavior. Once that setup is complete, then the application will be rendered the screen. To see the application running live against the real Express api, comment out the line with prepareMockServiceWorker
and uncomment the line that only has the code: renderApp()
.
Don't be alarmed if some tests aren't passing when navigating to a route different from the home /
route. This is due to a limitation with React test rendering behavior.
:::info
💡 Tip: Anytime tests that were passing before are no longer passing, navigate back to the /
route and refresh the page.
:::
Start by installing the core dependencies for this project.
- Navigate into the
bank-of-codepath-express-api
directory and runnpm install
to get the appropriate dependencies. Make sure the express server is running withnpm run dev
ornpm start
in theapi
directory. - In a new terminal window, navigate into the
bank-of-codepath-ui
directory and runnpm install
to download the frontend dependencies. - [ ] Runnpm run dev
to get the React app started up. - Open up
http://localhost:3000
in the browser to see the current state of the app. The API should be running athttp://localhost:3001
. That value is also stored in theAPI_BASE_URL
variable inside theconstants.js
file in the frontend repo.- The tests will run immediately as soon as the frontend starts up.
- They should all be failing, but that's ok. We'll use them to guide our development during this lab.
- Every time a file is updated, the tests will be re-run.
- Route Components
- Import the
BrowserRouter
,Routes
, andRoute
components fromreact-router-dom
- Inside the
return
statement in theApp.jsx
component, nest theBrowserRouter
component inside thediv
element with aclassName
ofapp
. - Nest the
Navbar
component inside theBrowserRouter
component. - Place the
Home
component inside amain
tag that is rendered directly after theNavbar
component.
- Import the
- Define routes
- Make the first child of the
main
tag theRoutes
component fromreact-router-dom
. - Use the
Route
component to add an index route for theHome
component at the/
route - Import the
TransactionDetail
component into theApp.jsx
component. - Add a dynamic route with the base path of
transactions
that is used for displaying a single transaction with thetransactionId
path parameter. Display that page with theTransactionDetail
component.
- Make the first child of the
- State variables
- Create default state and handlers with React's
useState
hook for the following items. These won't cause any tests to pass, but we'll need them for the upcoming tests-
isLoading
- a boolean representing whether or not the app is currently requesting data from the API -
transactions
- the list of bank transaction items fetched from the API -
transfers
- the list of bank transfer items fetched from the API -
error
- any errors associated with fetching data from the API -
filterInputValue
- a string value used to create a controlled input in theFilterInput.jsx
component
-
- Create default state and handlers with React's
- Render a nav link
- Render the
Logo
component as the first element nested inside thenav
element - Inside the
Logo
component, wrap theimg
element with aLink
component fromreact-router-dom
. - Pass a
path
prop to theLogo
component that corresponds to theHome
route. Then pass that as theto
prop in theLink
component - Clicking on the
img
element in theNavbar
should now redirect to the Home route. Manually navigate to a different path and try it out in the browser.
- Render the
- Pass props
- Go ahead and pass the
filterInputValue
to theNavbar
as thefilterInputValue
prop - Name the state updater function for that variable:
setFilterInputValue
. Pass that function to theNavbar
as itssetFilterInputValue
prop.
- Go ahead and pass the
- Create a controlled input
- Pass the
filterInputValue
to theFilterInput.jsx
component as itsinputValue
prop. - Define a function called
handleOnInputChange
that takes in achange
event and calls thesetFilterInputValue
function with the new input value extracted from the event. - Pass the
handleOnInputChange
function to theFilterInput.jsx
component as itshandleOnChange
prop.
- Pass the
- Create a controlled input
- Pass the appropriate props to the
input
element to create a controlled input. - Typing into the input should now update state. Use the React devtools to confirm this
- Pass the appropriate props to the
- Pass props
- Pass the state variables and updater functions as props to the
Home.jsx
component as needed:transactions
setTransactions
transfers
setTransfers
error
setError
isLoading
setIsLoading
filterInputValue
- Pass the state variables and updater functions as props to the
- Fetch data
- Create a
useEffect
hook that runs whenever theHome.jsx
component is mounted to the screen- When the effect kicks off, it should set
isLoading
to true - That function should fetch all transactions and transfers from the API. You can either create two separate
useEffect
hooks to accomplish this, or do them both in the same hook. Either way, make sure to send two HTTP requests to the/transactions
and/transfers
endpoints with theaxios.get
method. - If an error occurs while fetching data, it should be added to state.
- When data is returned from fetching data, it should be set in state accordingly.
- When the function has finished executing, it should set
isLoading
to false - Make sure to call the function at the end of the
useEffect
hook.
- When the effect kicks off, it should set
- Create a
- Custom rendering
- Loading
- While the app is fetching data, the
Home.jsx
component should have anisLoading
prop equal totrue
.- When that prop is
true
, it should render anh1
element with the text: `"Loading..." - Otherwise, render the
BankActivity
component. It should always render theAddTransaction
component.
- When that prop is
- While the app is fetching data, the
- Error
- If the
Home.jsx
component receives any defined value for itserror
prop, it should render an error message inside of anh2
element with the className oferror
.
- If the
- Loading
- Filtering transactions
- The
Home.jsx
component should create afilteredTransactions
array using itstransactions
prop. - If its
filterInputValue
prop is NOT an empty string:- It should filter the transactions based on whether or not the lowercased
description
property of a transaction contains the lowercasedfilterInputValue
- Otherwise, it should just be the raw array passed as the
transactions
prop.
- It should filter the transactions based on whether or not the lowercased
- The
filteredTransactions
array should be passed to theBankActivity
component as itstransactions
prop.
- The
This component is responsible for adding a new transaction to the Bank. We'll be turning each input into a controlled input and adding an onClick
handler to the submit button to make that happen.
- Define state variables and pass props
- Create a new state variable and updater function -
newTransactionForm
andsetNewTransactionForm
- The
newTransactionForm
state variable should be an object with 3 form fields -category
,description
, andamount
- The
- Also create one for the
isCreating
state with thesetIsCreating
state updater function - Pass all of them to the
Home.jsx
component as props
- Create a new state variable and updater function -
- Pass props
- Pass the
isCreating
andsetIsCreating
props directly to theAddTransaction
component as props - Pass the
newTransactionForm
to theAddTransaction
component as itsform
prop - Pass the
setNewTransactionForm
to theAddTransaction
component as itssetForm
prop - Define a
handleOnSubmitNewTransaction
function and pass it to theAddTransaction
component as thehandleOnSubmit
prop
- Pass the
- Form management
- Create a
handleOnFormFieldChange
function- It should take a
change
event as its single argument. - That function should then update individual fields in the form using the
change
event. - Pass that function to the
AddTransactionForm
component as itshandleOnFormFieldChange
prop. - Go ahead and pass the other required props to the
AddTransactionForm
component as indicated by the tests
- It should take a
- Create a
- Create controlled inputs
- Make sure each input is given a
name
prop corresponding to the correct field in thenewTransactionForm
. - Give each input the correct
placeholder
andtype
props, using the tests as a guide - Give each input the correct
value
andonChange
props as well - Test that each one works by entering text into the input and using the React devtools to ensure that the correct field in the
newTransaction
form state variable is being updated
- Make sure each input is given a
- Handle submit events
- The
button
element with aclassName
ofadd-transaction
should get passed thehandleOnSubmit
function as itsonClick
prop
- The
- Handle submit events
- The
handleOnCreateTransaction
function- Should be an
async
function that starts by settingisCreating
totrue
- Then, it should use the
axios.post
method to issue aPOST
request to the/transactions
endpoint with the contents of thenewTransactionForm
as its body- If anything goes wrong, it should call the
setError
function with the error and setisCreating
tofalse
- Otherwise, it should take the new transaction returned from the API and add it to the
transactions
array in state - Finally, it should reset the
newTransactionForm
to its original state and setisCreating
back tofalse
- If anything goes wrong, it should call the
- Should be an
- The
- The
AddTransaction
component should now let users submit new transactions to the backend that are stored in the database. Try running the app against the live API to see it in action.
- The
BankActivity.jsx
component should iterate over itstransactions
prop and render aTransactionRow
for each one. That component should render JSX wrapped by an element with theclassName
oftransaction-row
. - It should also iterate over its
transfers
prop and render aTransferRow
for each one. That component should render JSX wrapped by an element with theclassName
oftransfer-row
.
- Import the
Link
component fromreact-router-dom
- The
TransactionRow
component in theBankActivity.jsx
file should render JSX wrapped by aLink
component fromreact-router-dom
that links to the correct transaction detail page - Make sure to dynamically create the
to
prop based on theid
of each transaction - Clicking on the
TransactionRow
component should redirect to theTransactionDetail
page for that transaction
- Define state variables
- Inside the
TransactionDetail.jsx
component, create some state variables and state updater functions:-
hasFetched
andsetHasFetched
- default tofalse
-
transaction
andsetTransaction
- default to{}
-
isLoading
andsetIsLoading
-
error
andsetError
-
- Inside the
- Extract url params
- Import the
useParams
hook fromreact-router-dom
- Use it to extract the
transactionId
from the url route and store it in thetransactionId
variable
- Import the
- Fetch resources based on url params
- Create a
useEffect
hook inside the component. Inside it:- Define an async function called
fetchTransactionById
.- That function should start by setting
isLoading
totrue
andhasFetched
tofalse
. - Then it should use
axios.get
method to issue aGET
request to the API to fetch a single transaction by its id. - If there is an error, set that error in state.
- If valid data is returned, update the
transaction
state value. - Set
isLoading
tofalse
andhasFetched
at the end of the function no matter what. - Make sure to call that function at the end of the
useEffect
hook.
- That function should start by setting
- Add the
transactionId
to theuseEffect
hook's dependency array so that it runs each time the url route changes
- Define an async function called
- Create a
- Render proper component values
- The
TransactionDetail
component should then pass the correct props to theTransactionCard
component - The
TransactionCard
component should render the proper values when thetransaction
is valid - If no valid
transaction
is returned for thaturl
,isLoading
isfalse
, andhasFetched
istrue
, then theTransactionCard
component should render only render thetransactionId
inside anh3
element in thecard-header
, along with anh1
tag that saysNot Found
.
- The
Congrats! All tests should now be passing and the application should be fully complete. Try it out against the live API to make sure everything is working as expected!
- Display the current total balance of the user's bank account on the Home Page.
- Implement an
AddTransfer
component that allows users to add a transfer to their bank account. - Create a
TransferDetail
component that displays info about an individual transfer. - Ensure that the
FilterInput
filters both transactions and transfers. - Add an API endpoint that allows users to indicate that they've paid off certain transactions if that transaction took money out of the account. Create a button on the
TransactionDetail
component that lets users record this information.