pejay-ui 1.2.2 → 1.3.1
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/README.md +17 -2
- package/package.json +2 -2
- 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,202 @@
|
|
|
1
|
+
import { createSlice } from "@reduxjs/toolkit";
|
|
2
|
+
|
|
3
|
+
const initialState = {
|
|
4
|
+
/*
|
|
5
|
+
status: "idle" | "loading" | "error" | "success",
|
|
6
|
+
*/
|
|
7
|
+
oneData: "",
|
|
8
|
+
error: "",
|
|
9
|
+
status: "idle",
|
|
10
|
+
isLoading: false,
|
|
11
|
+
isError: false,
|
|
12
|
+
meta: {
|
|
13
|
+
requiresAuth: true,
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
|
|
19
|
+
import { createAsyncThunk } from "@reduxjs/toolkit";
|
|
20
|
+
|
|
21
|
+
# NOTE : it can also have asyncThunks to handle async logic like api calls
|
|
22
|
+
|
|
23
|
+
export const oneAsync = createAsyncThunk(
|
|
24
|
+
"one/oneAsync",
|
|
25
|
+
async (_ ,{rejectWithValue,signal}) => {
|
|
26
|
+
try {
|
|
27
|
+
const response = await axios.get("backend_api_route",{signal});
|
|
28
|
+
return response.data;
|
|
29
|
+
} catch (error:any) {
|
|
30
|
+
return rejectWithValue(error.response.data);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
----------------------------------------------------------------
|
|
36
|
+
(method 1)
|
|
37
|
+
# NOTE : and use it
|
|
38
|
+
|
|
39
|
+
const dispatch = useAppDispatch();
|
|
40
|
+
|
|
41
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
42
|
+
const handleClick = async () => {
|
|
43
|
+
try {
|
|
44
|
+
setIsLoading(true);
|
|
45
|
+
const data = await dispatch(oneAsync()).unwrap();
|
|
46
|
+
console.log(data);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.log(error);
|
|
49
|
+
}finally{
|
|
50
|
+
setIsLoading(false);
|
|
51
|
+
dispatch(setStatus("idle"));
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
----------------------------------------------------------------
|
|
59
|
+
(method 2)
|
|
60
|
+
# NOTE : other way to call it by using the slice state variable to store data in that and call from there
|
|
61
|
+
|
|
62
|
+
in this case we call it via useSelector to get the data
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
dispatch(oneAsync());
|
|
66
|
+
}, [dispatch]);
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
-----------------------------------------------------------------
|
|
70
|
+
# NOTE :
|
|
71
|
+
|
|
72
|
+
const {status,isloading,isError} = useOne();
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if(status === "idle"){
|
|
76
|
+
dispatch(oneAsync());
|
|
77
|
+
}
|
|
78
|
+
}, [dispatch]);
|
|
79
|
+
|
|
80
|
+
in this either i manlually make status idel again to dispatch
|
|
81
|
+
|
|
82
|
+
or
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
dispatch(fetchUsers(page));
|
|
86
|
+
}, [page, dispatch]);
|
|
87
|
+
|
|
88
|
+
use page for some kind of trigger that keep preventing from unwanted api calls
|
|
89
|
+
|
|
90
|
+
only a page reload or setting the status manually to idle will make the api call again
|
|
91
|
+
|
|
92
|
+
# NOTE : this above can be also done in (method 1) of calling by removing useState or we can make useState for api loading/error/status etc but that is messy and cannot be handel outside comp or doing it inside hook is also not good as each hook call is treated as new instance so best way is to either make inital states and handel via reducers custom ones or use extra reducers which is the best way to handel this. only one thing we can do is spread our manipulation data across 3/4 different steps
|
|
93
|
+
1. inside fullfill extra reducer (for heavy manipulation)
|
|
94
|
+
2. inside selector (memoised data can do some manipulation )
|
|
95
|
+
3. inside hook (optional or for component specifics extraction or manipulation)
|
|
96
|
+
4. inside component (optional for specific element or ui level requirement)
|
|
97
|
+
|
|
98
|
+
there is another way of data flow
|
|
99
|
+
|
|
100
|
+
1. call api , do heavy manipulation in fullfilled state, store data in state
|
|
101
|
+
2. call data in hook (for comp based manipulation further can use useMemo here to avoid unwanted processing of same data)
|
|
102
|
+
3. call in component (if required do further manipulation )
|
|
103
|
+
|
|
104
|
+
useMemo only preserve manipulated data to save manipulation from rerendering of the component itself not unmount / mount of comp
|
|
105
|
+
|
|
106
|
+
in mounting useMemo / hook will lose instances and cached data is destroyed
|
|
107
|
+
|
|
108
|
+
When createSelector is useful
|
|
109
|
+
When you want to keep raw data in Redux.
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
const oneSlice = createSlice({
|
|
113
|
+
name: "one",
|
|
114
|
+
initialState,
|
|
115
|
+
|
|
116
|
+
// Manually handling async logic via reducers
|
|
117
|
+
reducers: {
|
|
118
|
+
setOne: (state, action) => {
|
|
119
|
+
state.oneData = action.payload;
|
|
120
|
+
},
|
|
121
|
+
setStatus: (state, action) => {
|
|
122
|
+
state.status = action.payload;
|
|
123
|
+
},
|
|
124
|
+
setError: (state, action) => {
|
|
125
|
+
state.isError = true;
|
|
126
|
+
state.error = action.payload;
|
|
127
|
+
state.status = "error";
|
|
128
|
+
},
|
|
129
|
+
setIsLoading: (state, action) => {
|
|
130
|
+
state.isLoading = action.payload;
|
|
131
|
+
state.status = "pending";
|
|
132
|
+
state.isError = false;
|
|
133
|
+
state.error = "";
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
/*
|
|
137
|
+
|
|
138
|
+
there is 2 ways to make a async call in redux
|
|
139
|
+
|
|
140
|
+
1. Manually handling async logic via reducers
|
|
141
|
+
2. Using extraReducers
|
|
142
|
+
|
|
143
|
+
usually we use extra reducers because is easy what more we can use is use status state for preventing unwnted api calls
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# NOTE : extraReducers are used to handle async logic like api calls
|
|
148
|
+
extrareducers : {
|
|
149
|
+
builder
|
|
150
|
+
.addCase(oneAsync.pending, (state) => {
|
|
151
|
+
state.isLoading = true;
|
|
152
|
+
})
|
|
153
|
+
.addCase(oneAsync.fulfilled, (state, action) => {
|
|
154
|
+
state.isLoading = false;
|
|
155
|
+
const processedData = action.payload;
|
|
156
|
+
// here manipulates data accordingly and handover to selector.
|
|
157
|
+
state.oneData = processedData;
|
|
158
|
+
state.status = "idle";
|
|
159
|
+
})
|
|
160
|
+
.addCase(oneAsync.rejected, (state, action) => {
|
|
161
|
+
state.isLoading = false;
|
|
162
|
+
state.isError = true;
|
|
163
|
+
state.error = action.payload;
|
|
164
|
+
state.status = "error";
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
*/
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
export const { setOne } = oneSlice.actions;
|
|
173
|
+
export default oneSlice.reducer;
|
|
174
|
+
|
|
175
|
+
/*
|
|
176
|
+
|
|
177
|
+
a slice has state, reducers, and actions.
|
|
178
|
+
a state => data
|
|
179
|
+
a reducers => logic on how data changes / basically data manipulation
|
|
180
|
+
a actions => dispatchers to change the data
|
|
181
|
+
|
|
182
|
+
------------------------------------------------------------
|
|
183
|
+
# NOTE : to dispatch an action
|
|
184
|
+
|
|
185
|
+
import { useDispatch } from "react-redux";
|
|
186
|
+
import { setOne } from "../slices/one.slice";
|
|
187
|
+
const dispatch = useDispatch();
|
|
188
|
+
dispatch(setOne("hello"));
|
|
189
|
+
|
|
190
|
+
its basically calling the function defined in reducers with the data
|
|
191
|
+
|
|
192
|
+
------------------------------------------------------------
|
|
193
|
+
# NOTE : to call a state to get the data in comp we
|
|
194
|
+
|
|
195
|
+
import { useSelector } from "react-redux";
|
|
196
|
+
import { type RootState } from "../index";
|
|
197
|
+
const one = useSelector((state: RootState) => state.one.oneData);
|
|
198
|
+
|
|
199
|
+
this is basically accessing the value from the store.
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
*/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createSlice } from "@reduxjs/toolkit";
|
|
2
|
+
|
|
3
|
+
const initialState = {
|
|
4
|
+
twoData: "",
|
|
5
|
+
meta: {
|
|
6
|
+
requiresAuth: true,
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const twoSlice = createSlice({
|
|
11
|
+
name: "two",
|
|
12
|
+
initialState,
|
|
13
|
+
reducers: {
|
|
14
|
+
setTwo: (state, action) => {
|
|
15
|
+
state.twoData = action.payload;
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const { setTwo } = twoSlice.actions;
|
|
21
|
+
export default twoSlice.reducer;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { configureStore } from "@reduxjs/toolkit";
|
|
2
|
+
import {
|
|
3
|
+
persistStore,
|
|
4
|
+
FLUSH,
|
|
5
|
+
REHYDRATE,
|
|
6
|
+
PAUSE,
|
|
7
|
+
PERSIST,
|
|
8
|
+
PURGE,
|
|
9
|
+
REGISTER,
|
|
10
|
+
} from "redux-persist";
|
|
11
|
+
import rootReducer from "./reducers";
|
|
12
|
+
|
|
13
|
+
export const store = configureStore({
|
|
14
|
+
reducer: rootReducer,
|
|
15
|
+
middleware: (getDefaultMiddleware) =>
|
|
16
|
+
getDefaultMiddleware({
|
|
17
|
+
serializableCheck: {
|
|
18
|
+
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
|
|
19
|
+
},
|
|
20
|
+
}) /* .concat(middleware_name) */,
|
|
21
|
+
});
|
|
22
|
+
export const persistor = persistStore(store);
|
|
23
|
+
export type RootState = ReturnType<typeof store.getState>;
|
|
24
|
+
export type AppDispatch = typeof store.dispatch;
|
|
25
|
+
export default store;
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
|
|
29
|
+
Example usage:
|
|
30
|
+
|
|
31
|
+
import { Provider } from "react-redux";
|
|
32
|
+
import { store } from "./store";
|
|
33
|
+
<Provider store={store}>
|
|
34
|
+
<App />
|
|
35
|
+
</Provider>
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
*/
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createApi } from "@reduxjs/toolkit/query/react";
|
|
2
|
+
import { baseQuery } from "./baseQuery";
|
|
3
|
+
import { queryTags } from "./queryTags";
|
|
4
|
+
export const baseApi = createApi({
|
|
5
|
+
reducerPath: "baseApi",
|
|
6
|
+
baseQuery: baseQuery,
|
|
7
|
+
tagTypes: queryTags,
|
|
8
|
+
/*
|
|
9
|
+
keepUnusedDataFor: Infinity //data will never be garbage collected
|
|
10
|
+
refetchOnReconnect: true, //refetch on network reconnect
|
|
11
|
+
refetchOnMountOrArgChange: false, //refetch on mount or when args change
|
|
12
|
+
refetchOnFocus: false, //refetch on focus to tab
|
|
13
|
+
*/
|
|
14
|
+
endpoints: () => ({}),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
|
|
19
|
+
* in reducers.ts of redux store
|
|
20
|
+
const apiReducers = {
|
|
21
|
+
[baseApi.reducerPath]: baseApi.reducer,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
*/
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { fetchBaseQuery } from "@reduxjs/toolkit/query";
|
|
2
|
+
|
|
3
|
+
export const baseQuery = fetchBaseQuery({
|
|
4
|
+
baseUrl: "http://localhost:5001",
|
|
5
|
+
prepareHeaders: (headers) => {
|
|
6
|
+
const token = localStorage.getItem("token");
|
|
7
|
+
if (token) {
|
|
8
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
9
|
+
}
|
|
10
|
+
return headers;
|
|
11
|
+
},
|
|
12
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { baseApi } from "../baseApi";
|
|
2
|
+
import { invalidateTagIdOnSuccess } from "../queryTags";
|
|
3
|
+
|
|
4
|
+
export const apiOne = baseApi.injectEndpoints({
|
|
5
|
+
endpoints: (builder) => ({
|
|
6
|
+
// very basic fetch function
|
|
7
|
+
fetchData: builder.query({
|
|
8
|
+
query: () => "/data",
|
|
9
|
+
providesTags: [],
|
|
10
|
+
}),
|
|
11
|
+
// with params method 1
|
|
12
|
+
fetchData2: builder.query({
|
|
13
|
+
query: ({ id }) => `/data/${id}`,
|
|
14
|
+
providesTags: (_result, error, { id }) =>
|
|
15
|
+
error ? [] : [{ type: "Data", id }],
|
|
16
|
+
}),
|
|
17
|
+
// with params method 2
|
|
18
|
+
fetchData3: builder.query({
|
|
19
|
+
query: ({ id, name }) => ({
|
|
20
|
+
url: `/data`,
|
|
21
|
+
method: "GET",
|
|
22
|
+
params: { id, name },
|
|
23
|
+
}),
|
|
24
|
+
// manipulate response before it is sent to the component
|
|
25
|
+
transformResponse: (response) => {
|
|
26
|
+
/* manipulate here */
|
|
27
|
+
return response.data;
|
|
28
|
+
},
|
|
29
|
+
providesTags: [],
|
|
30
|
+
}),
|
|
31
|
+
|
|
32
|
+
/* =================================== */
|
|
33
|
+
|
|
34
|
+
postData: builder.mutation({
|
|
35
|
+
query: (body) => ({
|
|
36
|
+
url: "/data",
|
|
37
|
+
method: "POST",
|
|
38
|
+
body,
|
|
39
|
+
}),
|
|
40
|
+
invalidatesTags: invalidateTagIdOnSuccess("Data"),
|
|
41
|
+
}),
|
|
42
|
+
}),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const {
|
|
46
|
+
useFetchDataQuery,
|
|
47
|
+
useFetchData2Query,
|
|
48
|
+
useFetchData3Query,
|
|
49
|
+
usePostDataMutation,
|
|
50
|
+
} = apiOne;
|
|
51
|
+
|
|
52
|
+
/*
|
|
53
|
+
|
|
54
|
+
? Query Calls
|
|
55
|
+
|
|
56
|
+
*call in components
|
|
57
|
+
const { data, isLoading, isFetching, isError, error } = useFetchDataQuery();
|
|
58
|
+
|
|
59
|
+
*refetch every 5 seconds
|
|
60
|
+
const { data } = useFetchDataQuery({},{
|
|
61
|
+
pollingInterval: 5000,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
*get specific data from array of objects
|
|
65
|
+
const { user } = useFetchDataQuery({},{
|
|
66
|
+
selectFromResult: ({ data }) => ({
|
|
67
|
+
user: data?.find((u) => u.id === 2),
|
|
68
|
+
}),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
// ==================================== //
|
|
73
|
+
|
|
74
|
+
? MUTATIONS
|
|
75
|
+
|
|
76
|
+
* call in components
|
|
77
|
+
const [postData] = usePostDataMutation();
|
|
78
|
+
await postData({ name: "John",});
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./api.one";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const queryTags = ["Data"] as const;
|
|
2
|
+
|
|
3
|
+
type TagType = (typeof queryTags)[number];
|
|
4
|
+
|
|
5
|
+
/** Invalidate tag ids only when the mutation succeeds (skip on error). */
|
|
6
|
+
export function invalidateTagIdOnSuccess(...types: TagType[]) {
|
|
7
|
+
return (
|
|
8
|
+
_result: unknown,
|
|
9
|
+
error: unknown,
|
|
10
|
+
arg: { id: string | number },
|
|
11
|
+
) =>
|
|
12
|
+
error ? [] : types.map((type) => ({ type, id: arg.id }));
|
|
13
|
+
}
|
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
/*
|
|
2
|
-
#ANCHOR : TYPE:1 VITE PROJECT
|
|
3
|
-
const BASE_URL = import.meta.env.VITE_API_URL;
|
|
4
|
-
|
|
5
|
-
Environment variables must start with VITE_ to be exposed to client-side code in Vite.
|
|
6
|
-
|
|
7
|
-
---------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
#ANCHOR : TYPE:2 NEXT.JS / NON-VITE PROJECTS
|
|
10
|
-
const BASE_URL = process.env.NEXT_PUBLIC_API_URL;
|
|
11
|
-
|
|
12
|
-
Environment variables must start with NEXT_PUBLIC_ to be available in Next.js browser code.
|
|
13
|
-
|
|
14
|
-
---------------------------------------------------------
|
|
15
|
-
#ANCHOR
|
|
16
|
-
Vite doesn't automatically provide Node's process.env in browser code.
|
|
17
|
-
|
|
18
|
-
| Framework | Access Method | Public Prefix |
|
|
19
|
-
| --------------- | ------------------- | ---------------- |
|
|
20
|
-
| Next.js | `process.env.X` | `NEXT_PUBLIC_` |
|
|
21
|
-
| Vite | `import.meta.env.X` | `VITE_` |
|
|
22
|
-
| Node.js Backend | `process.env.X` | No prefix needed |
|
|
23
|
-
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
export const API_CONFIG = {
|
|
27
|
-
// NOTE: Change this BASE_URL to match your actual API endpoint
|
|
28
|
-
baseUrl: "http://localhost:5000/api",
|
|
29
|
-
timeout: 10000,
|
|
30
|
-
headers: {
|
|
31
|
-
"Accept": "application/json",
|
|
32
|
-
"Content-Type": "application/json",
|
|
33
|
-
},
|
|
34
|
-
} as const;
|
|
35
|
-
|
|
36
|
-
export const QUERY_CLIENT_CONFIG = {
|
|
37
|
-
defaultOptions: {
|
|
38
|
-
queries: {
|
|
39
|
-
// # NOTE: Global TanStack Query configurations
|
|
40
|
-
retry: 3, // Automatically retries failed requests 3 times on failure
|
|
41
|
-
refetchOnWindowFocus: false, // Refetches stale active queries when the browser tab gets focused
|
|
42
|
-
staleTime: 1000 * 60 * 5, // Data is considered fresh for 5 minutes (prevents duplicate requests)
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
} as const;
|
|
46
|
-
|
|
47
|
-
/*
|
|
48
|
-
# NOTE: HOW AND WHERE TO USE QUERY_CLIENT_CONFIG
|
|
49
|
-
|
|
50
|
-
This configuration is imported and passed when initializing the QueryClient at the root of your application (e.g., in App.tsx, main.tsx, or layout.tsx):
|
|
51
|
-
|
|
52
|
-
```typescript
|
|
53
|
-
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
54
|
-
import { QUERY_CLIENT_CONFIG } from "./api-base";
|
|
55
|
-
|
|
56
|
-
const queryClient = new QueryClient(QUERY_CLIENT_CONFIG);
|
|
57
|
-
|
|
58
|
-
export default function Providers({ children }) {
|
|
59
|
-
return (
|
|
60
|
-
<QueryClientProvider client={queryClient}>
|
|
61
|
-
{children}
|
|
62
|
-
</QueryClientProvider>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
```
|
|
66
|
-
*/
|
|
67
|
-
|
|
68
|
-
|
|
1
|
+
/*
|
|
2
|
+
#ANCHOR : TYPE:1 VITE PROJECT
|
|
3
|
+
const BASE_URL = import.meta.env.VITE_API_URL;
|
|
4
|
+
|
|
5
|
+
Environment variables must start with VITE_ to be exposed to client-side code in Vite.
|
|
6
|
+
|
|
7
|
+
---------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
#ANCHOR : TYPE:2 NEXT.JS / NON-VITE PROJECTS
|
|
10
|
+
const BASE_URL = process.env.NEXT_PUBLIC_API_URL;
|
|
11
|
+
|
|
12
|
+
Environment variables must start with NEXT_PUBLIC_ to be available in Next.js browser code.
|
|
13
|
+
|
|
14
|
+
---------------------------------------------------------
|
|
15
|
+
#ANCHOR
|
|
16
|
+
Vite doesn't automatically provide Node's process.env in browser code.
|
|
17
|
+
|
|
18
|
+
| Framework | Access Method | Public Prefix |
|
|
19
|
+
| --------------- | ------------------- | ---------------- |
|
|
20
|
+
| Next.js | `process.env.X` | `NEXT_PUBLIC_` |
|
|
21
|
+
| Vite | `import.meta.env.X` | `VITE_` |
|
|
22
|
+
| Node.js Backend | `process.env.X` | No prefix needed |
|
|
23
|
+
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export const API_CONFIG = {
|
|
27
|
+
// NOTE: Change this BASE_URL to match your actual API endpoint
|
|
28
|
+
baseUrl: "http://localhost:5000/api",
|
|
29
|
+
timeout: 10000,
|
|
30
|
+
headers: {
|
|
31
|
+
"Accept": "application/json",
|
|
32
|
+
"Content-Type": "application/json",
|
|
33
|
+
},
|
|
34
|
+
} as const;
|
|
35
|
+
|
|
36
|
+
export const QUERY_CLIENT_CONFIG = {
|
|
37
|
+
defaultOptions: {
|
|
38
|
+
queries: {
|
|
39
|
+
// # NOTE: Global TanStack Query configurations
|
|
40
|
+
retry: 3, // Automatically retries failed requests 3 times on failure
|
|
41
|
+
refetchOnWindowFocus: false, // Refetches stale active queries when the browser tab gets focused
|
|
42
|
+
staleTime: 1000 * 60 * 5, // Data is considered fresh for 5 minutes (prevents duplicate requests)
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
} as const;
|
|
46
|
+
|
|
47
|
+
/*
|
|
48
|
+
# NOTE: HOW AND WHERE TO USE QUERY_CLIENT_CONFIG
|
|
49
|
+
|
|
50
|
+
This configuration is imported and passed when initializing the QueryClient at the root of your application (e.g., in App.tsx, main.tsx, or layout.tsx):
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
54
|
+
import { QUERY_CLIENT_CONFIG } from "./api-base";
|
|
55
|
+
|
|
56
|
+
const queryClient = new QueryClient(QUERY_CLIENT_CONFIG);
|
|
57
|
+
|
|
58
|
+
export default function Providers({ children }) {
|
|
59
|
+
return (
|
|
60
|
+
<QueryClientProvider client={queryClient}>
|
|
61
|
+
{children}
|
|
62
|
+
</QueryClientProvider>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
|
|
@@ -58,28 +58,9 @@ return <ModuleComponent data={data} />;
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
# NOTE : Wrapping
|
|
61
|
-
<ErrorBoundary fallback={<div>Failed to load Stats</div>}>
|
|
62
61
|
<Suspense fallback={<FallBackComponent />}>
|
|
63
62
|
<ModuleComponent />
|
|
64
63
|
</Suspense>
|
|
65
|
-
</ErrorBoundary>
|
|
66
|
-
|
|
67
|
-
this is comp level error boundary in case of api fails then suspense bubbeling up will touch this first and stops
|
|
68
|
-
in case this is not present then the router level
|
|
69
|
-
so in this way we can only show a particaular comp failed to load not entore page
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
Manually (Option A - useQuery):
|
|
76
|
-
|
|
77
|
-
Code: if (error) return <ErrorComponent />
|
|
78
|
-
Behavior: Bypasses Error Boundaries. Component-level and router-level boundaries never trigger.
|
|
79
|
-
Automatically (Option B - useSuspenseQuery or useQuery with throwOnError: true):
|
|
80
|
-
|
|
81
|
-
Code: Component throws the error during render.
|
|
82
|
-
Behavior: The error bubbles up. It triggers the nearest Error Boundary (either your component-level boundary if you wrapped it, or falls back to the router-level boundary).
|
|
83
64
|
|
|
84
65
|
---------------------------------------------------------------------------------------------------
|
|
85
66
|
|
|
@@ -9,6 +9,10 @@ export async function apiRequest<T>(
|
|
|
9
9
|
// const token = typeof window !== "undefined" ? localStorage.getItem("token") : null;
|
|
10
10
|
const token = null;
|
|
11
11
|
|
|
12
|
+
/*
|
|
13
|
+
# NOTE: RequestInit options are spread first so each call can pass signal, method, body, or headers.
|
|
14
|
+
The final headers spread lets a specific request override defaults when an endpoint needs it.
|
|
15
|
+
*/
|
|
12
16
|
const response = await fetch(`${API_CONFIG.baseUrl}${endpoint}`, {
|
|
13
17
|
...options,
|
|
14
18
|
headers: {
|
|
@@ -18,6 +22,10 @@ export async function apiRequest<T>(
|
|
|
18
22
|
},
|
|
19
23
|
});
|
|
20
24
|
|
|
25
|
+
/*
|
|
26
|
+
# NOTE: This template assumes every successful response returns JSON.
|
|
27
|
+
If your API uses 204 No Content or empty delete responses, guard response.status before calling json().
|
|
28
|
+
*/
|
|
21
29
|
if (!response.ok) throw new Error(`Error Code ${response.status}`);
|
|
22
30
|
return (await response.json()) as T;
|
|
23
31
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
export { ModuleKeys } from "./keys";
|
|
2
|
-
export { ModuleMappers } from "./mappers";
|
|
3
|
-
export { ModuleService } from "./services";
|
|
4
|
-
export { ModuleQueries } from "./queries";
|
|
5
|
-
export { ModuleMutations } from "./mutations";
|
|
6
|
-
|
|
7
|
-
/*
|
|
8
|
-
# NOTE: here we can export types.ts as well if we defining types seperately in a file
|
|
9
|
-
export type * from "./types";
|
|
10
|
-
# NOTE : we can also export a mocks.ts file if we using mocks which contains dummy / fall back data
|
|
11
|
-
export { ModuleMocks } from "./mocks";
|
|
12
|
-
*/
|
|
1
|
+
export { ModuleKeys } from "./keys";
|
|
2
|
+
export { ModuleMappers } from "./mappers";
|
|
3
|
+
export { ModuleService } from "./services";
|
|
4
|
+
export { ModuleQueries } from "./queries";
|
|
5
|
+
export { ModuleMutations } from "./mutations";
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
# NOTE: here we can export types.ts as well if we defining types seperately in a file
|
|
9
|
+
export type * from "./types";
|
|
10
|
+
# NOTE : we can also export a mocks.ts file if we using mocks which contains dummy / fall back data
|
|
11
|
+
export { ModuleMocks } from "./mocks";
|
|
12
|
+
*/
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
export const ModuleKeys = {
|
|
2
|
-
module: () => ["moduleName"] as const,
|
|
3
|
-
fetch_query_name_example: () => [...ModuleKeys.module(), "fetch"] as const,
|
|
4
|
-
create_query_name_example: () => [...ModuleKeys.module(), "create"] as const,
|
|
5
|
-
update_query_name_example: () => [...ModuleKeys.module(), "update"] as const,
|
|
6
|
-
delete_query_name_example: () => [...ModuleKeys.module(), "delete"] as const,
|
|
7
|
-
fetch_infinite_query_name_example: () => [...ModuleKeys.module(), "fetch-infinite"] as const,
|
|
8
|
-
fetch_query_with_params_example: (params: any) => [...ModuleKeys.module(), "fetch-with-params", params] as const,
|
|
9
|
-
fetch_query_by_id_example: (id: string) => [...ModuleKeys.module(), "fetch-by-id", id] as const,
|
|
10
|
-
fetch_query_combo_example: (id: string, params: any) => [...ModuleKeys.module(), "fetch-combo", id, params] as const,
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
/*
|
|
14
|
-
# NOTE: as const is used at end so that keys are immutable
|
|
15
|
-
# NOTE : ans insted of standard object keys usd as function so its easy and clean to use and maintain in larger scale
|
|
16
|
-
# NOTE : you can change *_name_example to your own query name
|
|
17
|
-
*/
|
|
1
|
+
export const ModuleKeys = {
|
|
2
|
+
module: () => ["moduleName"] as const,
|
|
3
|
+
fetch_query_name_example: () => [...ModuleKeys.module(), "fetch"] as const,
|
|
4
|
+
create_query_name_example: () => [...ModuleKeys.module(), "create"] as const,
|
|
5
|
+
update_query_name_example: () => [...ModuleKeys.module(), "update"] as const,
|
|
6
|
+
delete_query_name_example: () => [...ModuleKeys.module(), "delete"] as const,
|
|
7
|
+
fetch_infinite_query_name_example: () => [...ModuleKeys.module(), "fetch-infinite"] as const,
|
|
8
|
+
fetch_query_with_params_example: (params: any) => [...ModuleKeys.module(), "fetch-with-params", params] as const,
|
|
9
|
+
fetch_query_by_id_example: (id: string) => [...ModuleKeys.module(), "fetch-by-id", id] as const,
|
|
10
|
+
fetch_query_combo_example: (id: string, params: any) => [...ModuleKeys.module(), "fetch-combo", id, params] as const,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
# NOTE: as const is used at end so that keys are immutable
|
|
15
|
+
# NOTE : ans insted of standard object keys usd as function so its easy and clean to use and maintain in larger scale
|
|
16
|
+
# NOTE : you can change *_name_example to your own query name
|
|
17
|
+
*/
|