Added a first round of docs for front end (#1246)

This commit is contained in:
Lucas Bordeau
2023-08-18 00:16:48 +02:00
committed by GitHub
parent e8e6d9f8ea
commit 390e70a196
11 changed files with 383 additions and 4 deletions

View File

@@ -1,6 +1,6 @@
{
"label": "Additional resources",
"position": 3,
"position": 10,
"collapsible": true,
"collapsed": true,
"className": "navbar-sub-menu"

View File

@@ -1,7 +1,7 @@
{
"label": "Frontend",
"position": 3,
"collapsible": false,
"collapsible": true,
"collapsed": false,
"className": "navbar-sub-menu"
}

View File

@@ -0,0 +1,151 @@
---
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.

View File

@@ -0,0 +1,10 @@
---
sidebar_position: 6
sidebar_custom_props:
icon: TbPaint
---
# Design System
We rely on our internal and custom design system, that is built on top of styled-components.

View File

@@ -1,5 +1,5 @@
---
sidebar_position: 0
sidebar_position: 4
sidebar_custom_props:
icon: TbFolder
---

View File

@@ -0,0 +1,27 @@
---
sidebar_position: 1
sidebar_custom_props:
icon: TbZoomQuestion
---
# Getting started
## Installation
To install the front end, you'll have to [install Yarn](https://yarnpkg.com/getting-started/install) first.
Then just run :
```
yarn
```
## Development flow
You can start the application for local development with :
```
yarn start
```
Then go to our [best-practice](/developer/frontend/best-practices) guide and the [folder architecture](/developer/frontend/folder-architecture) to learn more about how to structure your feature.

View File

@@ -0,0 +1,24 @@
---
sidebar_position: 10
sidebar_custom_props:
icon: TbKeyboard
---
# Hotkeys
You can intercept any hotkey combination and execute a custom action.
We added a thin wrapper on top of [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) to make it more performant and to avoid unnecessary re-renders.
We also created a wrapper hook `useScopedHotkeys` to make it easy to manage scopes.
```ts
useScopedHotkeys(
'ctrl+k,meta+k',
() => {
openCommandMenu();
},
AppHotkeyScope.CommandMenu,
[openCommandMenu],
);
```

View File

@@ -0,0 +1,52 @@
---
sidebar_position: 0
sidebar_custom_props:
icon: TbEyeglass
---
# Overview
## Tech Stack
We took care of having a clean and simple stack, with minimal boilerplate code.
**App**
- [React](https://react.dev/)
- [Apollo](https://www.apollographql.com/docs/)
- [GraphQL Codegen](https://the-guild.dev/graphql/codegen)
- [Recoil](https://recoiljs.org/docs/introduction/core-concepts)
- [TypeScript](https://www.typescriptlang.org/)
**Testing**
- [Jest](https://jestjs.io/)
- [Storybook](https://storybook.js.org/)
**Tooling**
- [Yarn](https://yarnpkg.com/)
- [Craco](https://craco.js.org/docs/)
- [ESLint](https://eslint.org/)
## Architecture
### Routing
We use [React Router](https://reactrouter.com/) for routing.
To avoid unnecessary [re-renders](/developer/frontend/best-practices#managing-re-renders) we handle all the routing logic in a `useEffect` in `AuthAutoRouter`.
### State Management
We use [Recoil](https://recoiljs.org/docs/introduction/core-concepts) for state management.
See our [best practices](/developer/frontend/best-practices#state-management) for more managing state.
## Testing
We use [Jest](https://jestjs.io/) for unit testing and [Storybook](https://storybook.js.org/) for component testing.
Jest is mainly used for testing utility functions, and not components themselves.
Storybook is used for testing the behavior of isolated components, as well as displaying our [design system](/developer/frontend/design-system).

View File

@@ -0,0 +1,102 @@
---
sidebar_position: 3
sidebar_custom_props:
icon: TbPencil
---
# Style guide
We define here the rules to follow when writing code.
Our goal is to have a consistent codebase, easy to read and easy to maintain.
For this we prefer to tend towards being a bit more verbose than being too concise.
Always keep in mind that code is read more often than it is written, especially on an open source project, where anyone can contribute.
There are a lot of rules that are not defined here, but that are automatically checked by our linters.
## React
### Use functional components
Always use TSX functional components.
Do not use default import with const, because it's harder to read and harder to import with code completion.
```tsx
// ❌ Bad, harder to read, harder to import with code completion
const MyComponent = () => {
return <div>Hello World</div>;
};
export default MyComponent;
// ✅ Good, easy to read, easy to import with code completion
export function MyComponent() {
return <div>Hello World</div>;
};
```
### Props
Create the type of the props and call it `OwnProps` if there's no need to export it.
Use props destructuring.
```tsx
// ❌ Bad, no type
export function MyComponent(props) {
return <div>Hello {props.name}</div>;
};
// ✅ Good, type
type OwnProps = {
name: string;
};
export function MyComponent({ name }: OwnProps) {
return <div>Hello {name}</div>;
};
```
## JavaScript
### Use nullish-coalescing operator `??`
```tsx
// ❌ Bad, can return 'default' even if value is 0 or ''
const value = process.env.MY_VALUE || 'default';
// ✅ Good, will return 'default' only if value is null or undefined
const value = process.env.MY_VALUE ?? 'default';
```
### Use optional chaining `?.`
```tsx
// ❌ Bad
onClick && onClick();
// ✅ Good
onClick?.();
```
## TypeScript
### Use type instead of Interface
We decided to always use type instead of interface, because they almost always overlap, and type is more flexible.
```tsx
// ❌ Bad
interface MyInterface {
name: string;
}
// ✅ Good
type MyType = {
name: string;
};
```

View File

@@ -8,7 +8,7 @@ const darkCodeTheme = require('prism-react-renderer/themes/dracula');
/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'Twenty - Documentation',
tagline: 'Dinosaurs are cool',
tagline: 'Twenty is cool',
favicon: 'img/logo-square-dark.ico',
// Prevent search engines from indexing the doc for selected environments

View File

@@ -23,6 +23,12 @@ import {
TbBugOff,
TbBrandVscode,
TbFolder,
TbEyeglass,
TbZoomQuestion,
TbPaint,
TbChecklist,
TbKeyboard,
TbPencil,
} from "react-icons/tb";
@@ -53,6 +59,13 @@ export default function DocSidebarItemLink({
'TbBrandVscode': TbBrandVscode,
'TbDeviceDesktop': TbDeviceDesktop,
'TbFolder': TbFolder,
'TbEyeglass': TbEyeglass,
'TbZoomQuestion': TbZoomQuestion,
'TbPaint': TbPaint,
'TbChecklist': TbChecklist,
'TbKeyboard': TbKeyboard,
'TbChecklist': TbChecklist,
'TbPencil': TbPencil,
};
let IconComponent = customProps && customProps.icon ? icons[customProps.icon] : TbFaceIdError;