pejay-ui 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/registry.json +33 -1
- package/templates/notes/create-pejay.md +222 -0
- package/templates/notes/notes-v1.md +516 -0
- package/templates/notes/notes-v2.md +764 -0
- package/templates/notes/notes-v3.md +574 -0
- package/templates/notes/notes-v4.md +811 -0
- package/templates/notes/notes-v5.md +579 -0
- package/templates/notes/notes-vf+1.md +311 -0
- package/templates/notes/notes-vfinal.md +852 -0
- package/templates/scaffolds/axios/api/index.ts +40 -0
- package/templates/scaffolds/axios/api/one.api.ts +94 -0
- package/templates/scaffolds/axios/endpoints.ts +9 -0
- package/templates/scaffolds/axios/index.ts +26 -0
- package/templates/scaffolds/axios/interceptors.ts +103 -0
- package/templates/scaffolds/axios/request.ts +32 -0
- package/templates/scaffolds/react-router/hook/useRouterSearch.ts +8 -0
- package/templates/scaffolds/react-router/router/guards/private.route.tsx +1 -0
- package/templates/scaffolds/react-router/router/index.ts +26 -0
- package/templates/scaffolds/react-router/router/layouts/error.layout.tsx +1 -1
- package/templates/scaffolds/redux-store/middlewares.ts +0 -0
- package/templates/scaffolds/redux-store/reducers.ts +30 -0
- package/templates/scaffolds/redux-store/selector/one.selector.ts +43 -0
- package/templates/scaffolds/redux-store/selector/two.selector.ts +11 -0
- package/templates/scaffolds/redux-store/slices/one.slice.ts +202 -0
- package/templates/scaffolds/redux-store/slices/two.slice.ts +21 -0
- package/templates/scaffolds/redux-store/store.ts +38 -0
- package/templates/scaffolds/rtk-query/baseApi.ts +24 -0
- package/templates/scaffolds/rtk-query/baseQuery.ts +12 -0
- package/templates/scaffolds/rtk-query/endpoints/api.one.ts +82 -0
- package/templates/scaffolds/rtk-query/endpoints/index.ts +1 -0
- package/templates/scaffolds/rtk-query/middlewares.ts +11 -0
- package/templates/scaffolds/rtk-query/queryTags.ts +13 -0
- package/templates/scaffolds/tanstack-query/api-base.ts +68 -68
- package/templates/scaffolds/tanstack-query/api-queries.ts +0 -19
- package/templates/scaffolds/tanstack-query/client.ts +8 -0
- package/templates/scaffolds/tanstack-query/module/index.ts +12 -12
- package/templates/scaffolds/tanstack-query/module/keys.ts +17 -17
- package/templates/scaffolds/tanstack-query/module/mappers.ts +15 -15
- package/templates/scaffolds/tanstack-query/module/mutations.ts +59 -55
- package/templates/scaffolds/tanstack-query/module/queries.ts +145 -156
- package/templates/scaffolds/tanstack-query/module/services.ts +74 -66
- package/templates/scaffolds/tanstack-router/layout/404.layout.tsx +3 -0
- package/templates/scaffolds/tanstack-router/layout/app.layout.tsx +10 -0
- package/templates/scaffolds/tanstack-router/layout/auth.layout.tsx +10 -0
- package/templates/scaffolds/tanstack-router/layout/error.layout.tsx +3 -0
- package/templates/scaffolds/tanstack-router/page/auth/login.tsx +3 -0
- package/templates/scaffolds/tanstack-router/page/one/index.tsx +3 -0
- package/templates/scaffolds/tanstack-router/page/one/one-id.tsx +128 -0
- package/templates/scaffolds/tanstack-router/router.ts +90 -0
- package/templates/scaffolds/tanstack-router/routes/_404.tsx +0 -0
- package/templates/scaffolds/tanstack-router/routes/__root.tsx +9 -0
- package/templates/scaffolds/tanstack-router/routes/_app.tsx +6 -0
- package/templates/scaffolds/tanstack-router/routes/_auth.tsx +6 -0
- package/templates/scaffolds/tanstack-router/routes/_error.tsx +0 -0
- package/templates/scaffolds/tanstack-router/routes/auth/login.tsx +6 -0
- package/templates/scaffolds/tanstack-router/routes/one/$id.tsx +191 -0
- package/templates/scaffolds/tanstack-router/routes/one/index.tsx +6 -0
- package/templates/scripts/setup.bat +284 -0
- package/templates/scripts/setup.ps1 +318 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export * as oneAPI from "./one.api";
|
|
2
|
+
/*
|
|
3
|
+
|
|
4
|
+
to call we simply do
|
|
5
|
+
|
|
6
|
+
import {oneAPI} from "@/api"
|
|
7
|
+
|
|
8
|
+
oneAPI.onePost({ payload: data });
|
|
9
|
+
|
|
10
|
+
oneAPI.oneGet();
|
|
11
|
+
|
|
12
|
+
oneAPI.oneGetId({ id: "1" });
|
|
13
|
+
|
|
14
|
+
* calling abort via signal
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
oneAPI.oneGetIdParams({ id: "1", params: { search:"adam" }, signal: controller.signal, });
|
|
17
|
+
|
|
18
|
+
controller.abort();
|
|
19
|
+
|
|
20
|
+
* aborts request on unmount
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
|
|
24
|
+
oneAPI.oneGetIdParams({
|
|
25
|
+
id: "1",
|
|
26
|
+
params: { search: "adam" },
|
|
27
|
+
signal: controller.signal,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return () => {
|
|
31
|
+
controller.abort();
|
|
32
|
+
};
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
oneAPI.onePut({ id: "1", payload: data });
|
|
37
|
+
|
|
38
|
+
oneAPI.oneDelete({ id: "1" });
|
|
39
|
+
|
|
40
|
+
*/
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ENDPOINTS } from "../endpoints";
|
|
2
|
+
import { CustomAxiosRequestConfig, request } from "../request";
|
|
3
|
+
|
|
4
|
+
type RequestOptions = Pick<
|
|
5
|
+
CustomAxiosRequestConfig,
|
|
6
|
+
"signal" | "skipErrorToast"
|
|
7
|
+
>;
|
|
8
|
+
|
|
9
|
+
export async function onePost(
|
|
10
|
+
{ payload }: { payload: any },
|
|
11
|
+
options?: RequestOptions,
|
|
12
|
+
) {
|
|
13
|
+
return await request({
|
|
14
|
+
url: ENDPOINTS.USER.BASE,
|
|
15
|
+
method: "POST",
|
|
16
|
+
data: payload,
|
|
17
|
+
...options,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function onePostFile(
|
|
22
|
+
{ payload }: { payload: any },
|
|
23
|
+
options?: RequestOptions,
|
|
24
|
+
) {
|
|
25
|
+
return await request({
|
|
26
|
+
...options,
|
|
27
|
+
url: ENDPOINTS.USER.BASE,
|
|
28
|
+
method: "POST",
|
|
29
|
+
data: payload,
|
|
30
|
+
headers: {
|
|
31
|
+
"Content-Type": "multipart/form-data",
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function oneGet(options?: RequestOptions) {
|
|
37
|
+
return await request({
|
|
38
|
+
url: ENDPOINTS.USER.BASE,
|
|
39
|
+
method: "GET",
|
|
40
|
+
...options,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function oneGetId(
|
|
45
|
+
{ id }: { id: string },
|
|
46
|
+
options?: RequestOptions,
|
|
47
|
+
) {
|
|
48
|
+
return await request({
|
|
49
|
+
url: ENDPOINTS.USER.BY_ID(id),
|
|
50
|
+
method: "GET",
|
|
51
|
+
...options,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function oneGetIdParams(
|
|
56
|
+
{
|
|
57
|
+
id,
|
|
58
|
+
params,
|
|
59
|
+
}: {
|
|
60
|
+
id: string;
|
|
61
|
+
params: any;
|
|
62
|
+
},
|
|
63
|
+
options?: RequestOptions,
|
|
64
|
+
) {
|
|
65
|
+
return await request({
|
|
66
|
+
url: ENDPOINTS.USER.BY_ID(id),
|
|
67
|
+
method: "GET",
|
|
68
|
+
params,
|
|
69
|
+
...options,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function onePut(
|
|
74
|
+
{ id, payload }: { id: string; payload: any },
|
|
75
|
+
options?: RequestOptions,
|
|
76
|
+
) {
|
|
77
|
+
return await request({
|
|
78
|
+
url: ENDPOINTS.USER.BY_ID(id),
|
|
79
|
+
method: "PUT",
|
|
80
|
+
data: payload,
|
|
81
|
+
...options,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function oneDelete(
|
|
86
|
+
{ id }: { id: string },
|
|
87
|
+
options?: RequestOptions,
|
|
88
|
+
) {
|
|
89
|
+
return await request({
|
|
90
|
+
url: ENDPOINTS.USER.BY_ID(id),
|
|
91
|
+
method: "DELETE",
|
|
92
|
+
...options,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
|
|
3
|
+
export const axiosInstance = axios.create({
|
|
4
|
+
baseURL: "http://localhost:5001",
|
|
5
|
+
timeout: 5000,
|
|
6
|
+
headers: {
|
|
7
|
+
"Content-Type": "application/json",
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
const api = axios.create({ timeout: 5000 });
|
|
13
|
+
This specific request uses a 30-second timeout instead
|
|
14
|
+
await api.get("/slow-endpoint", { timeout: 30000 });
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
TO integrate
|
|
19
|
+
1. Generic response typing (HIGH PRIORITY)
|
|
20
|
+
2. Error normalization utility
|
|
21
|
+
3. Network error utility
|
|
22
|
+
4. File upload helpers
|
|
23
|
+
5. File download helpers
|
|
24
|
+
6. Refresh-token queue
|
|
25
|
+
7. Monitoring hooks
|
|
26
|
+
*/
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { AxiosHeaders } from "axios";
|
|
2
|
+
|
|
3
|
+
import { axiosInstance } from ".";
|
|
4
|
+
|
|
5
|
+
let requestInterceptorId: number | null = null;
|
|
6
|
+
let responseInterceptorId: number | null = null;
|
|
7
|
+
|
|
8
|
+
export const setupInterceptors = ({
|
|
9
|
+
getAccessToken,
|
|
10
|
+
onUnauthorized,
|
|
11
|
+
}: {
|
|
12
|
+
getAccessToken: () => string;
|
|
13
|
+
onUnauthorized: () => void;
|
|
14
|
+
}) => {
|
|
15
|
+
if (requestInterceptorId !== null) {
|
|
16
|
+
axiosInstance.interceptors.request.eject(requestInterceptorId);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (responseInterceptorId !== null) {
|
|
20
|
+
axiosInstance.interceptors.response.eject(responseInterceptorId);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
requestInterceptorId = axiosInstance.interceptors.request.use(
|
|
24
|
+
(config) => {
|
|
25
|
+
const token = getAccessToken();
|
|
26
|
+
|
|
27
|
+
if (token) {
|
|
28
|
+
config.headers = AxiosHeaders.from(config.headers);
|
|
29
|
+
config.headers.set("Authorization", `Bearer ${token}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return config;
|
|
33
|
+
},
|
|
34
|
+
(error) => Promise.reject(error),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
responseInterceptorId = axiosInstance.interceptors.response.use(
|
|
38
|
+
(response) => response,
|
|
39
|
+
(error) => {
|
|
40
|
+
if (error?.response?.status === 401) {
|
|
41
|
+
onUnauthorized?.();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/*
|
|
45
|
+
const skipToast = error?.config?.skipErrorToast;
|
|
46
|
+
if (!skipToast) {
|
|
47
|
+
const message =
|
|
48
|
+
error?.response?.data?.message ||
|
|
49
|
+
error?.message || "Something went wrong";
|
|
50
|
+
toast.error(message);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
another method Handle only generic errors globally:
|
|
55
|
+
(error) => {
|
|
56
|
+
const status = error?.response?.status;
|
|
57
|
+
|
|
58
|
+
if (status >= 500) {
|
|
59
|
+
toast.error("Server error");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return Promise.reject(error);
|
|
63
|
+
}
|
|
64
|
+
And let components handle:
|
|
65
|
+
400 Validation Error
|
|
66
|
+
422 Form Error
|
|
67
|
+
409 Duplicate Record
|
|
68
|
+
because those errors are usually specific to the screen.
|
|
69
|
+
|
|
70
|
+
A practical setup is:
|
|
71
|
+
Interceptor:
|
|
72
|
+
401 → logout / redirect
|
|
73
|
+
500 → generic toast
|
|
74
|
+
|
|
75
|
+
Component:
|
|
76
|
+
400 → form validation
|
|
77
|
+
404 → custom UI
|
|
78
|
+
409 → business logic errors
|
|
79
|
+
|
|
80
|
+
This avoids flooding users with duplicate or overly generic error toasts while still preventing the UI from crashing.
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
return Promise.reject(error);
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/*
|
|
89
|
+
Call `setupInterceptors(...)` once from your app entry file or root provider,
|
|
90
|
+
before any API request runs.
|
|
91
|
+
|
|
92
|
+
Call setupInterceptors(...) once in your app bootstrap, like App.tsx, main.tsx, or a root provider. That is the real integration step.
|
|
93
|
+
|
|
94
|
+
Example:
|
|
95
|
+
import { setupInterceptors } from "./templates/axios/interceptors";
|
|
96
|
+
|
|
97
|
+
setupInterceptors({
|
|
98
|
+
getAccessToken: () => localStorage.getItem("token") ?? "",
|
|
99
|
+
onUnauthorized: () => {
|
|
100
|
+
// logout / redirect here
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
*/
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { AxiosRequestConfig, AxiosResponse } from "axios";
|
|
2
|
+
|
|
3
|
+
import { axiosInstance } from ".";
|
|
4
|
+
|
|
5
|
+
export interface CustomAxiosRequestConfig extends AxiosRequestConfig {
|
|
6
|
+
skipErrorToast?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const request = async ({
|
|
10
|
+
url,
|
|
11
|
+
method = "GET",
|
|
12
|
+
data,
|
|
13
|
+
params,
|
|
14
|
+
headers,
|
|
15
|
+
signal,
|
|
16
|
+
skipErrorToast,
|
|
17
|
+
...config
|
|
18
|
+
}: CustomAxiosRequestConfig): Promise<AxiosResponse> => {
|
|
19
|
+
const axiosConfig: CustomAxiosRequestConfig = {
|
|
20
|
+
...config,
|
|
21
|
+
url,
|
|
22
|
+
method,
|
|
23
|
+
data,
|
|
24
|
+
params,
|
|
25
|
+
headers,
|
|
26
|
+
signal,
|
|
27
|
+
skipErrorToast,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const response = await axiosInstance(axiosConfig as AxiosRequestConfig);
|
|
31
|
+
return response;
|
|
32
|
+
};
|
|
@@ -8,6 +8,10 @@ export function useRouterSearch<T extends Record<string, any>>({
|
|
|
8
8
|
}) {
|
|
9
9
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
10
10
|
|
|
11
|
+
/*
|
|
12
|
+
* Keep defaults in a ref so setSearch always reads the latest defaults without
|
|
13
|
+
* forcing callbacks to be recreated. Prefer passing a stable default object.
|
|
14
|
+
*/
|
|
11
15
|
const defaultRef = useRef(defaultParams);
|
|
12
16
|
defaultRef.current = defaultParams;
|
|
13
17
|
|
|
@@ -18,6 +22,7 @@ export function useRouterSearch<T extends Record<string, any>>({
|
|
|
18
22
|
for (const key of keys) {
|
|
19
23
|
const fallback = defaultRef.current[key];
|
|
20
24
|
if (Array.isArray(fallback)) {
|
|
25
|
+
// Repeated URL keys are parsed back into arrays, e.g. ?color=red&color=blue.
|
|
21
26
|
const val = searchParams.getAll(key);
|
|
22
27
|
if (val.length > 0) {
|
|
23
28
|
result[key as keyof T] = parseValue(val, fallback) as any;
|
|
@@ -43,9 +48,11 @@ export function useRouterSearch<T extends Record<string, any>>({
|
|
|
43
48
|
for (const key of keys) {
|
|
44
49
|
const value = updates[key as keyof T];
|
|
45
50
|
if (value !== undefined) {
|
|
51
|
+
// null or the default value removes the key, keeping URLs shareable and minimal.
|
|
46
52
|
if (value === null || areEqual(value, defaultRef.current[key])) {
|
|
47
53
|
next.delete(key);
|
|
48
54
|
} else if (Array.isArray(value)) {
|
|
55
|
+
// Delete first so array updates replace previous repeated keys instead of appending to them.
|
|
49
56
|
next.delete(key);
|
|
50
57
|
value.forEach((val) => {
|
|
51
58
|
if (val !== undefined && val !== null && val !== "") {
|
|
@@ -78,6 +85,7 @@ export function useRouterSearch<T extends Record<string, any>>({
|
|
|
78
85
|
function areEqual(a: any, b: any) {
|
|
79
86
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
80
87
|
if (a.length !== b.length) return false;
|
|
88
|
+
// Treat array query params as order-insensitive filter sets.
|
|
81
89
|
const sortedA = [...a].sort();
|
|
82
90
|
const sortedB = [...b].sort();
|
|
83
91
|
return sortedA.every((val, index) => val === sortedB[index]);
|
|
@@ -46,6 +46,7 @@ export default function LoginPage() {
|
|
|
46
46
|
|
|
47
47
|
const handleLoginSuccess = () => {
|
|
48
48
|
// 1. Read the redirectTo value (e.g., "/page_two") or default to "/"
|
|
49
|
+
// In production, validate this stays an internal path before navigating.
|
|
49
50
|
const destination = searchParams.get("redirectTo") || "/";
|
|
50
51
|
|
|
51
52
|
// 2. Redirect the user back to their bookmarked page instead of the root page!
|
|
@@ -8,6 +8,10 @@ import { MainLayout } from "./layouts/main.layout";
|
|
|
8
8
|
|
|
9
9
|
const PublicRoutes: RouteObject[] = [
|
|
10
10
|
{
|
|
11
|
+
/*
|
|
12
|
+
# NOTE: Guard routes without a path run before their children.
|
|
13
|
+
This keeps auth decisions centralized while the nested layout controls shared UI.
|
|
14
|
+
*/
|
|
11
15
|
Component: PublicRoute,
|
|
12
16
|
children: [
|
|
13
17
|
{
|
|
@@ -29,6 +33,10 @@ const PublicRoutes: RouteObject[] = [
|
|
|
29
33
|
|
|
30
34
|
const PrivateRoutes: RouteObject[] = [
|
|
31
35
|
{
|
|
36
|
+
/*
|
|
37
|
+
# NOTE: PrivateRoute can block or redirect every route below it.
|
|
38
|
+
Any child route added here automatically inherits the same auth requirement.
|
|
39
|
+
*/
|
|
32
40
|
Component: PrivateRoute,
|
|
33
41
|
children: [
|
|
34
42
|
{
|
|
@@ -78,6 +86,7 @@ const NotFoundRoutes: RouteObject[] = [
|
|
|
78
86
|
export const router = createBrowserRouter([
|
|
79
87
|
...PublicRoutes,
|
|
80
88
|
...PrivateRoutes,
|
|
89
|
+
// Keep the wildcard route last so specific public/private routes get matched first.
|
|
81
90
|
...NotFoundRoutes,
|
|
82
91
|
]);
|
|
83
92
|
|
|
@@ -181,6 +190,23 @@ export function useDocumentTitle(title: string) {
|
|
|
181
190
|
2. **Why we use Component-level skeletons:**
|
|
182
191
|
- **Data Loading:** Once the page code is downloaded and mounted, your API data fetching starts.
|
|
183
192
|
- **Interactive UI:** This is where you render custom skeleton loaders inside your page components while waiting for TanStack Query data, offering a premium and localized loading layout.
|
|
193
|
+
|
|
194
|
+
-------------------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
{
|
|
197
|
+
path: PATH.loads(),
|
|
198
|
+
handle: {
|
|
199
|
+
title: PAGE_TITLE.loads,
|
|
200
|
+
},
|
|
201
|
+
Component: LoadsPage,
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
import { useMatches } from "react-router";
|
|
205
|
+
|
|
206
|
+
const matches = useMatches();
|
|
207
|
+
|
|
208
|
+
const currentTitle =
|
|
209
|
+
matches[matches.length - 1]?.handle?.title ?? "";
|
|
184
210
|
*/
|
|
185
211
|
|
|
186
212
|
|
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { combineReducers } from "@reduxjs/toolkit";
|
|
2
|
+
import oneReducer from "./slices/one.slice";
|
|
3
|
+
import twoReducer from "./slices/two.slice";
|
|
4
|
+
import { persistReducer } from "redux-persist";
|
|
5
|
+
import storageLocal from "redux-persist/lib/storage";
|
|
6
|
+
/* import storageSession from "redux-persist/lib/storage/session"; */
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
|
|
10
|
+
# NOTE : combineReducers is used to combine multiple reducers into a single reducer
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/* this is per slice config you have to define multiple if you have multi-slice persisted reducers */
|
|
14
|
+
const twoConfig = {
|
|
15
|
+
key: "two",
|
|
16
|
+
storage: storageLocal,
|
|
17
|
+
whitelist: [],
|
|
18
|
+
blacklist: [],
|
|
19
|
+
};
|
|
20
|
+
const persistedReducers = {
|
|
21
|
+
two: persistReducer(twoConfig, twoReducer),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const reducres = { one: oneReducer };
|
|
25
|
+
export const rootReducer = combineReducers({
|
|
26
|
+
...reducres,
|
|
27
|
+
...persistedReducers,
|
|
28
|
+
});
|
|
29
|
+
export type RootReducerState = ReturnType<typeof rootReducer>;
|
|
30
|
+
export default rootReducer;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createSelector } from "@reduxjs/toolkit";
|
|
2
|
+
import { RootState } from "../index";
|
|
3
|
+
|
|
4
|
+
export const selectOneSlice = (state: RootState) => state.one;
|
|
5
|
+
export const oneSelector = createSelector([selectOneSlice], (state) => {
|
|
6
|
+
if (state.oneData) {
|
|
7
|
+
return state.oneData;
|
|
8
|
+
} else {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
|
|
15
|
+
# NOTE : Its primary job is:
|
|
16
|
+
1. Memoize expensive calculations
|
|
17
|
+
2. Return stable references (same array/object if inputs didn't change)
|
|
18
|
+
3. Prevent unnecessary recalculations and re-renders
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
now after this setp of selctor
|
|
22
|
+
we can handover all the states and memoised data to hook which can be called inside any number of component
|
|
23
|
+
it will save our time and code from repeating the code again and again
|
|
24
|
+
|
|
25
|
+
# NOTE : example
|
|
26
|
+
|
|
27
|
+
// hooks/useOne.ts
|
|
28
|
+
import { useSelector } from "react-redux";
|
|
29
|
+
import { selectOneSlice } from "../store/selector/one.selector";
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
export const useOne = () => {
|
|
33
|
+
const one = useSelector(selectOneSlice);
|
|
34
|
+
const { isLoading, isFetching, isError } = useSelector((state) => state.one);
|
|
35
|
+
any additional manipulation specific to component can be done here
|
|
36
|
+
return { one, isLoading, isFetching, isError };
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
usage in any component
|
|
40
|
+
import { useOne } from "../hooks/useOne";
|
|
41
|
+
const { one, isLoading, isFetching, isError } = useOne();
|
|
42
|
+
|
|
43
|
+
*/
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createSelector } from "@reduxjs/toolkit";
|
|
2
|
+
import { RootState } from "../index";
|
|
3
|
+
|
|
4
|
+
export const selectTwoSlice = (state: RootState) => state.two;
|
|
5
|
+
export const twoSelector = createSelector([selectTwoSlice], (state) => {
|
|
6
|
+
if (state.twoData) {
|
|
7
|
+
return state.twoData;
|
|
8
|
+
} else {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
});
|