In my first week in phase 2 of Moringa School Software Engineering classes, I covered the basics of getting data from APIs and working with forms. The Code Challenge for the week had the following tasks:
- Fetch Transactions from an API and tabulate them.
- Add new transactions. The transaction added should be posted to the backend API.
- FIlter out transactions by description
The initial code for the challenge is in this commit.
Before I started, I installed concurrently an npm package that allows one to run multiple commands concurrently. Like npm run watch-js & npm run watch-less but better. I wanted to run both react server and json.db server from one script:
npm run dev
"scripts": {
"start": "PORT=8000 react-scripts start",
"server": "json-server --watch db.json -p 8001",
"dev": "concurrently \"npm start\" \"npm run server\""
},
Fetch Transactions from an API and tabulate them.
I edited the Transaction.js
file:
import React from "react";
function Transaction() {
return (
<tr>
<td>{"your code here..."}</td>
<td>{"your code here..."}</td>
<td>{"your code here..."}</td>
<td>{"your code here..."}</td>
</tr>
);
}
export default Transaction;
First, the transaction
prop will be added, passing in data rendered from the API. From the API, data will have the following: id, date, description, category, and amount. So I updated the component to:
import React from 'react'
function Transaction({ transaction }) {
return (
<tr>
<td>{transaction.date}</td>
<td>{transaction.description}</td>
<td>{transaction.category}</td>
<td>{transaction.amount}</td>
</tr>
)
}
export default Transaction
Next step, I climbed up to the TransactionList
component, where I was supposed to render the data.
import React from "react";
import Transaction from "./Transaction";
function TransactionsList() {
return (
<table className="ui celled striped padded table">
<tbody>
<tr>
<th>
<h3 className="ui center aligned header">Date</h3>
</th>
<th>
<h3 className="ui center aligned header">Description</h3>
</th>
<th>
<h3 className="ui center aligned header">Category</h3>
</th>
<th>
<h3 className="ui center aligned header">Amount</h3>
</th>
</tr>
{/* render a list of <Transaction> components here */}
</tbody>
</table>
);
}
export default TransactionsList;
I added the prop transaction
to the component, so I can fetch data from the upper-level component, AccountContainer
. The data returned will be an array. So I mapped through the array "transaction" and displayed each item in a table.
{transaction.map((data) => (
<Transaction key={data.id} transaction={data} />
))}
Here is the updated component:
import React from 'react'
import Transaction from './Transaction'
function TransactionsList({ transaction }) {
return (
<table className='ui celled striped padded table'>
<tbody>
<tr>
<th>
<h3 className='ui center aligned header'>Date</h3>
</th>
<th>
<h3 className='ui center aligned header'>Description</h3>
</th>
<th>
<h3 className='ui center aligned header'>Category</h3>
</th>
<th>
<h3 className='ui center aligned header'>Amount</h3>
</th>
</tr>
{transaction.map((data) => (
<Transaction key={data.id} transaction={data} />
))}
</tbody>
</table>
)
}
export default TransactionsList
Next is to climb up to a higher level component: AccountContainer
import React from "react";
import TransactionsList from "./TransactionsList";
import Search from "./Search";
import AddTransactionForm from "./AddTransactionForm";
function AccountContainer() {
return (
<div>
<Search />
<AddTransactionForm />
<TransactionsList />
</div>
);
}
export default AccountContainer;
I used Hooks and useEffects to get data from the API.
import React, { useEffect, useState } from 'react'
import TransactionsList from './TransactionsList'
import Search from './Search'
import AddTransactionForm from './AddTransactionForm'
function AccountContainer() {
const [searchItem, setSearchItem] = useState('')
const [transactions, setTransactions] = useState([])
// get transactions from API
useEffect(() => {
fetch('http://localhost:8001/transactions')
.then((response) => response.json())
.then((data) => {
setTransactions(data)
})
}, [])
return (
<div>
<Search />
<AddTransactionForm />
<TransactionsList transaction={transactions} />
</div>
)
}
export default AccountContainer
Running the server to view the changes:
Add new transactions. The transaction added should be posted to the backend API.
To add new transaction, I started with the AddTransactionForm
component.
import React from "react";
function AddTransactionForm() {
return (
<div className="ui segment">
<form className="ui form">
<div className="inline fields">
<input type="date" name="date" />
<input type="text" name="description" placeholder="Description" />
<input type="text" name="category" placeholder="Category" />
<input type="number" name="amount" placeholder="Amount" step="0.01" />
</div>
<button className="ui button" type="submit">
Add Transaction
</button>
</form>
</div>
);
}
export default AddTransactionForm;
First I install uuid
to assign a unique ID to every transaction added.
npm i uuid
Then used state hooks to capture values, and create an event handler to send the data to the API once the form is submitted:
const handleChange = (e) => {
e.preventDefault()
const newTransaction = {
id: uuid(),
date: date,
description: description,
category: category,
amount: amount,
}
fetch('http://localhost:8001/transactions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newTransaction),
})
.then((resp) => resp.json())
.then((newQuestion) => console.log(newQuestion))
}
The updated component:
import React, { useState } from 'react'
import { v4 as uuid } from 'uuid'
function AddTransactionForm() {
const [description, setDescription] = useState('')
const [amount, setAmount] = useState('')
const [date, setDate] = useState('')
const [category, setCategory] = useState('')
const handleDescriptionChange = (event) => {
setDescription(event.target.value)
}
const handleAmountChange = (event) => {
setAmount(event.target.value)
}
const handleDateChange = (event) => {
setDate(event.target.value)
}
const handleCategoryChange = (event) => {
setCategory(event.target.value)
}
const handleChange = (e) => {
e.preventDefault()
const newTransaction = {
id: uuid(),
date: date,
description: description,
category: category,
amount: amount,
}
fetch('http://localhost:8001/transactions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newTransaction),
})
.then((resp) => resp.json())
.then((newQuestion) => console.log(newQuestion))
}
return (
<div className='ui segment' onSubmit={handleChange}>
<form className='ui form'>
<div className='inline fields'>
<input
type='date'
name='date'
value={date}
onChange={handleDateChange}
required
/>
<input
type='text'
name='description'
placeholder='Description'
value={description}
onChange={handleDescriptionChange}
required
/>
<input
type='text'
name='category'
placeholder='Category'
value={category}
onChange={handleCategoryChange}
required
/>
<input
type='number'
name='amount'
placeholder='Amount'
step='0.01'
onChange={handleAmountChange}
required
/>
</div>
<button className='ui button' type='submit'>
Add Transaction
</button>
</form>
</div>
)
}
export default AddTransactionForm
Running the server to view changes:
FIlter out transactions by description
Inside the Search
component:
import React from "react";
function Search() {
return (
<div className="ui large fluid icon input">
<input
type="text"
placeholder="Search your Recent Transactions"
onChange={() => console.log("Searching...")}
/>
<i className="circular search link icon"></i>
</div>
);
}
export default Search;
I added two props: ({ search, onSearchChange })
import React from 'react'
function Search({ search, onSearchChange }) {
return (
<div className='ui large fluid icon input'>
<input
type='text'
placeholder='Search your Recent Transactions'
onChange={onSearchChange}
value={search}
/>
<i className='circular search link icon'></i>
</div>
)
}
export default Search
Next, inside the AccountContainer
component, I wrote the logic to filter out data. I updated the Search component:
<Search search={searchItem} onSearchChange={searchFilteredItems} />
searchItem
is from the state hooks:
const [searchItem, setSearchItem] = useState('')
searchFilteredItems
is a function:
const searchFilteredItems = (e) => {
setSearchItem(e.target.value)
// console.log(searchItem)
}
That updates the value to of the input to the latest input as a user keys in. Created a function to filter trough the response array and return only transactions that match the search input:
const displaySearchItems = transactions.filter((transaction) =>
transaction.description.toLowerCase().includes(searchItem.toLowerCase())
)
Updated the TransactionsList
component to use the filtered items.
<TransactionsList transaction={displaySearchItems} />
The updated file: AccountContainer.js
import React, { useEffect, useState } from 'react'
import TransactionsList from './TransactionsList'
import Search from './Search'
import AddTransactionForm from './AddTransactionForm'
function AccountContainer() {
const [searchItem, setSearchItem] = useState('')
const [transactions, setTransactions] = useState([])
// get transations from API
useEffect(() => {
fetch('http://localhost:8001/transactions')
.then((response) => response.json())
.then((data) => {
setTransactions(data)
})
}, [])
const searchFilteredItems = (e) => {
setSearchItem(e.target.value)
// console.log(searchItem)
}
// console.log(typeof transactions)
const displaySearchItems = transactions.filter((transaction) =>
transaction.description.toLowerCase().includes(searchItem.toLowerCase())
)
console.log(displaySearchItems)
// const displaySearchItems = transactions.filter((item) => {
// return item.name.includes(searchItem)
// })
return (
<div>
<Search search={searchItem} onSearchChange={searchFilteredItems} />
<AddTransactionForm />
<TransactionsList transaction={displaySearchItems} />
</div>
)
}
export default AccountContainer
Running the server:
Conclusion
Successfully displayed data using useState and passed it dynamically to Tables. Users can add a new transaction and search for transactions based on descriptions.
Top comments (0)