Overview

Let’s talk about React.js!

Of course, Python will always be my native tongue, and it’s the best, no doubt! But if I had to pick a second place, it would be JavaScript and TypeScript. Among the frontend frameworks for JS and TS, one of the most well-known is React.js.

This time, I'll walk you through:

  • Using yarn,
  • Setting up react,
  • Introducing vite-tsconfig-paths to enable writing src/ as @/,
  • Preparing basic layers for pages, components, and utils,
  • Incorporating react-router-dom to make it a SPA,
  • Deploying it to GitHub Pages,

up to the stage just before adding your own unique touch.

 

Points to Note

  • Project name: my-react
    • In this article, the project name (= GitHub repository name) is my-react. Feel free to replace this with any name you prefer.
  • For GitHub Pages, we won’t use a custom domain; instead, we’ll keep it simple with https://foo.github.io/my-react/.

...Honestly, I don’t think there are many programming enthusiasts among the dear readers of Midori HP!

 

Using yarn

Get node and yarn ready. Use version 1 of yarn.

node --version
# --> v20.11.0

yarn --version
# --> 1.22.22

 

Setting up react

yarn create vite my-react -- --template react-swc-ts
# Created with React + TypeScript + SWC

cd my-react

yarn install
# yarn.lock

yarn list react
# --> └─ react@18.3.1
# NOTE: Warnings may appear, but that’s due to yarn list lacking exact match search.

yarn dev --host
# Yep, it opens.

yarn build
# Great, the dist folder is created.

yarn preview --host
# Nice, the content in dist opens.

# *.tsbuildinfo is a build cache, so exclude it from version control.

 

Deploying to GitHub Pages

Personally, I like to set up the deployment environment right at the start. After all, the deployment environment is the goal, so it makes sense to establish it early on.

First, set the base URL in vite.config.ts like this:

// Add this in vite.config.ts...

export default defineConfig({
  base: '/my-react/', // <-- This part
})

Here's a sample yaml for deploying to GitHub Pages. Pushing this to GitHub will create a gh-pages branch. Then, go to Settings > Pages and publish that branch.

# .github/workflows/deploy-react-application.yml

name: Deploy React Application

on:
  workflow_dispatch:

  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    # The one that’s easy to forget.
    permissions:
      contents: write

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"

      - name: Install dependencies
        run: yarn install

      - name: Build
        run: yarn build

      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v4
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          # Default build output for React is dist.
          publish_dir: ./dist

 

Introducing vite-tsconfig-paths

Writing imports like ../../../components/Button is too much of a hassle, so let’s make it possible to write @/components/Button instead.

yarn add -D vite-tsconfig-paths
# Installed vite-tsconfig-paths

Referencing the official README, add the following to vite.config.ts:

// vite.config.ts

import react from '@vitejs/plugin-react-swc'
import { defineConfig } from 'vite'
import tsconfigPaths from 'vite-tsconfig-paths'

export default defineConfig({
  base: '/my-react/',
  plugins: [react(), tsconfigPaths()],
})

It’s not very clear in the official README, but you’ll also need to edit tsconfig.json like this:

// tsconfig.json

{
  "compilerOptions": {
    // ... omitted for brevity ...

    // Configuration for vite-tsconfig-paths to map @ to src/.
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src"]
}

Note that in recent setups, tsconfig might be split into multiple files. If your tsconfig.json looks like this:

{
  "files": [],
  "references": [
    { "path": "./tsconfig.app.json" },
    { "path": "./tsconfig.node.json" }
  ]
}

The baseUrl and paths configuration should go in tsconfig.app.json, not tsconfig.json. As a rule of thumb, make changes in the file that already contains include.

// tsconfig.app.json

{
  "compilerOptions": {
    // ... omitted for brevity ...

    // Configuration for vite-tsconfig-paths to map @ to src/.
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src"]
}

 

Creating layers for pages, components, and utils

Starting with an empty React setup can leave you wondering where to go next. Let’s establish a foundation.

# Create folders.
mkdir -p src/{pages,components,utils}

# Create src/components/Button.tsx
echo 'type ButtonProps = {
  label: string
  onClick: () => void
}

function Button({ label, onClick }: ButtonProps) {
  return <button onClick={onClick}>{label}</button>
}

export default Button' > src/components/Button.tsx

# Create src/utils/formatDate.ts
echo 'function formatDate(date: Date): string {
  return date.toLocaleDateString("en-US", {
    year: "numeric",
    month: "long",
    day: "numeric",
  })
}

export default formatDate' > src/utils/formatDate.ts

# Create src/pages/HomePage.tsx
echo 'import Button from "@/components/Button"
import formatDate from "@/utils/formatDate"
import { useEffect, useState } from "react"

function HomePage() {
  const [formattedDate, setFormattedDate] = useState<string>('')

  useEffect(() => {
    const currentDate = new Date()
    const dateStr = formatDate(currentDate)
    setFormattedDate(dateStr)
  }, [])

  const handleClick = () => {
    alert("Button clicked!")
  }

  return (
    <div>
      <h1>Welcome to the Home Page - {formattedDate}</h1>
      <Button label="Click Me" onClick={handleClick} />
    </div>
  )
}

export default HomePage' > src/pages/HomePage.tsx

# Create src/pages/NotFoundPage.tsx
echo 'function NotFoundPage() {
  return (
    <div>
      <h1>Not Found</h1>
    </div>
  )
}

export default NotFoundPage' > src/pages/NotFoundPage.tsx

Now the files are set up, but HomePage.tsx isn’t in use yet. We’ll handle that in the next step.

 

Using react-router-dom to utilize pages

Install the package and edit main.tsx and App.tsx.

yarn add react-router-dom

echo 'import App from "@/App"
import "@/index.css"
import React from "react"
import ReactDOM from "react-dom/client"
import { BrowserRouter } from "react-router-dom"

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <BrowserRouter basename="/my-react">
      <App />
    </BrowserRouter>
  </React.StrictMode>
)' > src/main.tsx

echo 'import "@/App.css"
import HomePage from "@/pages/HomePage"
import NotFoundPage from "@/pages/NotFoundPage"
import { Route, Routes } from "react-router-dom"

function App() {
  return (
    <Routes>
      <Route path="/" element={<HomePage />} />
      {/* All other paths lead to 404 */}
      <Route path="*" element={<NotFoundPage />} />
    </Routes>
  )
}

export default App
' > src/App.tsx

And that's it!

 

Wrapping Up

Pushing this setup to GitHub Pages should make it work like this:

From here, you can add more screens by expanding the pages, add more UI components by growing the components, or centralize logic by expanding utils! Having a foundation like this makes the process so much easier.

I started with React.js thinking it might be a good fit for the front end of Midori HP. Vue.js was also an option, but since Vue 3 seems to have taken on some characteristics of React.js, I figured, “Hmm, maybe React.js is the better choice?”