Coding rules such as : linting, formatting, versionning and code development.
-
-
Save Naedri/a5e66eba90824a803961726ab45a2a01 to your computer and use it in GitHub Desktop.
- 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
- What are the tools ?
- What are the tools ?
- What are the tools ?
Please, have a look on the children pages from this section.
Add the following config files at the root level your repository :
- .prettierignore :
# Ignore artifacts:
build
coverage
# Ignore all HTML files:
# *.html
- .eslintignore :
*.css
*.svg
Follow the rules of :
- GitHub - ionic-team/prettier-config: Shared Prettier config π
- GitHub - ionic-team/eslint-config: Common eslint rules/preferences for Ionic.
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.
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"
}
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": {}
}
}
}
Add the following shortcuts to your package.json :
"scripts": {
...
"format:config": "npm run format --config ./.prettierrc",
}
It is from Conventional Commits or Commit Message Format from Angular :
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
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
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
- 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
fix(animations): color prod unit
try catch wrapping
TEL-99
In the package.json
file you can writte :
"husky": {
"hooks": {
"pre-commit": "npm run lint"
}
}
Favor squash merging under pull request ?
These coding rules are taken from the Airbnb React/JSX Style Guide.
With CSS Modules, itβs a guarantee that all the styles for a single component:
- Live in one place
- 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).
Have a look at the following page(s) :
-
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 ofReservationCard
. However, for root components of a directory, useindex.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 componentwithFoo()
, when passed a componentBar
should produce a component with adisplayName
ofwithFoo(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;
}
Client-Side and Server-Side should logs the message through a function for future adaptability.
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.
- Only include one React component per file.
- However, multiple Stateless, or Pure, Components are allowed per file. eslint:
react/no-multi-comp
.
- However, multiple Stateless, or Pure, Components are allowed per file. eslint:
- 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 allowarrays
andobjects
only if it is explicitly noted whatarray
andobject
contains, usingarrayOf
,objectOf
, orshape
.
- If you have internal state and/or refs, prefer
class extends React.Component
overReact.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>;
}
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.
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
At first, React hooks will be used as the primary state management tool state, especially with useState
, useEffect
, and useReducer
. We can use useReducer
as 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.
- 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} /> // π
)