Files
twenty_crm/docs/docs/developer/frontend/best-practices.mdx
gitstart-twenty ddcfe5f0ac chore(docs): Update the best practices page (#1303)
* chore(docs): Update the best practices page

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Add minor refactors

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

---------

Co-authored-by: v1b3m <vibenjamin6@gmail.com>
2023-08-29 10:34:51 +02:00

296 lines
8.9 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
sidebar_position: 2
sidebar_custom_props:
icon: TbChecklist
---
# Best practices
## State management
We use React and Recoil for state management.
### Use `useRecoilState` to store state
We recommend that you create as many atoms as you need to store your state.
Rule of thumb : It's better to be using too many atoms than trying to be too concise with props drilling.
```tsx
export const myAtomState = atom({
key: 'myAtomState',
default: 'default value',
});
export function MyComponent() {
const [myAtom, setMyAtom] = useRecoilState(myAtomState);
return (
<div>
<input
value={myAtom}
onChange={(e) => setMyAtom(e.target.value)}
/>
</div>
);
}
```
### Do not use `useRef` to store state
We do not recommend using `useRef` to store state.
If you want to store state, you should use `useState` or `useRecoilState`.
We recommand seeing [how to manage re-renders](#managing-re-renders) if you feel like you need `useRef` to prevent some re-renders from happening.
## Managing re-renders
Re-renders can be hard to manage in React.
We provide you with some rules that we follow to avoid unnecessary re-renders.
Keep in mind that re-renders can **always** be avoided by understanding the cause of the re-render.
### Work at the root level
We made it easy for you to avoid re-renders in new features by taking care of eliminating them at the root level.
There's only one `useEffect` in the sidecar component `AuthAutoRouter` that is holding all the logic that should be executed on a page change.
That way you know that there's only one place that can trigger a re-render.
### Always think twice before adding `useEffect` in your codebase
Re-renders are often caused by unnecessary `useEffect`.
You should think whether the useEffect is really needed, or if you can move the logic in a event handler function.
You'll find it generally easy to move the logic in a `handleClick` or `handleChange` function.
You can also find them in libraries like Apollo : `onCompleted`, `onError`, etc.
### Use a sibling component to extract useEffect or data fetching logic
If you feel like you need to add a `useEffect` in your root component, you should consider extracting it in a sidecar component.
The same can be applied for data fetching logic, with Apollo hooks.
```tsx
// ❌ Bad, will cause re-renders even if data is not changing,
// because useEffect needs to be re-evaluated
export function PageComponent() {
const [data, setData] = useRecoilState(dataState);
const [someDependency] = useRecoilState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
setData(someDependency);
}
}, [someDependency]);
return <div>{data}</div>;
};
export function App() {
return (
<RecoilRoot>
<PageComponent />
</RecoilRoot>
);
}
```
```tsx
// ✅ Good, will not cause re-renders if data is not changing,
// because useEffect is re-evaluated in another sibling component
export function PageComponent() {
const [data, setData] = useRecoilState(dataState);
return <div>{data}</div>;
};
export function PageData() {
const [data, setData] = useRecoilState(dataState);
const [someDependency] = useRecoilState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
setData(someDependency);
}
}, [someDependency]);
return <></>;
};
export function App() {
return (
<RecoilRoot>
<PageData />
<PageComponent />
</RecoilRoot>
);
}
```
### Use recoil family states and recoil family selectors
Recoil family states and selectors are a great way to avoid re-renders.
They are especially useful when you need to store a list of items.
### You shouldn't use `React.memo(MyComponent)`
We do not recommend `React.memo()` usage because it does not solve the cause of the re-render, but instead breaks the re-render chain, which can lead to unexpected behavior and make the code really hard to refactor.
### Limit `useCallback` or `useMemo` usage
They are often not necessary and will make the code harder to read and maintain for a gain of performance that is unnoticeable.
## Console.logs
`console.log` statements are invaluable during development, offering real-time insights into variable values and code flow. However, leaving them in production code can lead to several issues:
1. **Performance**: Excessive logging can affect the runtime performance, especially on client-side applications.
2. **Security**: Logging sensitive data can expose critical information to anyone who inspects the browser's console.
3. **Cleanliness**: Filling up the console with logs can obscure important warnings or errors that developers or tools need to see.
4. **Professionalism**: End users or clients checking the console and seeing a myriad of log statements might question the code's quality and polish.
## Naming
### Variable Naming
Variable names ought to precisely depict the purpose or function of the variable.
#### The issue with generic names
Generic names in programming are not ideal because they lack specificity, leading to ambiguity and reduced code readability. Such names fail to convey the variable or function's purpose, making it challenging for developers to understand the code's intent without deeper investigation. This can result in increased debugging time, higher susceptibility to errors, and difficulties in maintenance and collaboration. Descriptive naming, on the other hand, makes the code self-explanatory and easier to navigate, enhancing overall code quality and developer productivity.
```tsx
// ❌ Bad, uses a generic name that doesn't communicate its
// purpose or content clearly
const [value, setValue] = useState('');
```
```tsx
// ✅ Good, uses a descriptive name
const [email, setEmail] = useState('');
```
#### Some words to avoid in variable names
- dummy
### Event handlers
Event handler names should start with `handle` instead of `on`
```tsx
// ❌ Bad
const onEmailChange = (val: string) => {
// ...
}
```
```tsx
// ✅ Good
const handleEmailChange = (val: string) => {
// ...
}
```
## Optional Props
Avoid supplying the default value for an optional prop, as it generally doesnt contribute significantly.
EXAMPLE
Assume, we have the `EmailField` component defined below
```tsx
type OwnProps = {
value: string;
disabled?: boolean;
};
function EmailField({ value, disabled = false }: OwnProps) {
return <TextInput value={value} disabled fullWidth />;
}
```
USAGE
```tsx
// ❌ Bad, passing in the same value as the default value adds no value
function Form() {
return <EmailField value="username@email.com" disabled={false} />;
}
```
```tsx
// ✅ Good, assumes the default value
function Form() {
return <EmailField value="username@email.com" />;
}
```
## Prop Drilling: Keep It Minimal
Prop drilling, in the React context, refers to the practice of passing state variables and their setters through multiple component layers, even if intermediary components don't use them. While sometimes necessary, excessive prop drilling can lead to:
1. **Decreased Readability**: Tracing where a prop originates or where it's utilized can become convoluted in a deeply nested component structure.
2. **Maintenance Challenges**: Changes in one component's prop structure might necessitate adjustments in several components, even if they don't directly use the prop.
3. **Reduced Component Reusability**: A component receiving numerous props solely for the purpose of passing them down becomes less general-purpose and harder to reuse in different contexts.
To combat excessive prop drilling:
- **Context API**: Consider using React's Context API for state that needs to be accessed by many components throughout your app.
- **Component Composition**: Refactor components to minimize deep nesting or make use of render props to share stateful logic.
- **Utilize Recoiljs**: Recoiljs is a state management tool for React. By using atoms and selectors, it streamlines shared state access, reducing the need for prop drilling, and enhances component organization and performance.
## Imports
When importing, opt for the designated aliases rather than specifying complete or relative paths.
THE ALIASES
```js
{
alias: {
"~": path.resolve(__dirname, "src"),
"@": path.resolve(__dirname, "src/modules"),
"@testing": path.resolve(__dirname, "src/testing"),
},
}
```
USAGE
```tsx
// ❌ Bad, specifies the entire relative path
import {
CatalogDecorator
} from '../../../../../testing/decorators/CatalogDecorator';
import {
ComponentDecorator
} from '../../../../../testing/decorators/ComponentDecorator';
```
```tsx
// ✅ Good, utilises the designated aliases
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';=
```
## Breaking Changes
Prioritize thorough manual testing before proceeding to guarantee that modifications havent caused disruptions elsewhere, given that tests have not yet been extensively integrated.