Skip to content

Commit

Permalink
update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
alan2207 committed May 13, 2024
1 parent 8de52dc commit 21aab40
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 58 deletions.
4 changes: 2 additions & 2 deletions docs/additional-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

- [Official Documentation](https://react.dev/)
- [Tao Of React](https://alexkondov.com/tao-of-react/)
- [React Handbook](https://github.com/ericdiviney/react-handbook/)
- [React Handbook](https://reacthandbook.dev/)
- [React Philosophies](https://github.com/mithi/react-philosophies)
- [React Patterns](https://reactpatterns.com/)
- [React Typescript Cheatsheet](https://react-typescript-cheatsheet.netlify.app/)

## JavaScript

Expand All @@ -20,4 +21,3 @@
- [patterns.dev](https://www.patterns.dev/)
- [Naming Cheatsheet](https://github.com/kettanaito/naming-cheatsheet)
- [Clean Code Javascript](https://github.com/ryanmcdermott/clean-code-javascript)
- [React Typescript Cheatsheet](https://react-typescript-cheatsheet.netlify.app/)
8 changes: 4 additions & 4 deletions docs/api-layer.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

When your application interacts with either RESTful or GraphQL APIs, it is beneficial to use a single instance of the API client that has been pre-configured and can be reused throughout the application. For example, you can create a single API client instance using the native fetch API or libraries such as [axios](https://github.com/axios/axios), [graphql-request](https://github.com/prisma-labs/graphql-request), or [apollo-client](https://www.apollographql.com/docs/react/) with predefined configuration settings.

[Example Code for API Client](../src/lib/api-client.ts)
[API Client Example Code](../src/lib/api-client.ts)

### Define and Export Request Declarations

Expand All @@ -15,9 +15,9 @@ Every API request declaration should consist of:

- Types and validation schemas for the request and response data
- A fetcher function that calls an endpoint, using the API client instance
- A hook that consumes the fetcher function that is built on top of libraries such as [react-query](https://react-query.tanstack.com/), [swr](<[https://](https://swr.vercel.app/)>), [apollo-client](https://www.apollographql.com/docs/react/), [urql](https://formidable.com/open-source/urql/), etc. to manage the data fetching and caching logic.
- A hook that consumes the fetcher function that is built on top of libraries such as [react-query](https://tanstack.com/query), [swr](<[https://](https://swr.vercel.app/)>), [apollo-client](https://www.apollographql.com/docs/react/), [urql](https://formidable.com/open-source/urql/), etc. to manage the data fetching and caching logic.

This approach simplifies the tracking of defined endpoints available in the application. Additionally, typing the responses and inferring them further down the application enhances application type safety.

[Example Code for API Request Declarations - Query](../src/features/discussions/api/get-discussions.ts)
[Example Code for API Request Declarations - Mutation](../src/features/discussions/api/create-discussion.ts)
[API Request Declarations - Query - Example Code](../src/features/discussions/api/get-discussions.ts)
[API Request Declarations - Mutation - Example Code](../src/features/discussions/api/create-discussion.ts)
4 changes: 2 additions & 2 deletions docs/error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

### API Errors

Set up an interceptor for handling errors. You can use it to fire a notification toast to notify users that something went wrong, log out unauthorized users, or send new requests for refreshing tokens.
Implement an interceptor to manage errors effectively. This interceptor can be utilized to trigger notification toasts informing users of errors, log out unauthorized users, or send requests to refresh tokens to maintain secure and seamless application operation.

[API Errors Notification Example Code](../src/lib/api-client.ts)

### In App Errors

Use error boundaries to handle errors that happen in the React tree. It is very popular to set only 1 single error boundary for the entire application, which would break the entire application when an error occurs. That's why you should have more error boundaries on more specific parts of the application. That way if an error occurs the app will still work without the need to restart it.
Utilize error boundaries in React to handle errors within specific parts of your application. Instead of having only one error boundary for the entire app, consider placing multiple error boundaries in different areas. This way, if an error occurs, it can be contained and managed locally without disrupting the entire application's functionality, ensuring a smoother user experience.

[Error Boundary Example Code](../src/providers/app.tsx)

Expand Down
8 changes: 3 additions & 5 deletions docs/performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@

### Code Splitting

Code splitting is a technique of splitting production JavaScript into smaller files, thus allowing the application to be only partially downloaded. Any unused code will not be downloaded until it is required by the application.
Code splitting involves dividing production JavaScript into smaller files to optimize application loading times. This technique enables the application to be downloaded in parts, fetching only the necessary code when required.

Most of the time code splitting should be done on the routes level, but can also be used for other lazy loaded parts of application.

Do not code split everything as it might even worsen your application's performance because of many requests your application needs to make to get all the chunks.
Ideally, code splitting should be implemented at the routes level, ensuring that only essential code is loaded initially, with additional parts fetched lazily as needed. It's important to avoid excessive code splitting, as this can lead to a performance decline due to the increased number of requests required to fetch all the code chunks. Strategic code splitting, focusing on critical parts of the application, helps balance performance optimization with efficient resource loading.

[Code Splitting Example Code](../src/routes/index.tsx)

Expand All @@ -28,7 +26,7 @@ const [state, setState] = React.useState(() => myExpensiveFn());

- If you develop an application that requires a state to track many elements at once, you might consider state management libraries with atomic updates such as [jotai](https://jotai.pmnd.rs/).

- Use React Context wisely. React Context is good for low-velocity data like themes, user data, small local state etc. While dealing with medium-velocity/high-velocity data, you may consider using the [use-context-selector](https://github.com/dai-shi/use-context-selector) library that supports selectors (selectors are already built-in in most popular state management libraries like [zustand](https://docs.pmnd.rs/zustand/getting-started/introduction) or [jotai](https://jotai.org/)). Important to remember, context is often used as the "golden tool" for props drilling, whereas in many scenarios you may satisfy your needs by [lifting the state up](https://react.dev/learn/sharing-state-between-components#lifting-state-up-by-example) or [a proper composition of components](https://react.dev/learn/passing-data-deeply-with-context#before-you-use-context). Do not rush with context.
- Use React Context wisely. React Context is good for low-velocity data like themes, user data, small local state etc. While dealing with medium-velocity/high-velocity data, you may consider using the [use-context-selector](https://github.com/dai-shi/use-context-selector) library that supports selectors (selectors are already built-in in most popular state management libraries like [zustand](https://docs.pmnd.rs/zustand/getting-started/introduction) or [jotai](https://jotai.org/)). Important to remember, context is often used as the "golden tool" for props drilling, whereas in many scenarios you may satisfy your needs by [lifting the state up](https://react.dev/learn/sharing-state-between-components#lifting-state-up-by-example) or [a proper composition of components](https://react.dev/learn/passing-data-deeply-with-context#before-you-use-context). Do not rush with context and global state.

- If your application is expected to have frequent updates that might affect performance, consider switching from runtime styling solutions such as [emotion](https://emotion.sh/docs/introduction), [styled-components](https://styled-components.com/) that generate styles during runtime) to zero runtime styling solutions ([tailwind](https://tailwindcss.com/), [vanilla-extract](https://github.com/seek-oss/vanilla-extract), [CSS modules](https://github.com/css-modules/css-modules) which generate styles during build time).

Expand Down
14 changes: 7 additions & 7 deletions docs/project-configuration.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
# ⚙️ Project Configuration

The application has been bootstrapped using `Vite ` and its `react-ts` template. It allows us to create applications quickly without dealing with a complex tooling setup such as bundling, transpiling etc.
The sample application has been bootstrapped using `Vite ` and its `react-ts` template. It allows us to create applications quickly without dealing with a complex tooling setup such as bundling, transpiling etc.

Other popular ways we could have bootstrapped the application are:
Other popular ways you can bootstrap the application are:

- [Next.js](https://nextjs.org/)
- [Remix](https://remix.run/)

But for simplicity we will use Vite, as it doesn't force us to use the meta-framework specific things, and allows us to focus on React.
For simplicity we will use Vite, as it doesn't force us to use the meta-framework specific things, and allows us to focus on React.

You should always configure and use the following tools:

#### ESLint

ESLint is a linting tool for JavaScript. By providing specific configuration defined in the`.eslintrc.js` file it prevents developers from making silly mistakes in their code and enforces consistency in the codebase.
ESLint serves as a valuable linting tool for JavaScript, helping developers in maintaining code quality and adhering to coding standards. By configuring rules in the `.eslintrc.js` file, ESLint helps identify and prevent common errors, ensuring code correctness and promoting consistency throughout the codebase. This approach not only helps in catching mistakes early but also enforces uniformity in coding practices, thereby enhancing the overall quality and readability of the code.

[ESLint Configuration Example Code](../.eslintrc.js)

#### Prettier

Prettier is a great tool for formatting code. It enforces a consistent code style across your entire codebase. By utilizing the "format on save" feature in your IDE you can automatically format the code based on the configuration provided in the `.prettierrc` file. It will also give you good feedback when something is wrong with the code. If the auto-format doesn't work, something is wrong with the code.
Prettier is a useful tool for maintaining consistent code formatting in your project. By enabling the "format on save" feature in your IDE, code is automatically formatted according to the rules set in the `.prettierrc` configuration file. This practice ensures a uniform code style across your codebase and provides helpful feedback on code issues. If the auto-formatting fails, it signals potential syntax error. Furthermore, Prettier can be integrated with ESLint to handle code formatting tasks alongside enforcing coding standards effectively throughout the development process.

[Prettier Configuration Example Code](../.prettierrc)

#### TypeScript

ESLint is great for catching some of the bugs related to the language, but since JavaScript is a dynamic language ESLint cannot check data that run through the applications, which can lead to bugs, especially on larger projects. That is why TypeScript should be used. It is very useful during large refactors because it reports any issues you might miss otherwise. When refactoring, change the type declaration first, then fix all the TypeScript errors throughout the project and you are done. One thing you should keep in mind is that TypeScript does not protect your application from failing during runtime, it only does type checking during build time, but it increases development confidence drastically anyways. Here is a [great resource on using TypeScript with React](https://react-typescript-cheatsheet.netlify.app/).
ESLint is effective for detecting language-related bugs in JavaScript. However, due to JavaScript's dynamic nature, ESLint may not catch all runtime data issues, especially in complex projects. To address this, TypeScript is recommended. TypeScript is valuable for identifying issues during large refactoring processes that may go unnoticed. When refactoring, prioritize updating type declarations first, then resolving TypeScript errors throughout the project. It's important to note that while TypeScript enhances development confidence by performing type checking at build time, it does not prevent runtime failures. Here is a [great resource on using TypeScript with React](https://react-typescript-cheatsheet.netlify.app/).

#### Husky

Husky is a tool for executing git hooks. Use Husky to run your code validations before every commit, thus making sure the code is in the best shape possible at any point of time and no faulty commits get into the repo. It can run linting, code formatting and type checking, etc. before it allows pushing the code. You can check how to configure it [here](https://typicode.github.io/husky/#/?id=usage).
Husky is a valuable tool for implementing and executing git hooks in your workflow. By utilizing Husky to run code validations before each commit, you can ensure that your code maintains high standards and that no faulty commits are pushed to the repository. Husky enables you to perform various tasks such as linting, code formatting, and type checking before allowing code pushes. You can check how to configure it [here](https://typicode.github.io/husky/#/?id=usage).

#### Absolute imports

Expand Down
12 changes: 6 additions & 6 deletions docs/project-structure.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 🗄️ Project Structure

Most of the code lives in the `src` folder and looks like this:
Most of the code lives in the `src` folder and looks something like this:

```sh
src
Expand All @@ -15,22 +15,22 @@ src
|
+-- hooks # shared hooks used across the entire application
|
+-- lib # re-exporting different libraries preconfigured for the application
+-- lib # reusable libraries preconfigured for the application
|
+-- providers # all of the application providers
|
+-- routes # routes configuration
|
+-- stores # global state stores
|
+-- test # test utilities and mock server
+-- test # test utilities and mocks
|
+-- types # base types used across the application
+-- types # shared types used across the application
|
+-- utils # shared utility functions
```

In order to scale the application in the easiest and most maintainable way, keep most of the code inside the `features` folder, which should contain different feature-based things. Every `feature` folder should contain domain specific code for a given feature. This will allow you to keep functionalities scoped to a feature and not mix its declarations with shared things. This is much easier to maintain than a flat folder structure with many files.
For easy scalability and maintenance, organize most of the code within the features folder. Each feature folder should contain code specific to that feature, keeping things neatly separated. This approach helps prevent mixing feature-related code with shared components, making it simpler to manage and maintain the codebase compared to having many files in a flat folder structure. By adopting this method, you can enhance collaboration, readability, and scalability in the application's architecture.

A feature could have the following structure:

Expand Down Expand Up @@ -92,7 +92,7 @@ To prevent circular dependencies, here is another ESLint rule that can be used:
}
```
This was inspired by how [NX](https://nx.dev/) handles libraries that are isolated but available to be used by the other modules. Think of a feature as a library or a module that is self-contained but can expose different parts to other features via its entry point. This approach will also make it easier to split the application in a monorepo in the future.
This was inspired by how [NX](https://nx.dev/) handles libraries that are isolated but available to be used by the other modules. Think of a feature as a library or a module that is self-contained but can expose different parts to other features via its entry point. This approach would also make it easier to split the application in a monorepo in the future.
This way, you can ensure that the codebase is clean and easy to maintain.
Expand Down
Loading

0 comments on commit 21aab40

Please sign in to comment.