# Sailing Through Transactions

Curiosity drove me to explore GraphQL and its perks. Having never interacted with it before, I was keen to find a seamless way of incorporating it into my React application. Grafbase, a database that promised a serverless backend for my application as well as a single endpoint for accessing data, proved to be an optimum route to take. My application of choice was a transaction tracking application that sought to address the problem of financial management and organization in an increasingly fast-paced and interconnected world with a view of increasing financial literacy in its users. I started by selecting React as my library of choice for my front end alongside the serverless backend provided by Grafbase. I was able to come up with a transaction tracking application appropriately named 'Transaction Tracker'.

![Transaction tracker application image](https://cdn.hashnode.com/res/hashnode/image/upload/v1691986166653/82b9d4e2-5879-49ee-b2d6-0cb9f370f972.png align="center")

This article aims to help you step onto the dance floor of technology, where React and GraphQL perform their graceful moves in harmony, with Grafbase as their stage.

## **Getting Started**

Let's start by figuring out what exactly we want to make. To do this, we'll do a bit of planning to make sure we're on the same page about what we need. So, here's a simple breakdown of the key tasks our app should be able to handle:

1. **Adding transactions:** We should be able to input new transactions into the app.
    
2. **Listing transactions:** The app should show us a list of all the transactions we've added.
    
3. **Editing:** If we make a mistake or need to change something, the app should let us easily edit the transactions.
    
4. **Deleting transactions:** If we no longer need a transaction, the app should allow us to remove it.
    

With that, let's dive right in.

***Prerequisites:***

* *Basic knowledge of HTML, CSS, and JavaScript*
    
* *Node.js and npm (Node Package Manager) installed on your computer*
    
* *A code editor. eg. VS Code*
    

### **Step 1: Setting Up the Project**

First, let's set up the project by creating a new React app. Open your terminal and run the following commands:

```bash
npx create-react-app transaction-tracker 
```

and...

```bash
npx grafbase init
```

By now, your file structure should be looking like this:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692125673241/3739f02b-3b76-42f3-b48f-ad0e312794f0.png align="center")

### **Step 2: Creating the App Component**

Now, let's start building the transaction tracker's UI by modifying the `src/App.js` file. Replace the default code with the following:

```javascript
import React from "react";

function App() {
  const transactions = [
    {
      id: 1,
      description: "Travel Expenses",
      amount: 10000,
      date: "04/21/2023",
      type: "Expense"
    },
    {
      id: 2,
      description: "Groceries",
      amount: 3500,
      date: "08/10/2023",
      type: "Expense"
    },
    {
      id: 3,
      description: "Salary",
      amount: 50000,
      date: "08/01/2023",
      type: "Revenue"
    },
    {
      id: 4,
      description: "Rent",
      amount: 12000,
      date: "08/05/2023",
      type: "Expense"
    }
  ];

  return (
    <div className="App">
      <h1 className="title">Transaction Tracker</h1>
      <div className="transaction-form">
        <input
          type="text"
          className="input"
          placeholder="Description"
        />
        <input
          type="number"
          className="input"
          placeholder="Amount"
        />
        <input
          type="date"
          className="input"
        />
        <select
          className="input"
        >
          <option value="expense">Expense</option>
          <option value="revenue">Revenue</option>
        </select>
        {editingTransaction ? (
          <div>
            <button className="update-button">
              Update Transaction
            </button>
            <button className="cancel-button">
              Cancel
            </button>
          </div>
        ) : (
          <button className="add-button" onClick={handleAddTransaction}>
            Add Transaction
          </button>
        )}
      </div>
      <div>
        <ul className="transaction-list">
          {transactions.map((transaction) => (
            <li
              key={transaction.id}
              className={`transaction-item ${
                transaction.type === "expense" ? "expense" : "revenue"
              }`}
            >
              <strong>{transaction.description}</strong> ${transaction.amount.toFixed(2)} ({transaction.date})
              <div className="button-group">
                <button className="edit-button">
                  Edit
                </button>
                <button className="delete-button">
                  Delete
                </button>
              </div>
            </li>
          ))}
        </ul>
        <p className="total">Total: ${totalAmount.toFixed(2)}</p>
      </div>
    </div>
}

export default App;
```

The code provided above gives our app a basic structure to work with. Here's what we can deduce from it:

1. **Storing Transactions:** Right now, the transactions are directly written into the code. However, this isn't practical for the future. We need a better way to save and manage transactions.
    
2. **Important Fields:** Our transactions have specific details that we want to keep track of an `id`, `description`, `amount`, `date`, and `type`. Thus, even in storage, these fields should be accommodated.
    

This is where Grafbase comes in!

*To view the above changes on your browser, run the following commands on your terminal:*

```bash
cd transactions-tracker
npm start
```

*Now, you should be able to access your application on your browser via* `localhost:3000`*.*

### **Step 3: Grafbase Schema and CLI**

For this step, we shall be editing the `grafbase/schema.graphql` file. What we should be conscious of is that our Grafbase instance will be running locally on our computer. Replace the `grafbase/schema.graphql` code with the below code.

```graphql
type Transaction @model {
  id: ID!
  description: String!
  amount: Float!
  date: String!
  type: String!
}
```

On your new or split terminal, run the following commands:

*If you are using VS Code, to create a new terminal or split it, go to 'Terminal' on the top-most bar, select it and select the former or latter.*

```graphql
cd grafbase
npx grafbase dev
```

If everything is running right, you should get a response like in the one in the image below.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691991643940/512f1989-5bba-472b-87a2-db8d4ef3943a.png align="center")

Just like that, Grafbase is running locally on our computer. To make requests to it, we shall be using the endpoint [http://localhost:4000/graphql](http://127.0.0.1:4000/graphql) for the time being.

Additionally, a visit to [http://localhost:4000](http://127.0.0.1:4000/graphql) will present a playground similar to the one in the image below. This is one of the perks of using Grafbase - **the ability to test queries without having to run entire applications**.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692079338000/e16ff6d2-422a-4ecd-bb14-d8ed5298b887.png align="center")

***Important***

*While I intended to use the* [*http://localhost:4000/graphql*](http://127.0.0.1:4000/graphql) *endpoint to test out my application locally, I kept getting the "Unauthorized" error message. Having determined that I would require some sort of an API KEY, I opted to first deploy my* `grafbase/schema.graphql` *to Grafbase.*

Consequently, these subsequent parts are going to address the deployment to Grafbase. Thereafter, we shall get back to our application's logic so consider this as a much-needed detour.

### **Step 4: GitHub**

First of all, we need to host our application on GitHub. This step will be taken care of by the following commands which can either be run on our terminal or Git Bash. It is assumed that you have a GitHub account and have created a new repository. As such, the commands below will initialize an empty GitHub repository, add, commit and push all the files to your GitHub repository.

```bash
git init
git add .
git commit -m "The fetch, add, edit and delete transactions functionalities added"
git branch -M main
git remote add origin GitHub-Repository-URL
git push -u origin main
```

### **Step 5: Creating a Grafbase account**

Our next stopover brings us to the creation of a Grafbase account. It is simple and quick so let's get right to it! Navigate to [the Grafbase Website](https://grafbase.com/) and click on the Sign Up button.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080500604/f46e59d0-d2c4-4643-abe9-e6927694a67d.png align="center")

You will be navigated to the page below. I would recommend signing up with your GitHub account to ease the process of accessing your GitHub repository.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080829626/fcd39b9a-fe5b-4dee-ad7f-f11f71ffc47c.png align="center")

Most likely, an interface like the one below will pop up.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080348803/00a64c36-4d89-472b-b8a3-d5339ca48952.png align="center")

Just like that, our Grafbase account is created and we are ready for deployment.

### **Step 6: Deploying to Grafbase**

There are two methods of deploying your schema to Grafbase. One, through Grafbase's official website and two, through our VS Code terminal. I shall address both.

1. **Deploying via GitHub**
    

* Navigate to the **Dashboard** tab of [the Grafbase website](https://grafbase.com/).
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692087283215/b4973ba1-6678-4106-a4db-492f92f8cb84.png align="center")

* Mine has two projects already so it is similar to the one below. Yours will be empty but worry not, for we shall set up one right here. Click on **Create Project.**
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692080625631/2024cae9-9a4f-41d9-b3b3-2eb64cdee016.png align="center")

* Search for the repository which contains your application. Click on **Import**.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692087607425/6b75b145-1a88-465a-b11a-329828ce9eca.png align="center")

* Name your project appropriately and click on **Deploy.**
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692087691307/0744cc1c-b7df-44f8-80cd-01054a84b180.png align="center")

* Deployment might take a little while after which you will be navigated to the interface below. Our application, specifically the `schema.graphql` file is now deployed to Grafbase.
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692088459841/66e1c6f6-ce5f-43d0-a46b-44ea825fad2a.png align="center")
    
    *If you get the 'failed to deploy' error during deployment or 'failed to find* `schema.graphql`*, consider making sure that your application, specifically the grafbase folder, is located at the root of the GitHub repository as in the image below. With that, deployment should be smooth enough.*
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692089852351/4e697043-659c-4066-b46d-35aee873edc5.png align="center")
    
* Navigate to the **connect** tab to access your **API URL** and **KEY.** We shall be using them for our application.
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692158440615/0224ff0f-d937-418c-b9d2-9736d4ccaf1a.jpeg align="center")
    

1. **Deploying via the terminal**
    

While one can deploy their Grafbase schema by linking their GitHub repository, I prefer the terminal way better - well, because I appreciate its ease and accessibility. This involves the login and creation of Grafbase projects all at the comfort of your terminal. The commands below navigate to the current Grafbase directory and initialize a Grafbase instance.

```bash
cd grafbase 
npx grafbase init
```

Since we had already created our Grafbase account, we are only required to log in for authentication. Afterward, we have to create a Grafbase project which will be deployed, automatically. The following commands take care of that.

```bash
npx grafbase login
npx grafbase create
```

To redeploy an existing project where there are changes or updates, run:

```bash
npx grafbase deploy
```

With that, your Grafbase project will have been created. Navigate to the **connect** tab to access the **API URL** and **KEY** in a pop-up.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692084215996/a06c8735-fd5b-4fe0-9dea-631a956ac2f9.jpeg align="center")

### **Step 7: Using the Grafbase endpoint**

This is probably a great time to copy the API URL and replace it in our project. The same goes for our API KEY. You may consider storing them in a .env file to ensure the security of your application. Update the `env.local` file with the variables below.

```bash
REACT_APP_MY_API_KEY= "API_KEY"
REACT_APP_MY_API_URL= "API_URL"
```

To access them in our application, we will have to install `dotenv`. Run the following command to install it.

```bash
npm i --save-dev dotenv
```

### **Step 8: Grafbase Pathfinder**

Just before we move on to our application's code, I thought it wise to establish the queries that we will eventually use. The idea is that if the query works in Pathfinder, it should work in our application. To access the Pathfinder, you may choose to go to the Grafbase website way and go to the **View Pathfinder** tab. An alternative is using the terminal as discussed in Step 3.

* **Adding a Transaction**
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692102046611/a07143cc-3d2b-40a7-9d18-102ab6510849.png align="center")

In this example, we are using the `transactionCreate` mutation to add a new transaction to our system. The `input` parameter allows us to provide the necessary details for the transaction, such as its description, amount, date, and type.

```javascript
mutation TransactionCreate {
  transactionCreate(
    input: {
      description: "Roofing bill"
      amount: 34
      date: "2023-02-02"
      type: "expense"
    }
  ) {
    transaction {
      id
      description
      amount
      date
      type
    }
  }
}
```

Breaking Down the Mutation:

* `transactionCreate`: This is the name of the mutation. In GraphQL, we name our mutations for clarity and ease of use.
    
* `input`: The `input` parameter takes an object that holds the specific details of the transaction we want to create. In this case, we're creating a transaction with a description of "Roofing bill," an amount of $34, a date of February 2, 2023, and a type of "expense."
    
* `transaction`: This field specifies what data we want to retrieve as a result of the mutation. Here, we are asking for the `id`, `description`, `amount`, `date`, and `type` of the newly created transaction.
    
* **Fetching a Transaction**
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692102374569/09eb7c47-278e-4ed8-944a-9545f3448ffd.png align="center")

In this demo, we are fetching our transactions. The query below worked for me and gave me the response above.

```javascript
 {
      transactionCollection(first: 50) {
        edges {
          node {
            id
            description
            amount
            date
            type
          }
        }
      }
    }
```

Let's go through each part of the above query.

1. `transactionCollection(first: 50)` represents a collection of transactions. The `first` argument specifies that you want to retrieve the first 50 transactions from this collection. This is a common pattern in GraphQL to limit the number of results returned.
    
2. `edges` is a field that is used to access the edges of the `transactionCollection`. In GraphQL, edges are often used in connection with pagination, where each edge represents a data item.
    
3. `node` is used to access the data within each edge. In GraphQL, nodes typically contain the actual data that you're interested in retrieving.
    
4. Inside the `node` field, there are several subfields:
    
    * `id`: This subfield represents the unique identifier of a transaction.
        
    * `description`: This subfield represents the description of the transaction.
        
    * `amount`: This subfield represents the amount of the transaction.
        
    * `date`: This subfield represents the date of the transaction.
        
    * `type`: This subfield represents the type of the transaction (e.g., "expense" or "revenue").
        

When you execute this GraphQL query against a server that supports it, you'll receive a response containing the requested data for the first 50 transactions in the `transactionCollection`. The data will be structured in a way that mirrors the structure of the query, with an array of `edges`, and within each `edge`, an object containing the `node` data (including `id`, `description`, `amount`, `date`, and `type`). This kind of structured response allows you to efficiently retrieve and work with the specific data you need from the server.

### **Step 9: Fetching Transactions**

In this step, we'll use the `useEffect` hook to fetch transactions from a GraphQL API. This is to ensure that transactions are only fetched in the initial render.

```javascript
const fetchTransactions = async () => {
    try {
      const response = await fetch(process.env.REACT_APP_MY_API_URL, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "x-api-key": API_KEY,
        },
        body: JSON.stringify({
          query: `
          {
            transactionCollection(first: 50) {
              edges {
                node {
                  id
                  description
                  amount
                  date
                  type
                }
              }
            }
          }
          `,
        }),
      });

      const result = await response.json();
      console.log("API Response:", result);

      if (result.data && result.data.transactionCollection) {
        const transactionsData = result.data.transactionCollection.edges.map(edge => edge.node);
        setTransactions(transactionsData);
        const highestId = Math.max(...transactionsData.map(item => item.id));
        setCurrentId(highestId + 1);
      }
    } catch (error) {
      console.error("Error fetching transactions:", error);
    }
  };
```

1. `const fetchTransactions = async () => {}` defines an asynchronous function named `fetchTransactions`, which will be used to fetch transaction data from a GraphQL API.
    
2. `try {` initiates a try-catch block, where the code inside the `try` block will attempt to execute, and any errors that occur will be caught and handled in the `catch` block.
    
3. Inside the `try` block, a `fetch` request is made to your API URL
    
    * `method: "POST"` specifies that a POST request will be made.
        
    * `headers` specify the headers for the request, including the content type as JSON and an `x-api-key` header containing the API key.
        
    * `body` contains a GraphQL query as a JSON string. The query requests transaction data using the `transactionCollection` field, retrieving details such as `id`, `description`, `amount`, `date`, and `type`.
        
4. `const result = await response.json();`: This line awaits the response from the `fetch` request parses the response body as JSON, and assigns the parsed result to the `result` variable.
    
5. `console.log("API Response:", result);`: This logs the received API response to the console for debugging purposes.
    
6. Inside the `if` statement, the code checks if the `result` contains valid data and if the `transactionCollection` field is present:
    
    * If both conditions are met, the code extracts the transaction data from the `edges` using the `map` function and stores it in the `transactionsData` array.
        
    * The `setTransactions` function is used to update the state with the fetched transaction data.
        
    * The code calculates the highest ID among the fetched transactions and increments it by 1, setting it as the `currentId`.
        
7. `} catch (error) {`: This begins the `catch` block, where any errors that occurred during the `try` block execution will be caught and handled.
    
    * The code logs the error to the console for debugging purposes.
        

***NB:*** *Since we are using a single endpoint to access multiple versions of the same data, we use the* `POST` *method where we would have used the* `GET` *method in CRUD operations.*

### **Step 10: Displaying Transactions**

To display our transactions be it after fetching or adding them, we have to map through the transactions variable because our transactions are initially of type array.

```javascript
        <ul className="transaction-list">
          {transactions.map((transaction) => (
            <li
              key={transaction.id}
              className={`transaction-item ${
                transaction.type === "expense" ? "expense" : "revenue"
              }`}
            >
              <strong>{transaction.description}</strong> ${transaction.amount.toFixed(2)} ({transaction.date})
              <div className="button-group">
                <button className="edit-button">
                  Edit
                </button>
                <button className="delete-button">
                  Delete
                </button>
              </div>
            </li>
          ))}
        </ul>
```

### **Step 11: Adding Transactions**

Let's implement the functionality to add transactions. We shall be using the `handleAddTransaction` function.

```javascript
const handleAddTransaction = async () => {
    if (description && amount && date) {
      try {
        const response = await fetch(process.env.REACT_APP_MY_API_URL, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "x-api-key": API_KEY,
          },
          body: JSON.stringify({
            query: `
            mutation TransactionCreate($input: TransactionCreateInput!) {
              transactionCreate(input: $input) {
                transaction {
                  id
                  description
                  amount
                  date
                  type
                }
              }
            }
            `,
            variables: {
              input: {
                description,
                amount: parseFloat(amount),
                date,
                type,
              },
            },
          }),
        });
```

While the code may seem similar to that of fetching transactions, let's address the differences.

1. **Function Name and Purpose**:
    
    * The function name is `handleAddTransaction`. This function is specifically designed to handle the addition of a new transaction.
        
2. **Conditional Check**:
    
    * Before attempting to add a transaction, the code checks if the `description`, `amount`, and `date` values are all truthy (`if (description && amount && date)`). This ensures that all required fields have been provided before proceeding.
        
3. **Mutation Operation**:
    
    * Instead of a query, this code is using a GraphQL mutation operation. Mutations are used to modify data on the server. In this case, the `TransactionCreate` mutation is used to create a new transaction.
        
4. **Mutation Variables**:
    
    * The `variables` object is used to provide the necessary input data for the mutation. The `input` variable includes the `description`, `amount`, `date`, and `type` fields for the new transaction.
        
5. **Variables Placeholder**:
    
    * In the query, `$input` is used as a variable placeholder. This variable will be replaced with the actual `variables` object provided in the request.
        

### **Step 12: Deleting Transactions**

Now that we can fetch and add our transactions, let us work on deleting them.

```javascript
const handleDeleteTransaction = async (transactionId) => {
    try {
      const response = await fetch(process.env.REACT_APP_MY_API_URL, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "x-api-key": API_KEY,
        },
        body: JSON.stringify({
          query: `
            mutation TransactionDelete($id: ID!) {
              transactionDelete(by: { id: $id }) {
                deletedId
              }
            }
          `,
          variables: {
            id: transactionId,
          },
        }),
      });

      const result = await response.json();
      console.log("Delete Transaction Result:", result);

      if (result.data && result.data.transactionDelete) {
        const deletedId = result.data.transactionDelete.deletedId;
        const updatedTransactions = transactions.filter(transaction => transaction.id !== deletedId);
        setTransactions(updatedTransactions);

      }
    } catch (error) {
      console.error("Error deleting transaction:", error);    }
  };
```

The above code has the features explained below.

* **Function Name and Purpose**:
    

The function name is `handleDeleteTransaction`. This function is designed to handle the deletion of a transaction.

* **Mutation Operation**:
    

Similar to the previous code, this code also uses a GraphQL mutation operation (`TransactionDelete`) to delete a transaction. The mutation specifies that a transaction should be deleted based on the provided `transactionId`.

* **Mutation Variables**:
    

The `variables` object is used to provide the `transactionId` for the mutation. This variable is used to identify the transaction to be deleted.

* **Response Handling**:
    

After receiving the API response, the code checks if the `result` contains valid data and if the `transactionDelete` field is present.

If both conditions are met, the code extracts the `deletedId` from the response, which represents the ID of the deleted transaction.

The `updatedTransactions` array is created by filtering out the deleted transaction from the existing `transactions` array.

The state is updated with the `updatedTransactions` array using the `setTransactions` function.

* **Error Handling**:
    

Similar to previous examples, the code includes error handling in a `catch` block to log errors and display an error toast if the deletion process encounters an issue.

### **Step 13: Editing Transactions**

For editing transactions, the following steps can be followed:

1. `handleEditTransaction` **Function**:
    

```javascript
 const handleEditTransaction = (transaction) => {
    setDescription(transaction.description);
    setAmount(transaction.amount.toString());
    setDate(transaction.date);
    setType(transaction.type);
    setEditingTransaction(transaction);
  };
```

* This function is triggered when the "Edit" button is clicked on a transaction item in the UI.
    
* It takes a `transaction` object as an argument, which represents the transaction being edited.
    
* The function uses the individual state setter functions (`setDescription`, `setAmount`, `setDate`, `setType`, `setEditingTransaction`) to update the local state values with the properties of the `transaction` object. This pre-fills the input fields with the existing transaction data for editing.
    

1. `handleUpdateTransaction` **Function:**
    

```javascript
const handleUpdateTransaction = async () => {
    if (description && amount && date && editingTransaction) {
      try {
        const updatedTransaction = {
          description,
          amount: parseFloat({amount}),
          date,
          type,
        };
  
        const response = await fetch(process.env.REACT_APP_MY_API_URL, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "x-api-key": API_KEY,
          },
          body: JSON.stringify({
            query: `
            mutation TransactionUpdate($by: TransactionByInput!, $input: TransactionUpdateInput!) {
              transactionUpdate(by: $by, input: $input) {
                transaction {
                  id
                  description
                  amount
                  date
                  type
                }
              }
            }
            `,
            variables: {
              by: { id: editingTransaction.id },
              input: updatedTransaction,
            },
          }),
        });
  
        const result = await response.json();
        console.log("Update Transaction Result:", result);
  
        if (result.data && result.data.transactionUpdate) {
          const updatedTransactionData = result.data.transactionUpdate.transaction;
          const updatedTransactions = transactions.map(transaction =>
            transaction.id === updatedTransactionData.id ? updatedTransactionData : transaction
          );
          setTransactions(updatedTransactions);
  
          setDescription("");
          setAmount("");
          setDate("");
          setType("expense");
          setEditingTransaction(null);
  
        }
      } catch (error) {
        console.error("Error updating transaction:", error);
      }
    }
  };
```

* This function is triggered when the "Update" button is clicked after editing a transaction.
    
* It checks if all required fields are filled (`description`, `amount`, `date`) and if an `editingTransaction` is set (indicating an edit operation).
    
* It creates an `updatedTransaction` object with the edited values, preparing data for the mutation.
    
* It sends a mutation (`TransactionUpdate`) to update the transaction data on the server.
    
* After receiving the response, it updates the UI state with the edited transaction and clears the input fields.
    

1. **Transaction List Rendering**:
    

```javascript
        <ul className="transaction-list">
          {transactions.map((transaction) => (
            <li
              key={transaction.id}
              className={`transaction-item ${
                transaction.type === "expense" ? "expense" : "revenue"
              }`}
            >
              <strong>{transaction.description}</strong> ${transaction.amount.toFixed(2)} ({transaction.date})
              <div className="button-group">
                <button className="edit-button" onClick={() => handleEditTransaction(transaction)}>
                  Edit
                </button>
                <button className="delete-button" onClick={() => handleDeleteTransaction(transaction.id)}>
                  Delete
                </button>
              </div>
            </li>
          ))}
        </ul
```

* This JSX code fragment renders a list of transactions using the `map` function.
    
* For each transaction, it displays the `description`, `amount`, and `date` properties in the UI.
    
* It renders "Edit" and "Delete" buttons for each transaction, which invoke the `handleEditTransaction` and `handleDeleteTransaction` functions respectively.
    

### **Step 14: Styling the App**

To make the app visually appealing, we'll apply some basic styling. You can refer to the provided `App.css` styles below:

```css
.App {
  font-family: Arial, sans-serif;
  text-align: center;
  margin: 0 auto;
  max-width: 800px;
  padding: 20px;
  background-color: #f5f5f5;
}

h1,
.title {
  font-size: 24px;
  margin-bottom: 20px;
}

.transaction-form {
  display: flex;
  flex-direction: column;
  margin-bottom: 20px;
}

.input {
  padding: 10px;
  margin: 5px 0;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.add-button,
.update-button,
.cancel-button {
  padding: 10px;
  margin: 5px;
  background-color: #007bff;
  color: #fff;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.add-button:hover,
.update-button:hover,
.cancel-button:hover {
  background-color: #0056b3;
}

.error {
  color: red;
  margin-top: 10px;
}

.transaction-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.transaction-item {
  background-color: #fff;
  border: 1px solid #ccc;
  padding: 10px;
  margin: 5px 0;
  border-radius: 4px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.transaction-content {
  display: flex;
  align-items: center;
}

.transaction-description {
  font-weight: bold;
}

.transaction-amount {
  margin-left: 15px;
  font-size: 18px;
}

.transaction-item button {
  padding: 5px 10px;
  margin-left: 10px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.transaction-item button.edit-button {
  background-color: #007bff;
  color: #fff;
}

.transaction-item button.edit-button:hover {
  background-color: #0056b3;
}

.transaction-item button.delete-button {
  background-color: #dc3545;
  color: #fff;
}

.transaction-item button.delete-button:hover {
  background-color: #c82333;
}

.expense {
  background-color: #ffc4c4;
}

.revenue {
  background-color: #c1f0c1;
}

.total {
  margin-top: 10px;
  font-size: 18px;
  font-weight: bold;
}
```

### **Step 15: Test the app**

With that, we can now see what we have come up with. Isn't it amazing! The full code can be accessed on my [GitHub page](https://github.com/KaburaJ/Grafbase-TransactionTracker.git) where features such as user feedback with Toastify have been added.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692081423154/b822777b-f6a5-4af1-bbc9-23ac15e68835.png align="center")

Below is a video of the application in action.

%[https://youtu.be/_jKCl7UjchY] 

If we want to view the metrics of our API and the number of requests sent to it, we can simply visit the Grafbase website and navigate to our previously created project. A sample of that can be accessed in the image below.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692118008151/42ae348c-5fef-429a-b31f-41572454cc70.png align="center")

## **Conclusion**

A lot can be done in improving this application. A good example is the addition of a registration and login page. Other improvements to the code might include the incorporation of components to make the code reusable as well as using Apollo and Relay to make the code more readable and clean. Perhaps, you may consider these additional features as my challenge to you even as you implement your version of this transaction tracker.

The above was my submission for the [Grafbase Hackathon](https://hashnode.com/n/grafbasehackathon) in conjunction with [Hashnode](https://hashnode.com/). Thank you, guys, for reading my article! Feedback in the form of questions and comments is welcome.
