The application will need to manage 2 different kinds of data: bills and categories.

Bills will be displayed in the BillsTable component, while categories will be shown in the NavBar component.

Despite being displayed in separate components, they will be both handled inside the App component, because that’s where we’ll centralize the data management.

Now, before being able to do anything in the app, when we start we want to show a box where the user adds the first category: the AddCategory component.

We do this by checking in the JSX if we should show that component or not. I’m going to add a property called shouldShowAddCategory to the App component state, using Hooks, and check that. We initialize it to true, for the time being.

const [shouldShowAddCategory, setShouldShowAddCategory] = useState(true)

We use conditional rendering, a technique that allows us to check some property and render something different depending on its value:

function App() {
  const [shouldShowAddCategory, setShouldShowAddCategory] = useState(true)
  return (
    <div className="App">
      {shouldShowAddCategory ? (
        <AddCategory />
      ) : (
        <div>
          <NavBar />
          <div className="container flex">
            <div className="w-1/2">
              <BillsTable />
            </div>
            <div className="w-1/2">
              <Chart />
            </div>
          </div>
        </div>
      )}
    </div>
  )
}

Let’s add a form to the AddCategory component now. We’ll make it simple, just a single input field, and a button to add the new category.

import React from 'react'

export default () => {
  return (
    <form>
      <h1>Enter a category of bills</h1>
      <p>E.g. 'Electricity' or 'Gas' or 'Internet'</p>
      <input placeholder="Add category" />
      <button>Add</button>
    </form>
  )
}

When the user enters a value in the input field, the onChange event handler is fired, and we store the new value in it:

import React, { useState } from 'react'

export default () => {
  const [category, setCategory] = useState()

  const handleChange = e => {
    setCategory(e.target.value)
  }

  return (
    <form>
      <h1>Enter a category of bills</h1>
      <p>E.g. 'Electricity' or 'Gas' or 'Internet'</p>
      <input placeholder="Add category" value={category} onChange={handleChange} />
      <button>Add</button>
    </form>
  )
}

On click of the “add” button, we add an handleSubmit event handler, which triggers the onSubmit event handler we pass as a prop by App. We need to call preventDefault() on the event object to avoid the default browser behavior that happens when we submit a form:

import React, { useState } from 'react'

export default (props) => {
  const [category, setCategory] = useState()

  const handleChange = e => {
    setCategory(e.target.value)
  }

  const handleSubmit = e => {
    e.preventDefault()
    if (!category) {
      alert('Please enter a category')
      return
    }

    props.onSubmit(category)
  }

  return (
    <form>
      <h1>Enter a category of bills</h1>
      <p>E.g. 'Electricity' or 'Gas' or 'Internet'</p>
      <input placeholder="Add category" value={category} onChange={handleChange} />
      <button onClick={handleSubmit}>Add</button>
    </form>
  )
}

in src/index.js:

<AddCategory onSubmit={addCategory} />

We use hooks to create a new state for the categories list:

const [categories, setCategories] = useState([])

and we implement the addCategory() method:

const addCategory = category => {
  const updatedCategories = [...(categories || []), category]
  setCategories(updatedCategories)
  setShouldShowAddCategory(false)
}

the [...(categories || []), category] construct allows us to create a new array from the existing categories, appending a new one. Takes care of the spread when the categories array is null or undefined, defaulting it to an empty array with || [].

Here’s the full code of App up to now:

import React, { useState } from 'react'
import ReactDOM from 'react-dom'

import './styles.css'

import AddCategory from './components/AddCategory.js'
import AddBill from './components/AddBill.js'
import NavBar from './components/NavBar.js'
import Chart from './components/Chart.js'
import BillsTable from './components/BillsTable.js'

function App() {
  const [shouldShowAddCategory, setShouldShowAddCategory] = useState(true)
  const [categories, setCategories] = useState([])

  const addCategory = category => {
    setCategories([...categories, category])
    setShouldShowAddCategory(false)
  }

  return (
    <div className="App">
      {shouldShowAddCategory ? (
        <AddCategory onSubmit={addCategory} />
      ) : (
        <div>
          <NavBar />
          <div className="container flex">
            <div className="w-1/2">
              <BillsTable />
            </div>
            <div className="w-1/2">
              <Chart />
            </div>
          </div>
        </div>
      )}
    </div>
  )
}

const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)

Now when you enter a category name, the form goes away and you see the rest of the application.

The form, however, is quite ugly! Since we use Tailwind, we just need to add some classes to the template to make it look good:

import React, { useState } from 'react'

export default props => {
  //...

  return (
    <form className="h-100 w-full flex items-center justify-center font-sans">
      <div className="bg-white rounded shadow p-6 m-4 w-full lg:w-3/4 lg:max-w-lg">
        <div className="mb-4">
          <h1 className="text-grey-darkest">Enter a category of bills</h1>
          <p>E.g. 'Electricity' or 'Gas' or 'Internet'</p>
          <div className="flex mt-4">
            <input
              className="shadow appearance-none border rounded w-full py-2 px-3 mr-4 text-grey-darker"
              placeholder="Add category"
              value={category}
              onChange={handleChange}
            />
            <button
              className="flex-no-shrink p-2 border-2 rounded bg-teal text-white border-teal hover:text-white hover:bg-teal"
              onClick={handleSubmit}
            >
              Add
            </button>
          </div>
        </div>
      </div>
    </form>
  )
}

I basically added some classes, plus an additional container div. Every class in Tailwind performs a single thing. So for example m-4 adds 1rem of margin, p-6 adds a 1.5rem padding, and so on. You are welcome to discover all those classes on the official Tailwind docs, they are not React related, but rather add some nice styling to our app.

If you think those classes clutter the HTML, you’re right, but take a look at how we didn’t add a single line of CSS to make the form look good.

You can see the app at this point in this CodeSandbox.


Go to the next lesson