Create-react-app is a great tool, but don’t cover every subject witch good project need. I want to show is in simple steps how to add some linting, component tests and git hooks integration witch can be very helpful. That will give as a good base for other topics like optimization or TDD in the future.

Start witch typescript

Let’s start. First of all – to be able of using create-react-app you need to have npm 5.2+ or yarn. How to check your version? Type in terminal:

npm -v

As you can check in react docs you can start your project by typing

npx create-react-app my-app --typescript
or
yarn create react-app my-app --typescript

Check if we have working project (I will show that with npm but you can do it with yarn too)

cd my-app
npm start

Now we should be able to check working example on localhost. Next step – clear package.json. In default we have all installed packages in dependencies. Witch is incorrect. Live there only react and react-dom. The rest move to devDependencies.

{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.10.1",
    "react-dom": "^16.10.1"
  },
  "devDependencies": {
    "@types/jest": "24.0.18",
    "@types/node": "12.7.8",
    "@types/react": "16.9.3",
    "@types/react-dom": "16.9.1",
    "react-scripts": "3.1.2",
    "typescript": "3.6.3"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

Add linter

Install tslint to your project:

npm install tslint tslint-react --save-dev

Add bit of configuration in file tslint.json, simple example:

{
  "extends": ["tslint:latest", "tslint-react"],
  "rules": {
    "interface-over-type-literal": false,
    "jsx-wrap-multiline": false,
    "quotemark": [true, "single", "jsx-double"],
    "semicolon": [true, "never"]
  }
}

Add a command to your package.json

...
"scripts": {
  "start": "react-scripts start",
  "build": "react-scripts build",
  "test": "react-scripts test",
  "eject": "react-scripts eject",
  "lint": "tslint -c tslint.json 'src/**/*.{ts,tsx}'"
},
...

If you use VSCode there is a very good plugin to lint your code in the editor: TSLint from Microsoft. If you can’t see any results after installing linter plugin or adding linter to your project restart VSCode.

Let’s lint our project:

npm run lint

After running this command you will see a lot of linter errors. It’s time to fix those! You can ask why there is a rule for no semicolon – personally I think that using them in code is simply waist of time and makes code more hard to read and understand. This is anyway transpile to js.

Ok, let’s remove errors. For now we don’t need service worker (if you wish to live it – just remove linter errors from the file other way:)

rm src/serviceWorker.ts

src/index.tsx:

import React from 'react'
import ReactDOM from 'react-dom'

import './index.css'

import App from './App'

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

src/App.tsx:

import React from 'react'
import logo from './logo.svg'

import './App.css'

const App: React.FC = () => {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  )
}

export default App

src/App.test.tsx:

import React from 'react'
import ReactDOM from 'react-dom'

import App from './App'

it('renders without crashing', () => {
  const div = document.createElement('div')
  ReactDOM.render(<App />, div)
  ReactDOM.unmountComponentAtNode(div)
})

And we should be good with lint at this point.

Tests

On default we have already jest installed and test if our up renders. But what about components? We will use for that purpose react-test-renderer. Additionally jest-ts.

npm install ts-jest react-test-renderer @types/react-test-renderer --save-dev

For component test we need to create one – src/components/text.tsx

import React, { useState } from 'react'

const text = 'TEST COMPONENT'

export const Text: React.SFC<{}> = () => {

  const [showText, setShowText] = useState(true)

  const toggleText = () => setShowText(!showText)

  return (
    <div>
      <button onClick={toggleText}>Toggle text</button>
      {showText && text}
    </div>
  )
}

We can add index.ts for easier imports (src/components/index.ts)

export * from './text'

For visual check lets add it to src/App.tsx

import React from 'react'
import logo from './logo.svg'

import { Text } from './components'

import './App.css'

const App: React.FC = () => {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
        <Text />
      </header>
    </div>
  )
}

export default App

Ok, we have visual test but what about testing component by any CI or script? We have to create it! Start with src/components/text.test.tsx

import React from 'react'
// tslint:disable-next-line: no-implicit-dependencies
import { act, create } from 'react-test-renderer'

import { Text } from './text'

describe('Text component tests', () => {
  test('it render', () => {
    const counter = create(<Text />)
    const instance = counter.root
    const div = instance.findByType('div')
    expect(div.children[1]).toBe('TEST COMPONENT')
  })
  test('it increment by value', async () => {
    const counter = create(<Text />)
    const instance = counter.root
    const button = instance.findByType('button')
    await act(async () => {
      button.props.onClick()
    })
    const div = instance.findByType('div')
    expect(div.children[1]).toBe(undefined)
  })
})

Above we have to test – first if component renders and second to check how behave on simulation use. This is a very simple example how to write test for components with side effects as user click.

Git hooks – husky

When you in a team or just for yourself before you commit any changes to your repo you want to be sure that your code passes lint rules and all test are ok. But who want’s or remember all the time to run all of that before each commit? We can built automated tool witch will do it for us.

npm install husky --save-dev

And add it to package.json:

...
"husky": {
  "hooks": {
    "pre-commit": "npm run lint && npm test"
  }
},
..

That line can be read as – start with linter, if there is no errors run tests, if all will pass do the commit. When there will be any error in lint or in tests you will need to fix it, the script will not run the commit at all. Git repo is already initialized by react-crate-app. Try the commit now πŸ™‚

Important! In package.json you need to change the line

"test": "react-scripts test"
to
"test": "react-scripts test --watchAll=false"

Github repository here.

Interested how to create npm react component package?

If this article was helpful for you – buy me a 🍺🍺🍺


0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *