Skip to content

[React] Updated some old rules #183

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 76 additions & 99 deletions frontend/react/style-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,62 +37,46 @@ src
└───app
│ │
│ └───components
│ │ └───atoms # Can't be broken down into smaller things
│ │ | └───Input
│ │ | └───Text
│ │ | └───Button
│ │ | └───etc
│ │ └───molecules # Two or more atoms
│ │ | └───FormInput
│ │ | └───SearchBar
│ │ | └───Table
│ │ | └───etc
│ │ └───organisms # Two or more molecules
│ │ └───LoginForm
│ │ └───UserTable
│ │ └───etc
│ | └───FormInput
│ | └───SearchBar
│ | └───Table
│ | └───etc
| |
│ └───screens
│ └───MyScreenComponent
│ └───assets # Screen specific app assets
│ └───reducer
│ | actions.js
│ | reducer.js
│ | selectors.js
│ | effects.js
│ └───context
| | index.ts
│ | actions.ts
│ | reducer.ts
│ └───components # Screen specific components
│ | constants.js
│ | i18n.js
│ | index.js
│ | layout.js
│ | constants.ts
│ | i18n.ts
│ | index.tsx
│ | styles.scss
│ | utils.js
│ | utils.ts
└───assets # General app assets
└───config
| api.js
| i18n.js
| api.ts
| i18n.ts
└───constants
└───redux
│ | store.js
│ └───myReducer
│ | actions.js
│ | reducer.js
│ | selectors.js
│ | effects.js
└───propTypes
│ | Model1.js
│ │ Model2.js
└───types # Custom global TypeScript classes
└───contexts # Global contexts
│ └───Auth
│ | index.ts
| | reducer.ts
| | actions.ts
└───scss
└───services
│ │ serializers.js
│ │ serializers.ts
│ └───Model
│ | ModelService.js
│ | serializers.js
│ | ModelService.ts
│ | serializers.ts
└───utils
│ index.js
│ index.tsx
```

## Class vs `React.createClass` vs stateless
Expand Down Expand Up @@ -175,18 +159,18 @@ src

## Naming

- **Extensions**: Use `.js` extension for React components.
- **Filename**: For services use PascalCase. E.g., `ReservationCard.js` or a folder with the service name and `index.js` as filename. For React components, there must be a folder in PascalCase with its name and the component file should be `index.js` (and optionally `layout.js` if you're using [Smart/Dumb components](https://medium.com/@thejasonfile/dumb-components-and-smart-components-e7b33a698d43)
- **Extensions**: Use `.ts` extension for React components.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be .tsx in most cases.

- **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`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- **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`
- **Filename**: For services use PascalCase. E.g., `ReservationCard.ts` or a folder with the service name and `index.ts` 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 myService from './MyService';
import myComponent from './MyComponent';
import myComponent from './MyComponent/index.tsx';

// good
import MyService from './MyService'; # webpack infers index.js
import MyComponent from './MyComponent'; # webpack infers index.js
import MyService from './MyService'; # webpack infers index.tsx
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import MyService from './MyService'; # webpack infers index.tsx
import MyService from './MyService'; # webpack infers index.ts

import MyComponent from './MyComponent'; # webpack infers index.tsx

// bad
const MyComponent = <MyComponent />;
Expand All @@ -196,26 +180,17 @@ src
```
- **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. Only do this if your component is becoming too big, it should be an exception, not a rule.
- Use index.tsx as the component filename.


```jsx
// MyComponent/index.js
import MyComponent from './layout'
// MyComponent/index.tsx

function MyComponentContainer() {
function MyComponent() {
// Do smart stuff

return <MyComponent />
}

// MyComponent/layout.js
function MyComponent() {
return (
// Some JSX
)
}
```

- **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)`.
Expand Down Expand Up @@ -270,15 +245,15 @@ src

```jsx
// bad
/* routes.js */
/* routes.ts */
const userListRoute = '/users';
const itemListRoute = '/items';

/* Another file */
import * as Routes from './routes';

// good
/* routes.js */
/* routes.ts */
const Routes = {
userListRoute: '/users',
itemListRoute: '/items'
Expand Down Expand Up @@ -405,25 +380,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 <MyComponent foo={this.props.foo} bar={this.props.bar} />
}
function MyComponent(props) {
return <Child foo={props.foo} bar={props.bar} />
}

// good
class MyComponentContainer extends Component {
render() {
const { foo, bar } = this.props;
return <MyComponent foo={foo} bar={bar} />
}
function MyComponent({ foo, bar }) {
return <Child foo={foo} bar={bar} />
}
```

- Always use object destructuring to explicitly get props variables in layout Components:
- Always use object destructuring to explicitly get props variables:

```jsx
// bad
Expand Down Expand Up @@ -472,27 +442,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 <MyComponent foo={bar => bar + 1} />
}
function MyComponent() {
return <button onClick={event => {
let baz = "Hello World";
console.log(event.target.value + baz)
}} />
}

// good
class MyComponentContainer extends Component {
foo = bar => bar + 1;
function MyComponent() {
const foo = event => {
let baz = "Hello World";
console.log(event.target.value + baz)
};

render() {
return <MyComponent foo={this.foo} />
}
return <button onClick={foo} />
}

// better when passing it to a child component
import Child from './components/Child';

function MyComponent() {
const foo = useCallback(event => {
let baz = "Hello World";
console.log(event.target.value + baz)
}, []);

return <Child onClick={foo} />
}
```

Expand Down Expand Up @@ -667,26 +650,20 @@ src

```jsx
// bad
render() {
return <MyComponent variant="long body" foo="bar">
<MyChild />
</MyComponent>;
}
return <MyComponent variant="long body" foo="bar">
<MyChild />
</MyComponent>;

// good
render() {
return (
<MyComponent variant="long body" foo="bar">
<MyChild />
</MyComponent>
);
}
return (
<MyComponent variant="long body" foo="bar">
<MyChild />
</MyComponent>
);

// good, when single line
render() {
const body = <div>hello</div>;
return <MyComponent>{body}</MyComponent>;
}
const body = <div>hello</div>;
return <MyComponent>{body}</MyComponent>;
```

## Tags
Expand Down