Skip to content

Commit

Permalink
small refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
alan2207 committed Aug 11, 2024
1 parent 71c1835 commit 29462bb
Show file tree
Hide file tree
Showing 10 changed files with 54 additions and 56 deletions.
5 changes: 1 addition & 4 deletions apps/react-vite/__mocks__/zustand.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { act } from '@testing-library/react';
import { afterEach, vi } from 'vitest';
import * as zustand from 'zustand';

const { create: actualCreate, createStore: actualCreateStore } =
Expand All @@ -18,8 +19,6 @@ const createUncurried = <T>(stateCreator: zustand.StateCreator<T>) => {

// when creating a store, we get its initial state, create a reset function and add it in the set
export const create = (<T>(stateCreator: zustand.StateCreator<T>) => {
console.log('zustand create mock');

// to support curried version of create
return typeof stateCreator === 'function'
? createUncurried(stateCreator)
Expand All @@ -37,8 +36,6 @@ const createStoreUncurried = <T>(stateCreator: zustand.StateCreator<T>) => {

// when creating a store, we get its initial state, create a reset function and add it in the set
export const createStore = (<T>(stateCreator: zustand.StateCreator<T>) => {
console.log('zustand createStore mock');

// to support curried version of createStore
return typeof stateCreator === 'function'
? createStoreUncurried(stateCreator)
Expand Down
22 changes: 4 additions & 18 deletions apps/react-vite/src/app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,10 @@
import { useQueryClient } from '@tanstack/react-query';
import { useMemo } from 'react';
import { RouterProvider } from 'react-router-dom';
import { AppProvider } from './provider';
import { AppRouter } from './router';

import { AppProvider } from './main-provider';
import { createRouter } from './routes';

const AppRouter = () => {
const queryClient = useQueryClient();

const router = useMemo(() => createRouter(queryClient), [queryClient]);

return <RouterProvider router={router} />;
};

function App() {
export const App = () => {
return (
<AppProvider>
<AppRouter />
</AppProvider>
);
}

export default App;
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { QueryClientProvider } from '@tanstack/react-query';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import * as React from 'react';
import { ErrorBoundary } from 'react-error-boundary';
Expand All @@ -8,13 +8,20 @@ import { MainErrorFallback } from '@/components/errors/main';
import { Notifications } from '@/components/ui/notifications';
import { Spinner } from '@/components/ui/spinner';
import { AuthLoader } from '@/lib/auth';
import { queryClient } from '@/lib/react-query';
import { queryConfig } from '@/lib/react-query';

type AppProviderProps = {
children: React.ReactNode;
};

export const AppProvider = ({ children }: AppProviderProps) => {
const [queryClient] = React.useState(
() =>
new QueryClient({
defaultOptions: queryConfig,
}),
);

return (
<React.Suspense
fallback={
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
import { QueryClient } from '@tanstack/react-query';
import { LoaderFunctionArgs, createBrowserRouter } from 'react-router-dom';
import { QueryClient, useQueryClient } from '@tanstack/react-query';
import { useMemo } from 'react';
import {
LoaderFunctionArgs,
RouterProvider,
createBrowserRouter,
} from 'react-router-dom';

import { ProtectedRoute } from '@/lib/auth';

import { AppRoot } from './app/root';
import { AppRoot } from './routes/app/root';

export const createRouter = (queryClient: QueryClient) =>
export const createAppRouter = (queryClient: QueryClient) =>
createBrowserRouter([
{
path: '/',
lazy: async () => {
const { LandingRoute } = await import('./landing');
const { LandingRoute } = await import('./routes/landing');
return { Component: LandingRoute };
},
},
{
path: '/auth/register',
lazy: async () => {
const { RegisterRoute } = await import('./auth/register');
const { RegisterRoute } = await import('./routes/auth/register');
return { Component: RegisterRoute };
},
},
{
path: '/auth/login',
lazy: async () => {
const { LoginRoute } = await import('./auth/login');
const { LoginRoute } = await import('./routes/auth/login');
return { Component: LoginRoute };
},
},
Expand All @@ -40,13 +45,13 @@ export const createRouter = (queryClient: QueryClient) =>
path: 'discussions',
lazy: async () => {
const { DiscussionsRoute } = await import(
'./app/discussions/discussions'
'./routes/app/discussions/discussions'
);
return { Component: DiscussionsRoute };
},
loader: async () => {
const { discussionsLoader } = await import(
'./app/discussions/discussions'
'./routes/app/discussions/discussions'
);
return discussionsLoader(queryClient)();
},
Expand All @@ -55,41 +60,41 @@ export const createRouter = (queryClient: QueryClient) =>
path: 'discussions/:discussionId',
lazy: async () => {
const { DiscussionRoute } = await import(
'./app/discussions/discussion'
'./routes/app/discussions/discussion'
);
return { Component: DiscussionRoute };
},

loader: async (args: LoaderFunctionArgs) => {
const { discussionLoader } = await import(
'./app/discussions/discussion'
'./routes/app/discussions/discussion'
);
return discussionLoader(queryClient)(args);
},
},
{
path: 'users',
lazy: async () => {
const { UsersRoute } = await import('./app/users');
const { UsersRoute } = await import('./routes/app/users');
return { Component: UsersRoute };
},

loader: async () => {
const { usersLoader } = await import('./app/users');
const { usersLoader } = await import('./routes/app/users');
return usersLoader(queryClient)();
},
},
{
path: 'profile',
lazy: async () => {
const { ProfileRoute } = await import('./app/profile');
const { ProfileRoute } = await import('./routes/app/profile');
return { Component: ProfileRoute };
},
},
{
path: '',
lazy: async () => {
const { DashboardRoute } = await import('./app/dashboard');
const { DashboardRoute } = await import('./routes/app/dashboard');
return { Component: DashboardRoute };
},
},
Expand All @@ -98,8 +103,16 @@ export const createRouter = (queryClient: QueryClient) =>
{
path: '*',
lazy: async () => {
const { NotFoundRoute } = await import('./not-found');
const { NotFoundRoute } = await import('./routes/not-found');
return { Component: NotFoundRoute };
},
},
]);

export const AppRouter = () => {
const queryClient = useQueryClient();

const router = useMemo(() => createAppRouter(queryClient), [queryClient]);

return <RouterProvider router={router} />;
};
10 changes: 1 addition & 9 deletions apps/react-vite/src/lib/react-query.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
UseMutationOptions,
DefaultOptions,
QueryClient,
} from '@tanstack/react-query';
import { UseMutationOptions, DefaultOptions } from '@tanstack/react-query';

export const queryConfig = {
queries: {
Expand All @@ -13,10 +9,6 @@ export const queryConfig = {
},
} satisfies DefaultOptions;

export const queryClient = new QueryClient({
defaultOptions: queryConfig,
});

export type ApiFnReturnType<FnType extends (...args: any) => Promise<any>> =
Awaited<ReturnType<FnType>>;

Expand Down
2 changes: 1 addition & 1 deletion apps/react-vite/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import { createRoot } from 'react-dom/client';

import './index.css';
import App from './app';
import { App } from './app';
import { enableMocking } from './testing/mocks';

const root = document.getElementById('root');
Expand Down
2 changes: 0 additions & 2 deletions apps/react-vite/src/testing/setup-tests.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import '@testing-library/jest-dom/vitest';

import { queryClient } from '@/lib/react-query';
import { initializeDb, resetDb } from '@/testing/mocks/db';
import { server } from '@/testing/mocks/server';

Expand All @@ -25,5 +24,4 @@ beforeEach(() => {
afterEach(() => {
server.resetHandlers();
resetDb();
queryClient.clear();
});
2 changes: 1 addition & 1 deletion apps/react-vite/src/testing/test-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import userEvent from '@testing-library/user-event';
import Cookies from 'js-cookie';
import { RouterProvider, createMemoryRouter } from 'react-router-dom';

import { AppProvider } from '@/app/main-provider';
import { AppProvider } from '@/app/provider';

import {
createDiscussion as generateDiscussion,
Expand Down
2 changes: 1 addition & 1 deletion docs/performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Code splitting involves splitting production JavaScript into smaller files to op

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](../apps/react-vite/src/app/routes/index.tsx)
[Code Splitting Example Code](../apps/react-vite/src/app/router.tsx)

### Component and state optimizations

Expand Down
9 changes: 7 additions & 2 deletions docs/project-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ src
| |
| +-- routes # application routes / can also be called pages
+-- app.tsx # main application component
+-- app-provider # application provider that wraps the entire application with global providers
+-- provider.tsx # application provider that wraps the entire application with different global providers
+-- router.tsx # application router configuration
+-- assets # assets folder can contain all the static files such as images, fonts, etc.
|
+-- components # shared components used across the entire application
Expand Down Expand Up @@ -55,6 +56,10 @@ src/features/awesome-feature

NOTE: You don't need all of these folders for every feature. Only include the ones that are necessary for the feature.

In some cases it might be more practical to keep all api calls outside of the feature folders in a dedicated `api` folder where all API calls are defined. This can be useful if you have a lot of shared api calls between features.

````sh

In the past, it was recommended to use barrel files to export all the files from a feature. However, it can cause issues for Vite to do tree shaking and can lead to performance issues. Therefore, it is recommended to import the files directly.

It might not be a good idea to import across the features. Instead, compose different features at the application level. This way, you can ensure that each feature is independent which makes the codebase less convoluted.
Expand Down Expand Up @@ -98,7 +103,7 @@ To forbid cross-feature imports, you can use ESLint:
],
},
],
```
````
You might also want to enforce unidirectional codebase architecture. This means that the code should flow in one direction, from shared parts of the code to the application (shared -> features -> app). This is a good practice to follow as it makes the codebase more predictable and easier to understand.
Expand Down

0 comments on commit 29462bb

Please sign in to comment.