diff --git a/frontend/react/style-guide.md b/frontend/react/style-guide.md index d3dc3dfb..1036a80c 100644 --- a/frontend/react/style-guide.md +++ b/frontend/react/style-guide.md @@ -26,7 +26,7 @@ - Only include one React component per file. - Always use JSX syntax. - - Do not use `React.createElement` unless you're initializing the app from a file that is not JSX. + - Do not use `React.createElement`. - Always use export default for Components. - Use `export default` for reducers, actionCreators and services. As a general rule of thumb, use `export default` for all files that have a unique object to export. @@ -38,49 +38,52 @@ src └───app │ │ │ └───components -│ │ └───baseComponents -│ │ └───Input -│ │ └───Text -│ │ └───Button -│ │ └───etc +│ | └───FormInput +│ | └───SearchBar +│ | └───Table +│ | └───etc +| | │ └───screens │ └───MyScreenComponent -│ └───assets // Screen specific app assets -│ | components -│ | constants.js -│ | i18n.js -│ | index.js -│ | layout.js +│ └───assets # Screen specific app assets +│ └───context +| | index.ts +│ | actions.ts +│ | reducer.ts +│ └───components # Screen specific components +│ | constants.ts +│ | i18n.ts +│ | index.tsx │ | styles.scss -│ | utils.js +│ | utils.ts │ -└───assets // General app assets +└───assets # General app assets └───config - | api.js - | i18n.js + | api.ts + | i18n.ts └───constants -└───redux -│ | store.js -│ └───myReducer -│ | actions.js -│ | reducer.js -│ | selectors.js -│ -└───propTypes -│ | Model1.js -│ │ Model2.js +└───types # Custom global TypeScript classes +└───contexts # Global contexts +│ └───Auth +│ | index.ts +| | reducer.ts +| | actions.ts │ └───scss └───services - | MyService.js +│ │ serializers.ts +│ └───Model +│ | ModelService.ts +│ | serializers.ts │ └───utils -│ index.js +│ index.tsx ``` ## Class vs `React.createClass` vs stateless - - If you have internal state and/or refs, prefer `class extends Component` over `React.createClass`. eslint: [`react/prefer-es6-class`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-es6-class.md) [`react/prefer-stateless-function`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-stateless-function.md) + - Avoid `React.createClass`. eslint: [`react/prefer-es6-class`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-es6-class.md) [`react/prefer-stateless-function`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-stateless-function.md) + - Prefer normal functions (not arrow functions) over classes: ```jsx // bad @@ -92,49 +95,47 @@ src }); // good - class Listing extends Component { - // ... - render() { - return
{this.state.hello}
; - } + function Listing({ hello }) { + return
{hello}
; } ``` - - And if you don't have state or refs, only for stateless components, prefer normal functions (not arrow functions) over classes: + - Prefer normal function components (not arrow functions) over `class extends Component`. Only exception is usage of `componentDidCatch` and `getDerivedStateFromError` (which have no hooks support) ```jsx // bad class Listing extends Component { + state = { foo: 1 }; + render() { - return
{this.props.hello}
; + return
{this.state.hello}
; } } - // bad (relying on function name inference is discouraged) - const Listing = ({ hello }) => ( -
{hello}
- ); + // bad + const Listing = ({ hello }) => { + const [foo, setFoo] = useState(1); + return
{hello}
; + } // good function Listing({ hello }) { + const [foo, setFoo] = useState(1); return
{hello}
; } ``` - - Avoid using helper render methods when possible. Functions that return JSX elements should probably be layout components. + - Avoid using helper render methods when possible. Functions that return JSX elements should probably be components themselves. ```jsx // bad - function TextContainer extends Component { + function TextContainer({ text }) { renderText = text => text; - render() { - return ( -
- {this.renderText('aText')} -
- ) - } + return ( +
+ {this.renderText(text)} +
+ ) } // good @@ -159,46 +160,37 @@ src ## Naming - - **Extensions**: Use `.js` extension for React components. - - **Filename**: For component filenames and services use PascalCase. E.g., `ReservationCard.js`. + - **Extensions**: Use `.ts` extension for React components. + - **Filename**: For services use PascalCase. E.g., `ReservationCard.ts` or a folder with the service name and `index.tsx` as filename. For React components, there must be a folder in PascalCase with its name and the component file should be `index.tsx` - **Reference Naming**: Use PascalCase for React components and camelCase for their associated elements. eslint: [`react/jsx-pascal-case`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-pascal-case.md) ```jsx // bad - import reservationCard from './ReservationCard'; + import myService from './MyService'; + import myComponent from './MyComponent/index.tsx'; // good - import ReservationCard from './ReservationCard'; + import MyService from './MyService'; # webpack infers index.tsx + import MyComponent from './MyComponent'; # webpack infers index.tsx // bad - const ReservationItem = ; + const MyComponent = ; // good - const reservationItem = ; + const myComponent = ; ``` - **Component Hierarchy**: - Component files should be inside folders that match the component's name. - - Use index.js as the filename of a container component. Use `Container` as the suffix of the component's name. - - Use layout.js as the filename of a layout component. + - Use index.tsx as the component filename. ```jsx - // MyComponent/index.js - import MyComponent from './layout' + // MyComponent/index.tsx - class MyComponentContainer extends Component { + function MyComponent() { // Do smart stuff - render() { - return - } - } - - // MyComponent/layout.js - function MyComponent() { - return ( - // Some JSX - ) + return } ``` @@ -254,7 +246,7 @@ src ```jsx // bad - /* routes.js */ + /* routes.ts */ const userListRoute = '/users'; const itemListRoute = '/items'; @@ -262,7 +254,7 @@ src import * as Routes from './routes'; // good - /* routes.js */ + /* routes.ts */ const Routes = { userListRoute: '/users', itemListRoute: '/items' @@ -389,25 +381,20 @@ src - Always use object destructuring to explicitly get props variables in the render function of class Components: ```jsx - import MyComponent from './layout'; + import Child from './components/Child'; // bad - class MyComponentContainer extends Component { - render() { - return - } + function MyComponent(props) { + return } // good - class MyComponentContainer extends Component { - render() { - const { foo, bar } = this.props; - return - } + function MyComponent({ foo, bar }) { + return } ``` - - Always use object destructuring to explicitly get props variables in layout Components: + - Always use object destructuring to explicitly get props variables: ```jsx // bad @@ -456,27 +443,40 @@ src /> ``` - - Avoid passing arrow functions in props when possible. Instead, create a reference to the function and pass that reference. + - Only use inline arrow functions when the arrow function is simple + - If the function is complex, define it as a const beforehand. If you are passing it to a child component. you may use `useCallback` for memoization as well. > Why? Passing arrow functions as props in render creates a new function each time the component renders, which is less performant. ```jsx - import MyComponent from './layout'; - // bad - class MyComponentContainer extends Component { - render() { - return bar + 1} /> - } + function MyComponent() { + return