React Locally

Create my own react starter kit

Few words

I thought it might be a good idea to write a few words about why I went to the trouble of creating my own starter kit.

With every new project, I used to copy what I did on the previous project, remove everything except the boilerplate, add a few tools and refactor the code a bit. Rinse and repeat.

So, I created my first react starter kit react-locally to avoid going through this painful procedure every time I started a project, with the goal of getting me up and running faster with all the tools and libraries I needed.

Why I create my own?

As you may know, Facebook created their own starter kit called create-react-app, developed by the React community, and maintained by Facebook. It includes all the basics needed to get started, which is why it’s so popular and used by a lot of developers.

So, why didn’t I just use that?

The serious answer is that, I don’t know JavaScript, and there is so many dependencies and devDependency in that project. Looking inside it feels like I’m dead. That’s why I threw myself into a painful procedure of creating my own react starter kit.

Another answer is that, as a poor and low cost web developer, I eat CSS. But when it comes to JavaScript books or projects, 80% of it goes over my head. I set a basic rule— if I don’t know what this framework or plugin is for, I won’t use it.

So I have to know, or learn first.

The funny thing is, when it comes to learning new things, my brain acts like a sloth, so I go slow.

What’s in it?

I need a few features to ensure my development workflow is fast, structured, and efficient. So I choose:

  • 🔥 vite as build tool. Its hot reloading and page refreshing in blessing fast.
  • 👀 react as the view.
  • 👮 typescript as catch types errors early in editor.
  • 🧹 eslint, eslint-airbnb-config and prettier - combines performing code formatting. Stop worrying about code style consistency.
  • 🖌️ dart-sass support - I extend it with CSS Modules.
  • 🔀 react-router-dom v6 as the router.
  • 🪖 react-helmet-async provides control of the page title/meta/styles/scripts from within my components. SEO friendly.
  • 🧪 vitest as the test framework.

All in all there are 24 dependencies in the react-locally starter pack… I try my best to keep them up to date.

Why I keep it open source?

For me. Simple.

Imagine you’re in a huge library, trying to find a book. Sometimes, you forget where you put it! That’s me with my code. So, I keep it open source, like leaving a map for myself.

And, I believe there is someone who might have similar needs as mine. Feel free to check it out, folk. Contribute if you think something’s missing.

And if you just want to get started, the quickest way, if you already have git and node, is this:

git clone -b main git@github.com:zafree/react-locally.git your-project
cd your-project
yarn install
yarn dev

Or if you’re curious about how I crafted it, then keep scrolling. '

Start writing the process

At last, it’s time to learn something new. My brain is already starting to act like a sloth. So I will go slow. If you’re following, follow me slowly.

Before starting, I break down the entire process into simple, easy-to-follow steps:

  1. setup Vite + React + TypeScript
  2. level it up with eslint, eslint-airbnb-config, and
  3. get pretty with prettier
  4. add dart-sass + CSS Module
  5. testing with Vitest
  6. integrate react-router-dom

Step 1

Setup Vite + React + TypeScript.

  • 🔥 vite hot reloading and page refreshing in blessing fast.
  • 👀 react one library covers two worlds— web and native.
  • 👮 typescript catch types errors early in editor.

It is so easy to install React + Typescript using Vite. Open terminal and type:

yarn create vite

Now go to project dir and run:

yarn install

Step 2

yarn add eslint -D

eslint-plugin-react@latest 
@typescript-eslint/eslint-plugin@latest 
@typescript-eslint/parser@latest
npx eslint --init

I already have eslint in my project, so just need to level it up with eslint-airbnb-config.

Let’s install eslint-airbnb-config:

npx install-peerdeps --dev eslint-config-airbnb

eslint-config-airbnb@19.0.4 
eslint@^8.2.0 
eslint-plugin-import@^2.25.3 
eslint-plugin-jsx-a11y@^6.5.1 
eslint-plugin-react@^7.28.0 
eslint-plugin-react-hooks@^4.3.0 --dev

After it installed, it’s time to update hooks inside .eslintrc config file:

module.exports = {
  root: true,
  env: { browser: true, es2020: true },
  extends: [
  - 'eslint:recommended',
  + 'airbnb',
  + 'airbnb/hooks',
    'plugin:@typescript-eslint/recommended',
    'plugin:react-hooks/recommended',
  ],
  ignorePatterns: ['dist', '.eslintrc.cjs'],
  parser: '@typescript-eslint/parser',
  plugins: ['react-refresh'],
  rules: {
    'react-refresh/only-export-components': [
      'warn',
      { allowConstantExport: true },
    ],
  },
}

Now I need these rules also support TypeScript. For that I need to install eslint-config-airbnb-typescript:

yarn add eslint-config-airbnb-typescript -D

...and update hook:

extends: [
  'airbnb',
+ 'airbnb-typescript',
  'airbnb/hooks',
  ...
]

Now I have rules for my .tsx files as well. Now, I need to tell ESLint where to find my TypeScript config.

Set parserOptions.project to the path of tsconfig.json.

+ parserOptions: {
+   project: './tsconfig.json'
+ },

Now, I can add my custom rules also, like:

rules: {
  'react-refresh/only-export-components': [
    'warn',
    { allowConstantExport: true },
  ],
+ 'react/react-in-jsx-scope': 0,
},

Lastly, update tsconfig.json:

"include": [
  "src",
+ "vite.config.ts",
+ ".eslintrc.cjs",
]

the developer window. Done. From now airbnb’s default rules are applied all over my code.

Step 3

So at this point, ESLint is configured, which handles code style and things like missing semicolons or using single quotes vs double quotes.

For handling code formatting, I’m going to install prettier:

yarn add -D prettier eslint-config-prettier eslint-plugin-prettier

Configuration File I’m going to create .prettierrc.cjs (as like .eslintrc.cjs) file that exports an object using module.exports.

const config = {
  trailingComma: "es5",
  tabWidth: 2,
  semi: false,
  singleQuote: true,
};

module.exports = config;

Integrating with Linters After reading the doc, I found that eslint-plugin-prettier is generally not recommended, but it can be useful in certain circumstances, which aligns with my needs.

Open .eslintrc config file and add prettier to the plugins section:

plugins: [
  'react-refresh',
+ 'prettier',
],

Also, add plugin:prettier/recommended as the last item in the extends array in .eslintrc config file. This allows eslint-config-prettier to override other configs.

extends: [
  'airbnb',
  'airbnb-typescript',
  'airbnb/hooks',
  'plugin:@typescript-eslint/recommended',
  'plugin:react-hooks/recommended',
+ 'plugin:prettier/recommended',
]

Step 4

I love CSS, and Dart Sass is my go-to for every project. Vite or React does not provide built-in support for .scss, .sass, .less, .styl or stylus files.

To use sass, first I have to install it:

yarn add sass -D

Now I just need to use CSS modules combined with sass by prepending .module to the file extension, for example Header.module.sass.

Just need a few adjustments on render classNames. I prefer localsConvention: 'camelCase' and shorter generateScopedName.

To do that, open vite.config.ts file and add a css section as follows:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
+ css: {
+   modules: {
+     localsConvention: 'camelCase',
+     generateScopedName: '[local]__[hash:base64:4]',
+   },
+ },
})

Now there’s no need to worry about class names mismatching.

If you're curious why I use CSS Modules, check out this article by Glen Madden. It has the answer you need.

Step 5

If I have no plans to write any unit tests in my project, I skip this step.

Write unit testing for every component is definitely a best practice. So I do my best to write some, even though I’m not good at it. But integrating a testing library is definitely a good add-ons.

Let’s setup vitest:

yarn add vitest -D
  • 🧪 vitest super fast testing framework.

There is an example list for different setups provided by Vitest. The one I am going to be utilizing here is react-testing-lib this. You can also check out React Testing Library.

To get started, I am going to install:

yarn add -D jsdom @testing-library/react @testing-library/jest-dom

Now from the example, open their config file and update my vite.config.ts accordingly:

+ /* eslint-disable import/no-extraneous-dependencies */
+ /// <reference types="vitest" />
+ /// <reference types="vite/client" />

import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
+ test: {
+  globals: true,
+  environment: 'jsdom',
+  setupFiles: ['./src/setupTests.ts'],
+  css: true,
+ },
})

At this point, along with setting globals: true, they are instructed that we need to go into tsconfig.json file and update compilerOptions:

{
  "compilerOptions": {
    ...
    "jsx": "react-jsx",
  + "types": ["vitest/globals"],
    ...
  },
  ...
}

Now it’s ready to write any unit testing.

Step 6

This is the last step.

I ask myself, does my website or application have multiple pages or routes?

If yes, I use react-route-dom.

Once my website loads, it’s allowing me to navigate between pages without any browser refreshing. It's like magic, delivering a smooth user experience while navigation.

Install it as a dependency:

yarn add react-router-dom

After that, I followed the tutorial, spending an hour to set up everything I needed. I have to do just two things.

1st, wrap AppLayout component with BrowserRouter in my App.tsx file:

import { BrowserRouter } from 'react-router-dom'
import AppLayout from './components/app-layout/AppLayout'

function App() {
  return (
    <BrowserRouter>
      <AppLayout />
    </BrowserRouter>
  )
}

export default App

2nd, set up NavLink and Routes in AppLayout component:

import { NavLink, Route, Routes } from 'react-router-dom'
import Home from '../../pages/home/Home'
import Contact from '../../pages/contact/Contact'

function AppLayout() {
  return (
    <>
      <nav>
        <NavLink to="/">Home</NavLink>
        <NavLink to="/contact">Contact</NavLink>
      </nav>
      <Routes>
        <Route index element={<Home />} />
        <Route path="contact" element={<Contact />} />
      </Routes>
    </>
  )
}

export default AppLayout

All done! This setup will enable navigation between my pages without browser refreshing.