npx create-react-app my-app --template pwa-typescript

Cleanup

Delete App.css and logo.svg.

function App() {
return <div>Hello World</div>
}
export default App

Tailwind

Tailwind documentation has a good page showing how to set up tailwind in create-react-app.

  • Headwind by Ryan Heybourn
oooh, Intelliscense!
// Run the development servernpm start

Storybook

One can’t go back to traditional ways after using a fully-featured component library like storybook.

npx sb init
export const LoggedOut = Template.bind({});
LoggedOut.args = {
...HeaderStories.LoggedOut.args,
};
npx rsb-gen atoms/Sample
const Sample = () => {
return <div className='p-4 bg-red-600 text-white'>Hello World!</div>
}
export default Sample
npm run storybook
Storybook needs to be configured for tailwind.
import ‘../src/index.css’

Storybook addons

Visit the addons page and install the required addons as development dependencies. I’m going to install the below addons.

npm i -D @storybook/addon-a11y storybook-addon-designs
...
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/preset-create-react-app',
'storybook-addon-designs',
'@storybook/addon-a11y',
],
...
  • Accessibility is a very useful tool that alerts us about the accessibility issues on the page.

Theming Storybook

The storybook layout above looks very familiar. Let's theme is to our taste. We can theme the storybook according to the actual theme of our application. We don’t have one.

.storybook/main.js
.storybook/manager.js
docs: {
theme: yourTheme,
}

Format, Lint, and Type Check!

Waiting for GitHub Co-pilot? Before that, we have to make sure we use these co-pilots (or pair programmers) to their best.

Prettier

Install the below VSCode extension.

...
"prettier": "prettier --ignore-path .gitignore \"**/*.{js,jsx,ts,tsx,json}\"",
"format:check": "npm run prettier -- --check",
"format:fix": "npm run prettier -- --write",
...

ESLint

Create react app comes configured with eslint. But we need to set up eslint in our project as if we can add and remove additional rules to make the lining stricter to catch more issues!

npx eslint --init
"lint": "eslint --ext .ts --ext .tsx ."
npm i -D eslint-config-prettier
...
extends: ['plugin:react/recommended', 'standard', 'prettier'],
...
env: {
browser: true,
es2021: true,
jest: true,
}
...
rules: {
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
'no-unused-vars': 'warn',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'error',
},
...
  • react/prop-types: We rely on our types heavily and we don’t use prop-types. Hence turned off.
  • no-unused-vars: This is a helpful rule but developing with no-unused-vars set to error is very hard. Hence I downgraded it to warn.
  • no-use-before-define: I turned off the JS version of it and turned on the typescript version. I found this rule complaining about the key in the typescript index signature.
settings: {
react: {
version: 'latest',
}
}

Type Checking

We initialized our project having typescript in the template. We already have tsconfig.json file in our root. I’m going to add some compilerOptions.

// compilerOptions in tsConfig.json
...
"baseUrl": "."
"noImplicitAny": true,
...
  • We set noImplicityAny to true. Because any is bad!
"type:check": "tsc"
const a = 9
a = 99

Validate in parallel!

We have our formating, linting, and type checking scripts ready. Wouldn’t be great if we can run them all in parallel with one command?

npm i -D npm-run-all
"validate": "npm-run-all --parallel type:check format:check lint"

Note:

To set the baseURL for the storybook’s webpack configuration, Add the below inside webpackFinal (.storybook/main.js) before returning the config object.

config.resolve.modules = [...(config.resolve.modules || []),path.resolve(__dirname, '../')]

Defend Early

Ok, now we have our validate script. When should we run it? We can run it in our CI/CD pipeline. But I would like to have the script run on every commit.

The formating, linting, and type issues will never leave the developer’s machine!

I’m going to use lint-staged. Run the below command that uses mrm@2.

npx mrm@2 lint-staged
  • Creates pre-commit git hook.
  • Creates lint-staged configuration in package.json.
module.exports = {
'*.{ts,tsx}': (filenames) => [
'npm run format:fix',
'npm run validate'
],
}
  • We run format:fix to automatically fix the staged files to our prettier config (So format:check inside validate never fails.)
  • lint staged runs git add . in the end, so we don’t have to do it ourselves. (Remember? format:fix will update files.)
const a = 9
a = 99

Commit Lint

How to encourage developers to give meaningful commit messages? Let's do it.

# Install commitlint cli and conventional config
npm install --save-dev @commitlint/{config-conventional,cli}
# Configure commitlint to use conventional config
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
# Add hook
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'
  • type may not be empty.
echo "{ \"path\": \"cz-conventional-changelog\" }" > .czrc
  • npx git-cz
  • Or you can also install commitizen globally npm install commitizen -g and run cz, git-cz or git cz.

Redux

React itself is very functional. Look at the below example.

const [count, setCount] = useState<number>(0)
  • setCount: Set the value by returning a new object/value.
  • As discussed,useState, useReducer hooks have immutable states.
  • Side effects: Side effects are neatly packed only within useEffect hook.
npm i @reduxjs/toolkit react-redux
// Add these imports at the top.
import { Provider } from 'react-redux'
import { store } from './store'
// And wrap the <App />.
<Provider store={store}>
<App />
</Provider>

Github actions

We use Github actions to defend our remote repository.

Workflow

Let's create the workflow file in .github/workflows/node.js.yml with the below content.

Protect the main branch

Settings -> Branches -> Branch protection rules -> Click Add rule
protection rules for the main branch.
git checkout -b developgit push --set-upstream origin develop

Tangent: Custom render function

My workflow failed with the below error.

FAIL src/App.test.tsxcould not find react-redux context value; please ensure the component is wrapped in a <Provider>
import { render } from '@testing-library/react'
import App from './App'
test('renders', () => {
render(<App />)
})
import { renderWithProviders } from 'src/utils/testUtils'
import App from './App'
test('renders', () => {
renderWithProviders(<App />)
})

Warnings as errors

Now our npm run build step failed with the below error.

Treating warnings as errors because process.env.CI = true.
Most CI servers set it automatically.
Failed to compile. src/App.tsxLine 18:10: 'text' is assigned a value but never used no-unused-vars
Line 18:16: 'textSetter' is assigned a value but never used no-unused-vars

Collaborator

Add a collaborator to the project.

Settings -> Manage Access -> Add People

npm run analyze

"preanalyze": "npm run build","analyze": "npx source-map-explorer 'build/static/js/*.js'",

Dependency Cruise

Initiate depcruise

npx depcruise --init
"depcheck": "npx depcruise --config .dependency-cruiser.js src","depcheck:graph": "npx depcruise --include-only '^src' --output-type dot src | dot -T svg > dependencygraph.svg",
complete dependency graph of our project

Cloc

"cloc": "npx cloc src"

Chromatic

How to keep track of all visual changes that happen on each code change? It could be purely visual with some messedup CSS or it could be functional too.

  • Add project.
  • Choose from github or create a project.
  • npm install -D chromatic
  • npx chromatic — project-token='id’ Replace id.
  • Then chromatic will ask you to create some UI change and run the above command again.
// from
<div className='p-4 border'>Some component!!</div>
// to
<div className='p-5 border border-red-900'>Some component!!</div>

Chromatic in CI/CD

We want this automated UI checking to happen on each code push. Lets implement that in our ci/ci pipeline.

- name: Publish to Chromatic
uses: chromaui/action@v1
# Chromatic GitHub Action options
with:
token: ${{ secrets.GITHUB_TOKEN }}
# 👇 Chromatic projectToken, refer to the manage page to obtain it.
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN e7d1b1895737 }}
- uses: actions/checkout@v2
with:
fetch-depth: 0 # 👈 Required to retrieve git history

Firebase

Login to firebase and create a new project.

  • Firestore
  • Storage
  • Hosting

Auth

Enable email password authentication in the console (Authentication -> Signin method)

Hosting

# Install firebase globally
npm i -g firebase
# Install firebase in the project
npm i firebase
# Login
firebase login
# Initializing hosting
firebase init hosting
# Answer a bunch of questions and voila. Firebase hosting will be setup for you including the ci/cd workflows.

Sentry

Go to sentry.io and create a new project.

  • Set alert settings
# Have these states.
const obj = { msg: 'Objects are not valid as a React child' }
const [text, setText] = useState<string>(JSON.stringify(obj))
# Then render text in jsx....
{text}
{/** @ts-ignore */}
<button onClick={() => setText(obj)}>Break the world</button>
...
Application crash reported in sentry dashboard

Sentry error boundary

Ok, sentry is tracking our application crashes, but lets make the application crash gracefully using error boundary.

crash gracefully with sentry error boundary.

Cypress

We will use react-testing-library for unit and integration testing. Cypress is great for testing the critical user flows interacting with our backend exactly like our user.

# Install dependenciesnpm i -D cypress @testing-library/cypress eslint-plugin-cypress
  • @testing-library/cypress: The documentation says ‘The more your tests resemble the way your software is used, the more confidence they can give you.’
  • eslint-plugin-cypress: To satisfy eslint.

Generate sample tests

# Simply run this in the terminal.
./node_modules/.bin/cypress open
# or have a script in package.json
"cypress:open" : "cypress open"
# and run this
npm run cypress:open

Update eslint

  • Add ‘plugin:cypress/recommended’, to extends array in eslintrc.js.
  • Add ‘cypress/no-unnecessary-waiting’: ‘warn’ in rules.
  • To fix a unused-expression problem by chai, add the below in the overrides.
overrides: [{
files: ['*.test.js', '*.spec.js'],
rules: {
'no-unused-expressions': 'off',
},
}]

Update tsconfig

  • Add “types”: [“cypress”, “@testing-library/cypress”] in compilerOptions.

Configure testing library

Cypress Testing Library extends Cypress's cy commands. Add the below line to your project’s cypress/support/commands.js.

import '@testing-library/cypress/add-commands';

Why testing library?

I absolutely love how testing library wants us to mimic actual user while interacting with the ui.

Thank you.

--

--

Fullstack engineer @ atem.green | React, Typescript, Redux, JavaScript, UI, Storybook, CSS, UX, Cypress, CI/CD.

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Karthick Ragavendran

Karthick Ragavendran

Fullstack engineer @ atem.green | React, Typescript, Redux, JavaScript, UI, Storybook, CSS, UX, Cypress, CI/CD.