Skip to content

Instantly share code, notes, and snippets.

@Naedri
Last active December 5, 2023 06:17
Show Gist options
  • Save Naedri/a5e66eba90824a803961726ab45a2a01 to your computer and use it in GitHub Desktop.
Save Naedri/a5e66eba90824a803961726ab45a2a01 to your computer and use it in GitHub Desktop.
Coding rules such as : linting, formatting, versionning and code development.

Coding rules

Coding rules such as : linting, formatting, versionning and code development.

Client support

  • Hardware : Which is the smallest screen do we plan to support ?
    • Samsung galaxy s20/ultra ?
  • Software : Which versions do we plan to support ?
    • Android
    • Chrome

Testing

Code reviewing

Code quality analysis automatic tools

Supplementary materials

Please, have a look on the children pages from this section.

What are the tools ?

Protocols

Common action to do for below protocoles

ignoring files

Add the following config files at the root level your repository :

  • .prettierignore :
# Ignore artifacts:
build
coverage

# Ignore all HTML files:
# *.html
  • .eslintignore :
*.css
*.svg

Protocol for an ionic repository

install dependencies

Follow the rules of :

defining shortcuts and config

Add the following shortcuts to your package.json :

"eslintConfig": {
    "extends": "@ionic/eslint-config/recommended"
},
"prettier": "@ionic/prettier-config",
"private": true,
"scripts": {
    ...
    "eslint": "eslint . --ext ts,tsx,js,jsx",
    "eslint:fix": "eslint . --fix --ext ts,tsx,js,jsx",
    "prettier": "prettier --check \"./src/**/*.{ts,tsx,js,jsx,json,md}\"",
    "prettier:fix": "prettier --write \"./src/**/*.{ts,tsx,js,jsx,json,md}\"",
    "lint": "npm run eslint && npm run prettier",
    "format": "npm run eslint:fix && npm run prettier:fix"
}
  • lint: will search for problems, but will not fix.
  • format: will search for problems, and will fix.

Protocol for a new repository

Install dependencies

Install the following dev dependencies in your package.json with npm install --save-dev :

  "devDependencies": {
    ...
    "@typescript-eslint/eslint-plugin": "^5.27.1",
    "@typescript-eslint/parser": "^5.27.1",
    "eslint": "^7.32.0",
    "eslint-config-prettier": "^8.5.0",
    "eslint-import-resolver-typescript": "^2.7.1",
    "eslint-plugin-import": "^2.26.0",
    "eslint-plugin-prettier": "^4.0.0",
    "eslint-plugin-react": "^7.30.0",
    "prettier": "^2.6.2"
  }

Set up config

Add the following config files at the root level your repository :

  • .prettierrc.json
{
    "arrowParens": "avoid",
    "bracketSameLine": true,
    "bracketSpacing": true,
    "endOfLine": "lf",
    "printWidth": 80,
    "proseWrap": "always",
    "semi": true,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "es5",
    "useTabs": false
}
  • .eslintrc.json
{
  "env": {
    "browser": true,
    "es2021": true,
    "jest": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/recommended-requiring-type-checking",
    "prettier"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "ecmaVersion": 12,
    "project": [
      "./tsconfig.json"
    ],
    "sourceType": "module"
  },
  "plugins": [
    "react",
    "react-hooks",
    "@typescript-eslint",
    "prettier"
  ],
  "rules": {
    "react/react-in-jsx-scope": "off",
    "camelcase": "error",
    "spaced-comment": "error",
    "quotes": [
      "error",
      "single"
    ],
    "no-duplicate-imports": "error"
  },
  "settings": {
    "import/resolver": {
      "typescript": {}
    }
  }
}

defining shortcuts

Add the following shortcuts to your package.json :

  "scripts": {
    ...
    "format:config": "npm run format --config ./.prettierrc",
  }

Interoperability with other tools

  • You may install the corresponding x-config-prettier package : Stylelint, ESlint, TSlint.
  • Deprecated practices : avoid use prettier-eslint and prettier-cli.

References

Naming commit message convention

It is from Conventional Commits or Commit Message Format from Angular :

<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

Description

Type

It must be one of the following:

  • build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
  • ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
  • docs: Documentation only changes
  • feat: A new feature
  • fix: A bug fix
  • perf: A code change that improves performance
  • refactor: A code change that neither fixes a bug nor adds a feature
  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
  • test: Adding missing tests or correcting existing tests

Scope

It could be the one of the following is the list of supported scopes:

  • animations
  • common
  • compiler
  • core
  • elements
  • forms
  • http
  • language-service
  • platform-browser
  • platform-server
  • router

Subject

  • Here the Jira ID goes
  • use the imperative, present tense: "change" not "changed" nor "changes"
  • don't capitalize the first letter
  • no dot (.) at the end

Example

fix(animations): color prod unit 

try catch wrapping 

TEL-99 

Git hooks

Examples

In the package.json file you can writte :

  "husky": {
    "hooks": {
      "pre-commit": "npm run lint"
    }
  }

Good pratices

Favor squash merging under pull request ?

CSS

These coding rules are taken from the Airbnb React/JSX Style Guide.

Use CSS modules

With CSS Modules, it’s a guarantee that all the styles for a single component:

  1. Live in one place
  2. Only apply to that component and nothing else

Plus, any component can have a true dependency, like:

import buttons from "./buttons.css";
import padding from "./padding.css";

element.innerHTML = `<div class="${buttons.red} ${padding.large}">`;

This approach is designed to fix the problem of the global scope in CSS (ref).

Factoring

Have a look at the following page(s) :

Naming

  • Extensions: Use .tsx extension for React components.

  • Filename: Use PascalCase for filenames. E.g., ReservationCard.jsx.

  • Reference Naming: Use PascalCase for React components and camelCase for their instances. eslint: react/jsx-pascal-case

// πŸ’©
import reservationCard from './ReservationCard';

// πŸ‘
import ReservationCard from './ReservationCard';

// πŸ’©
const ReservationItem = <ReservationCard />;

// πŸ‘
const reservationItem = <ReservationCard />;
  • Component Naming: Use the filename as the component name. For example, ReservationCard.jsx should have a reference name of ReservationCard. However, for root components of a directory, use index.jsx as the filename and use the directory name as the component name:
// πŸ’©
import Footer from './Footer/Footer';

// πŸ’©
import Footer from './Footer/index';

// πŸ‘
import Footer from './Footer';
  • Higher-order Component Naming: Use a composite of the higher-order component’s name and the passed-in component’s name as the displayName on the generated component. For example, the higher-order component withFoo(), when passed a component Bar should produce a component with a displayName of withFoo(Bar).

    Why? A component’s displayName may be used by developer tools or in error messages, and having a value that clearly expresses this relationship helps people understand what is happening.

// πŸ’©
export default function withFoo(WrappedComponent) {
  return function WithFoo(props) {
    return <WrappedComponent {...props} foo />;
  }
}

// πŸ‘
export default function withFoo(WrappedComponent) {
  function WithFoo(props) {
    return <WrappedComponent {...props} foo />;
  }

  const wrappedComponentName = WrappedComponent.displayName
    || WrappedComponent.name
    || 'Component';

  WithFoo.displayName = `withFoo(${wrappedComponentName})`;
  return WithFoo;
}

Logging

Client-Side and Server-Side should logs the message through a function for future adaptability.

National Language Support (NLS)

Every text displayed to the used should be gathered into one file, in order to ease the traduction that we could need in the future.

React

Basic Rules

  • Only include one React component per file.
  • Always use TSX syntax.
  • Do not use React.createElement unless you’re initializing the app from a file that is not TSX.
  • react/forbid-prop-types will allow arrays and objects only if it is explicitly noted what array and object contains, using arrayOf, objectOf, or shape.

Class vs React.createClass vs stateless

  • If you have internal state and/or refs, prefer class extends React.Component over React.createClass. eslint: react/prefer-es6-class react/prefer-stateless-function
// πŸ’©
const Listing = React.createClass({
  // ...
  render() {
    return <div>{this.state.hello}</div>;
  }
});

// πŸ‘
class Listing extends React.Component {
  // ...
  render() {
    return <div>{this.state.hello}</div>;
  }
}

And if you don’t have state or refs, prefer normal functions (not arrow functions) over classes:

// πŸ’©
class Listing extends React.Component {
  render() {
    return <div>{this.props.hello}</div>;
  }
}

// πŸ’© (relying on function name inference is discouraged)
const Listing = ({ hello }) => (
  <div>{hello}</div>
);

// πŸ‘
function Listing({ hello }) {
  return <div>{hello}</div>;
}

Mixins

Why? Mixins introduce implicit dependencies, cause name clashes, and cause snowballing complexity. Most use cases for mixins can be accomplished in better ways via components, higher-order components, or utility modules.

Share Data with Props

To pass data into a component with props, we will try to use as much as possible the object destructuring synthax.

// πŸ’©
function MyComponent({ name }) {
  return <p>πŸ”₯ {name}</p>;
}

// πŸ‘
function MyComponent(props) {
  return <p>πŸ”₯ {props.name}</p>;
}

// Both can be used by the same way
<MyComponent name="Jeff" />

01:00 Big Components

πŸ’© A dirty React component : ListComponent

import React from 'react'
import './ListComponent.css'

class ListComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      lastClickedButton: '',
    }
  }

  render() {
    return (
      <div>
        <h1>The last clicked button is {this.state.lastClickedButton}</h1>
        <ul>
          <li>
            <button
              onClick={() => {
                this.setState({lastClickedButton: 'Create'})
                this.props.createSomething()
              }}
              className="my-button">
              Create
            </button>
          </li>

          <li>
            <button
              onClick={() => {
                this.setState({lastClickedButton: 'Read'})
                this.props.readSomething()
              }}
              className="my-button">
              Read
            </button>
          </li>

          <li>
            <button
              onClick={() => {
                this.setState({lastClickedButton: 'Update'})
                this.props.updateSomething()
              }}
              className="my-button">
              Update
            </button>
          </li>

          <li>
            <button
              onClick={() => {
                this.setState({lastClickedButton: 'Destroy'})
                this.props.destroySomething()
              }}
              className="my-button">
              Destroy
            </button>
          </li>
        </ul>
      </div>
    )
  }
}

πŸ‘ Prefer functional components with React Hooks

πŸ‘ DRY

πŸ‘ Proper naming & props destructuring

πŸ‘ If you use JS add a PropTypes validation layer

πŸ‘ Split into small pieces

import React from 'react'
import PropTypes from 'prop-types'

const ListItem = ({action, title, setClicked}) => {
  return (
    <li>
      <button
        onClick={() => {
          setClicked(title)
          action()
        }}
        className="my-button">
        {title}
      </button>
    </li>
  )
}

ListItem.propTypes = {
  action: PropTypes.func,
  setClicked: PropTypes.func,
  title: PropTypes.string,
}

export default ListItem
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import ListItem from './ListItem.jsx';

const List = ({ create, read, update, destroy }) => { 
  const [clicked, setClicked] = useState('');

  return (
    <h1>The last clicked button is {clicked}</h1>
    <div>
      <ul>
        <ListItem title="Create" action={create} setClicked={setClicked} /> 
        <ListItem title="Read" action={read} setClicked={setClicked} /> 
        <ListItem title="Update" action={update} setClicked={setClicked} /> 
        <ListItem title="Destroy" action={destroy} setClicked={setClicked} />
      </ul>
    </div>
  )
};

List.propTypes = {
  create: PropTypes.func, read: PropTypes.func,
  update: PropTypes.func, destroy: PropTypes.func,
};

 export default List;

Reference : Refactoring a Complex React Component

01:55 Nesting Gotcha

πŸ’© Child using the function defined by the parent => bad performance issue

function Parent(){
  const[count, setCount] = useState(0);
  const handleClick = () => setCount(count+1);
  const Child = () => {
    return <button onClick={handleClick}>+</button>
  }
}

return (
  <div>
    <Child />
  <div>
)

πŸ‘ pass the function in as a prop

const Child = ({onClick}) => {
  return <button onClick={onClick}>+</button>
}

function Parent(){
  const[count, setCount] = useState(0);
  const handleClick = () => setCount(count+1);
}

return (
  <div>
    <Child onClick = {handleClick}/>
  <div>
)

02:35 Failure to memoΓ―se

If your component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.

React.memo only checks for prop changes.

function MyComponent(props) {
  /* render using props */
}
function areEqual(prevProps, nextProps) {
  /*
  return true if passing nextProps to render would return
  the same result as passing prevProps to render,
  otherwise return false
  */
}
export default React.memo(MyComponent, areEqual);

This method only exists as a performance optimization. Do not rely on it to β€œprevent” a render, as this can lead to bugs.

03:15 Useless Divs

πŸ’© As JSX need to return only one element, I wrap the two elements into a div

function Frag(){
  return (
  <div>
    <nav></nav>
    <article></article>
  </div>
  )
}

πŸ‘ As JSX needs to return only one element, I wrap the two elements into a </> or a <React.Fragment>

function Frag(){
  return (
  <>
    <nav></nav>
    <article></article>
  </>
  )
}

03:44 Messy Files

React components should be organised by folders, in which one folder contains one React component.

These folders should include at least a structure similar to the following one (here example with Navbar) :

Navbar
└── index.tsx
β”œβ”€β”€ Navbar.module.css
β”œβ”€β”€ Navbar.spec.ts
└── Navbar.tsx  

Theindex.tsx helps to import our component like this :

  • import Navbar from './Navbar'; πŸ‘
  • not like that : import Navbar from './Navbar/Navbar'; πŸ’©

Theindex.tsx includes the following :

export { default } from './Navbar';

To help, it has been implemented a folder template that can be used with the vscode folder template extension.

04:40 Big Bundles

If a slow initial page load occurs, it may means the app is too big (importing too much stuff), to tackle this trouble we would try to load some module asynchronously.

πŸ‘ Using lazy loading and Suspense:

const Button = React.lazy(() => import('./Button'));

function Page(){
  return (
  <Suspens fallback={<div>Loading...</div>}>
    <Button />
  </Suspens>
  );
}

05:34 Prop Drilling

Which state management libraries to use ? Hooks, Redux, and Recoil ?

At first, React hooks will be used as the primary state management tool state, especially with useState, useEffect, and useReducer. We can use useReduceras follow (reference) :

function reducer(state, action) {
  switch(action.type) {
    case 'increment' : 
      return {count: state + 1};
    case 'decrement' :
      return {count: state - 1};
    default:
      throw new Error();
  }
}

function App() {
  const[state, dispatch] = useReducer(reducer, 0);
  return(
    <>
      Count: {state}
      <button onClick={()=> dispatch({type: 'decrement'})}>-</button>
      <button onClick={()=> dispatch({type: 'increment'})}>+</button>
    </>
  );
}

However if we had to choose one, we may think about Recoil, for the following reason :

  • It was built and released by engineers from Facebook's team, the React creator.
  • It doesn't impose a strict learning curve as Redux and Mobx do.
  • it doesn't intend to have so much Redux boilerplate in the codebase.
How to pass data among react component siblings ?
  • Redux β†’ no
  • ContextAPI (useContext hook) β†’ parsimoniously, cause it makes you contained component impossible to reuse without having it with the same context (given by the same outside parent component).
  • Combination of callback and use of props β†’ yes

06:30 Prop Plowing

Try to use the spread syntax when you can.

Here an example for spread props :

const data = {
  id: 7
  name: "John",
  age: 29
}

return (
  // <User id={data.id} name={data.name} age={data.age} /> // πŸ’©
  <User {...data} /> // πŸ‘
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment