The day has come to wrap up the ASP.NET Core Budgeting App Series.

To recap, we:

Which brings us to NOW. This is how I put together the basic UI with React and a summary tying it all together.

Building a client app using React and Axios

This React thing is cool, you guys.

The API is small and simple, and there are established patterns to keep everything modular, clean, and easy to reason about. The tutorials straight from the source are very good. I didn’t run into any issues requiring google-search-tangents to get unstuck, and that’s pretty cool.

To get myself up to speed, I used this tutorial, ignoring the initial .NET extension stuff. We didn’t use that, if you recall. The simple comments-in-a-comments-list example mapped similarly to our bank-transactions-in-a-transaction-list.

Another important article I read was this Thinking In React piece. Essential reading for React newcomers, it showed me how to get started building the components of the app by drawing out the UI and turning it into a hierarchy.

Excuse my left-handed chicken scratch, but as an example, this is a mock of the transaction screen.

A possible hierarchy for this would be something like:

  • App
    • Header
    • Account List
      • Account List Item
    • Transactions Panel
      • Transaction Table
        • Transaction Table Row
      • New Transaction Form

This gave me a nice information architecture to lead the development. Some folks like to go top down, but in this case I went bottom up by starting with the Account List Item as my first component.

import React from 'react';

export default class AccountListItem extends React.Component {

    constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
        this.props.onSelected(this.props.account.id);
    }

    render() {
        return <li><a href="#" onClick={this.handleClick}>{this.props.account.name}</a></li>;
    }
}

As you can see, the AccountListItem component is only responsible for rendering a single <li></li>. By using props, We are essentially creating a contract for parent components. They will need to provide an account (like a checking account) and will need to have an onSelected handler that accept the account’s ID to react to clicks on the account item.

In the context of this UI, clicking on an account will signal the app to display the transactions for that account and will be the current context for new transactions added through the form. It will be the active account, so to speak.

Another great piece of advice is to build everything static at first, don’t worry about data. You can simply work with a hardcoded object to fill your UI. Kind of like - build all the markup first, then later worry about inflating it with data.

Our AccountList is even simpler:

import React from 'react';
import AccountListItem from './AccountListItem.jsx';

export default class AccountList extends React.Component {

    render() {
        return <ul>{this.props.accounts.map((account) => <AccountListItem account={account} onSelected={this.props.onItemSelected} key={account.name} />)}</ul>;
    }
}

We respect our AccountListItem’s contract by providing an onSelected handler and simply pass it on up the chain of props, thereby creating a further contract. The reason we pass it up again is because our TransactionTable needs to know about changes to the active account, so it is useful to have the common ancestor component handle it. The common ancestor to all components that need to know about the active account will be our App component.

This is also a good example of uni-directional data flows. React does not use two-way binding, so all your data and events start at some point and flow up or back down the hierarchy via props.

Okay, I’m going to skip over the discussion of the TransactionTable since it is almost identical to our AccountList and AccountListItem except that it is using a <table> and <tr> rows instead of a ul list of <li> items. I’m also going to skip over the NewTransactionForm for brevity.

The final piece is bringing all of our components together into our main App component and connecting it to our Web API using the Axios ajax javascript library.

First, a light wrapper around Axios to give it a friendlier API:

import axios from 'axios';

export default class BudgetAPI {
 
    static getAccounts() {
        return axios.get("http://localhost:5051/api/accounts");
    }

    static addTransaction(transaction) {
        return axios.post("http://localhost:5051/api/transactions", transaction);
    }

    static deleteTransaction(transactionId) {
        return axios.delete("http://localhost:5051/api/transactions/" + transactionId);
    }
}

You wouldn’t hardcode paths like this, of course, but this is a demo!

Because this app is served on a different port than the Web API, I had to enable cross-origin requests to get Axios to work. In the Web API’s Startup.cs file, two changes were needed:

In the ConfigureServices() method:

services.AddCors();

And in the Configure() method:

app.UseCors(builder =>
          builder
             .WithOrigins("http://localhost:5000")
             .AllowAnyMethod()
             .AllowAnyHeader()
             );

Don’t hardcode that. :)

And the App component:

import React from 'react';
import Header from './header.jsx';
import AccountList from './account/AccountList.jsx';
import TransactionTable from './transaction/TransactionTable.jsx';
import NewTransactionForm from './transaction/NewTransactionForm.jsx';
import BudgetApi from '../data/BudgetApi';

export default class App extends React.Component {

    constructor() {
        super();
        this.state = {
            accounts: [],
            selectedAccountId: 0
        };
        this.handleAccountListItemSelected = this.handleAccountListItemSelected.bind(this);
        this.handleNewTransactionFormSubmitted = this.handleNewTransactionFormSubmitted.bind(this);
        this.handleDeleteTransactionClicked = this.handleDeleteTransactionClicked.bind(this);
        this.getSelectedAccount = this.getSelectedAccount.bind(this);
        this.refreshAccounts = this.refreshAccounts.bind(this);
    }

    render() {
        return (
            <div>
                <Header />
                <AccountList accounts={this.state.accounts} onItemSelected={this.handleAccountListItemSelected} />
                <TransactionTable account={this.getSelectedAccount()} onDeleteTransactionClicked={this.handleDeleteTransactionClicked} />
                <NewTransactionForm account={this.getSelectedAccount()} onFormSubmitted={this.handleNewTransactionFormSubmitted} />
            </div>
        );
    }

    componentDidMount() {
        this.refreshAccounts();
    }

    refreshAccounts() {
        BudgetApi.getAccounts()
            .then(response => {
                this.setState({
                    accounts: response.data
                });
            });
    }

    handleAccountListItemSelected(selectedAccountId) {
        this.setState({
            selectedAccountId: selectedAccountId
        });
    }

    handleNewTransactionFormSubmitted(transaction) {
        BudgetApi.addTransaction(transaction)
            .then(response => {
                this.refreshAccounts();
            });
    }

    handleDeleteTransactionClicked(transactionId) {
        BudgetApi.deleteTransaction(transactionId)
            .then(response => {
                this.refreshAccounts();
            });
    }

    getSelectedAccount() {
        for (var i = 0; i < this.state.accounts.length; i++) {
            if (this.state.accounts[i].id == this.state.selectedAccountId) {
                return this.state.accounts[i];
            }
        }
    }
}

The app begins by pulling a fresh set of accounts in componentDidMount(). We pass the accounts down to theAccountList . The active account is determined by a call to getSelectedAccount(). You’ll also notice that the final implementations of the click handlers live up here. When an account is clicked, the selectedAccountId state is changed which causes our TransactionTable and our NewTransactionForm to be updated.

That’s all there is to it! One final touch-up before I took this screenshot was to wrap some of the components in <div>’s and add Bootstrap class names to make it pretty. Kind of… :)

Conclusions

We made it.

Granted, we’ve really only scratched the surface of ASP.NET Core, but it’s enough of a start to give us some useful first impressions.

I’ve got to say, my favorite part about all of this was how easy it was to keep the code clean and modular.

Built in dependency injection is a beautiful thing.

Ask the folks over at Android what it’s like getting dependency injection set up…

Command line tools are much appreciated. Some people like menus and buttons, and that’s fine for big daddy Visual Studio, but being able to work from a lightweight text editor and the command line is awesome, and brings me back to my college roots.

Entity Framework Core saves a ton of time. EF gets a lot of heat on reddit, and maybe things would go haywire if this project got really big, but scenarios where your data models and relationships are normal, go for it. Having my data handled just by writing a POCO and a couple of migration commands is…well…it’s great..dude.

Start using ASP.NET Core!

No more “should I use framework or core?” questions. Jump into Core, reference full framework if you need it, and have plan in case of changes. Don’t be scared :)

And shout out to React. I mentioned my thoughts about it earlier in the post, but I’ll say it again, it’s been a blast. I have a feeling I’ll use it in future projects to come.

Prior series posts: Part 1, Part 2, Part 3, Part 4