Organizing a React Web Application

Organizing a React Web Application

This story was originally written here on my blog. If you enjoy my research, you can subscribe to me on Ghost or Substack and follow me on Twitter

One of the things that’s irritated me as of late is how little attention is paid to how to properly organize front and back end code. I know this is probably a super subjective (and, knowing the dev community, controversial) discussion, so I wanted to contribute to it in an attempt to see how other methods compare. This is an organizational methodology that I’ve evolved over time in my React projects, and I can almost guarantee it’s not the best methodology in one way or another. I don’t want to present it as the way to organize your code, just a way I have organized my React code, both by natural development methodology and by observing other people’s code.

So, with that disclaimer out of the way, let’s dive in.

STACS (Services, Types, Assets, Components and Styles)

Quick note: I’m going to be developing an intentionally simple project alongside this blog to explain my methodology. Yes, it’s incredibly simple, not meant to describe every use case and will probably have issues. Bear with me. You can find the full code on GitHub here.

One of my favorite things about React is the component organizing methodology. This allows for libraries like PrimeReact that are functionally collections of pre-built, sleek and professional looking components and styles, as well as an organized hierarchy of components that can be changed without worrying (too much) about dependencies up or down stream.

I imagine there is plenty of debate about what constitutes a component. I envision some people getting way too granular with components, essentially forming a new component for each and every piece of code to minimize how much HTML they have to write in any given component. That seems… incredibly chaotic and inefficient. For me, I use components to represent disparate pieces of a UI. If I have a div that contains a couple of related data, I’ll make that a component, but unless each individual piece of data requires its own state that can’t be managed by a parent component (which would be rare, I would think) I’ll usually group these related pieces of data in a component and manage the state there.

State is honestly one of the most important governing principles when organizing components. Each individual component will contain code that manages its own state and passes state down from parent components. There are also methods of passing code up to parent components by passing setter functions down from parent components to child components, but I’d imagine that’s probably a model to avoid.

This is probably the most subjective parts of the project, but I’m going to try to keep things CSS framework-agnostic since I tend not to use CSS frameworks, mainly out of sheer laziness to learn them. I will create a folder in the components directory per each major component. That folder will contain types relevant to the component in a Types subdirectory (if I’m doing typescript), a Styles subdirectory that will contain exported style objects, and any child components used only by the major component. I will also create a Services directory containing services that are only required by the major component or its children.

I think Services are important enough to warrant some discussion. The way I view Services is any function whose main purpose is data fetching or manipulation. To me, there is some value in separating those functions from the main front end code, in the same way that separating styling objects from the main front end code is useful. I want to keep my component code as clean as possible, only pertaining to the actual structural code as possible. This was hard to do coming from an Object Oriented background, because it basically means separating out a lot of the “Object properties” like styles and “Object functions” like data fetching and manipulation out of the “Object” itself, but I think the end result is far more organized and readable.

We’re starting to get a bit in the weeds on theory, so let me demonstrate using an example. I’m building out a simple scraping dashboard for another article, but since I’m using React for the front end, I can pretty easily crib that code for this one as well. I’m lazy, get over it. I’m going to leave the full directory listing here for future reference throughout the article.

.
├── backend
│   ├── api.py
│   ├── data.json
│   ├── __pycache__
│   │   └── api.cpython-310.pyc
│   └── tempdata.json
└── scrapeboard_fe
    ├── package.json
    ├── package-lock.json
    ├── README.md
    ├── src
    │   ├── App.tsx
    │   ├── Components
    │   │   ├── Header
    │   │   │   ├── Header.tsx
    │   │   │   └── style.js
    │   │   ├── ScrapeTable
    │   │   │   ├── ScrapeTable.tsx
    │   │   │   ├── Services
    │   │   │   │   └── tableservice.ts
    │   │   │   ├── style.js
    │   │   │   └── Types
    │   │   │       └── Scrape.ts
    │   │   └── Stats
    │   │       ├── services
    │   │       │   └── scrapeservice.js
    │   │       ├── Stats.tsx
    │   │       └── style.js
    │   ├── index.tsx
    │   └── react-app-env.d.ts
    └── tsconfig.json

11 directories, 20 files

Right now, we will have three components: a simple header, a stats display with an embedded button and a table containing information on our scrapes. The Header component will just have a piece of text, so no need for a Type or Service folder here. It’s just a raw, simple React functional component with a single style file. The style file just contains a Style object with styling information inside. If there were more than one piece that I wanted styled different within this components, I would create sub-objects within the Style object that contain styles for disparate objects. In this case, though, we’re keeping it simple.

// Header.tsx
import React from 'react'
import Style from './style'

export default function Header() {
  return (
    <div style={Style as React.CSSProperties}>
      ScrapeBoard
    </div>
  )
}
//style.js
const style = {
    marginRight:'auto',
    marginLeft: 'auto',
    fontSize:'36px',
		textAlign:'center'
}

export default style;

Next, the Stats component. This one is going to have styles and services, so it will really show off the value of this organizing methodology. I’ll leave the code below and walk through it after you look it over.

// Stats.tsx
import React from 'react'
import Style from './style'
import ScrapeService from './services/scrapeservice'

export default function Stats() {
  const [scrapes, setScrapes] = React.useState(0)
  const [ss, setSS] = React.useState(new ScrapeService)
  
  React.useEffect(() => {
    async function setter() {
      setScrapes(await ss.getScrapes())
    }
    setter()
  },[scrapes])

  async function clicker(){
    async function setter() {
      setScrapes(await ss.incScrapes())
    }
    setter()
  }

  return (
    <div style={Style.container}>
      <p>Scrapes: {scrapes}</p><br/>
      <button onClick={clicker}>Scrape again!</button>
    </div>
  )
}
// Stats/services/scrapeservice.js
import axios from 'axios'

class ScrapeService {
    async getScrapes(){
        let res = await axios.get('<http://127.0.0.1:8000/get_scrapes>')
        return res.data.scrapes
    }

    async incScrapes(){
        let res = await axios.post('<http://127.0.0.1:8000/inc_scrapes>')
        return res.data.scrapes;
    }
}

export default ScrapeService
//Stats/style.js
const style = {
    container : {
        marginLeft:"50px",
        fontSize: "24px"
    }
}
export default style;

Now I know I probably handled the React code to update the state in the least efficient way possible. I’m working on it, okay? Think more about the structure of the project files and less about my ugly React code.

All this really does is send off a POST request to our simple API, which increments a piece of data in a JSON file and then sends the new value back. It grabs the initial value by sending a GET request. As simple as it could possibly get.

What I want to highlight here is the way the code is separated. The component code (Stats.tsx) only contains code pertaining to the state of the component data and the way it is structured. The service code (scrapeservice.js) only contains code pertaining to actually fetching data. The style code (style.js) only contains code relevant to styling. Everything is separated out and organized in a pretty manner without really adding much, if any, complication. If I need to change something about the style, I shouldn’t have to do much changing of the component code, unless I add a new sub-object in the style file that I need to implement in the component. If I need to change something functional with how the front end interacts with the backend, the vast majority of those changes will be made in the service file.

It’s a thing of beauty, to me, but it also might just be my preferred chaotic aesthetic.

Finally, the ScrapeTable. This one is pretty much going to implement everything we talked about earlier. We will have a Type file in the Types folder, a service class exported to the main component and a Style file exporting multiple objects.

Before I give you the code, I will note that I changed up some of the state handling in between writing the last section and writing this one. The reason being, I want the ScrapeTable to update when the state of the scrapeCount is updated, so now the scrapeCount state is handled by the App component instead of the Stats component, and the setter for the scrapeCount variable is passed down to the Stats component while the ScrapeTable component gets the value of scrapeCount to monitor for changes. When the button (located in the Stats component) is pressed and the result is returned from the backend, the scrapeCount variable is updated, which triggers an update in the ScrapeTable component that updates the table with the newest values.

I know, exhausting… but here is how the ScrapeTable component is laid out.

//ScrapeTable.tsx

import React from 'react'
import TableService from './Services/tableservice'
import Scrape from './Types/Scrape'
import Style from './style'

export default function ScrapeTable(props : {scrapecount : number}) {
  const [ts, SetTableService] = React.useState(new TableService())
  const [scrapes, setScrapes] = React.useState(Array<Scrape>())
  React.useEffect(() => {
    async function getter(){
      let tempscrapes : Array<Scrape> = await ts.getScrapes()
      setScrapes(tempscrapes)
    }
    getter()
  }, [props.scrapecount])

  return (
    <div>
      <table className="scrape-table" style={Style.table as React.CSSProperties}>
        <tr className="table-headers" style={Style.tableRow}>
          <th>ID</th>
          <th>Time</th>
          <th>Temp</th>
          <th>Description</th>
          <th>Delete?</th>
        </tr>
        {scrapes.map((s,i) => {
          return(
            <tr style={Style.tableRow as React.CSSProperties} key={i}>
              <td>{i}</td>
              <td>{s.time}</td>
              <td>{s.temp}</td>
              <td>{s.desc}</td>
              <td></td>
            </tr>
          )
        })}
      </table>
    </div>
  )
}
//ScrapeTable/Services/tableservice.js
import axios from "axios";
import Scrape from "../Types/Scrape"

class TableService {
    async getScrapes(){
        let Scrapes : Array<Scrape> = [];
        let res = await axios.get('<http://127.0.0.1:8000/get_scrapes>')
        console.log('Res data: ',res.data)
        let resarr : Array<{time : number, temp : number, desc : string}> = res.data['scrapelist'];
        resarr.forEach((ele) => {
            Scrapes.push({time:ele.time, temp:ele.temp, desc:ele.desc})
        })
        console.log('Number of scrapes: ',Scrapes.length)
        return Scrapes;
    }
}

export default TableService
//ScrapeTable/style.js
const Style = {
    table:{
        width:"80%",
        border:"1px solid black",
        borderCollapse: "collapse",
        textAlign:"center",
        marginLeft:"auto",
        marginRight:"auto",
        marginTop:"30px"
    },
    tableRow:{
        border:"1px solid black"
    }
}
export default Style
//ScrapeTable/Types/Scrape.ts
type Scrape = {
    time:number,
    temp: number,
    desc: string
}

export default Scrape;

I think the code (and my endless rambling in this article thus far) speaks for itself, more or less, so I’m not going to belabor the point. I will note again, for those who likely ignored it the first time, I’m not really saying that this is necessarily the best way to lay out a React project. I’ll also admit there are probably some issues with my React code… This article isn’t meant to serve as the perfect, end-all React organization system, it’s merely the way I do things… and, frankly, it works.