Last active
October 2, 2024 21:57
-
-
Save saifmohamedsv/fb15502fdfcddab8e4ca36e569e9939f to your computer and use it in GitHub Desktop.
Create custom boilerplate for your react.js app
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { execSync } = require("child_process"); | |
const fs = require("fs"); | |
const readline = require("readline").createInterface({ | |
input: process.stdin, | |
output: process.stdout, | |
}); | |
let appName = "brand-ecommerce"; // Replace with your desired app name | |
// Create a new next.js app | |
execSync( | |
`npx create-next-app@latest ${appName} --ts --tailwind --eslint --app --src-dir --use-yarn`, | |
{ | |
stdio: "inherit", | |
} | |
); | |
// Navigate to the app directory | |
process.chdir(appName); | |
// Install dependencies | |
execSync("yarn", { stdio: "inherit" }); | |
console.log("Next.js app setup completed."); | |
// Install dev dependencies | |
// execSync("yarn add -D package-name", { stdio: "inherit" }); | |
// Install {package-name} package | |
execSync("yarn add @reduxjs/toolkit react-redux", { stdio: "inherit" }); | |
// Create files & folders (Folder Strcuture) | |
fs.mkdirSync("src/components"); | |
fs.mkdirSync("src/components/ui"); | |
fs.mkdirSync("src/services"); | |
fs.mkdirSync("src/services/api"); | |
fs.mkdirSync("src/hooks"); | |
fs.mkdirSync("src/utils"); | |
fs.mkdirSync("src/lib"); | |
fs.mkdirSync("src/lib/features"); | |
fs.writeFileSync("src/utils/helpers.ts", ".."); | |
fs.writeFileSync("src/utils/formatting.ts", ".."); | |
// Creating files contents | |
const StoreProviderContent = ` | |
"use client"; | |
import type { AppStore } from "@/lib/store"; | |
import { makeStore } from "@/lib/store"; | |
import { setupListeners } from "@reduxjs/toolkit/query"; | |
import type { ReactNode } from "react"; | |
import { useEffect, useRef } from "react"; | |
import { Provider } from "react-redux"; | |
interface Props { | |
readonly children: ReactNode; | |
} | |
export const StoreProvider = ({ children }: Props) => { | |
const storeRef = useRef<AppStore | null>(null); | |
if (!storeRef.current) { | |
// Create the store instance the first time this renders | |
storeRef.current = makeStore(); | |
} | |
useEffect(() => { | |
if (storeRef.current != null) { | |
// configure listeners using the provided defaults | |
// optional, but required for 'refetchOnFocus'/'refetchOnReconnect' behaviors | |
const unsubscribe = setupListeners(storeRef.current.dispatch); | |
return unsubscribe; | |
} | |
}, []); | |
return <Provider store={storeRef.current}>{children}</Provider>; | |
}; | |
`; | |
fs.writeFileSync("src/app/StoreProvider.tsx", StoreProviderContent); | |
fs.writeFileSync( | |
"src/lib/createAppSlice.ts", | |
` | |
import { asyncThunkCreator, buildCreateSlice } from "@reduxjs/toolkit"; | |
// buildCreateSlice allows us to create a slice with async thunks. | |
export const createAppSlice = buildCreateSlice({ | |
creators: { asyncThunk: asyncThunkCreator }, | |
}); | |
` | |
); | |
fs.writeFileSync( | |
"src/lib/hooks.ts", | |
` | |
// This file serves as a central hub for re-exporting pre-typed Redux hooks. | |
import { useDispatch, useSelector, useStore } from "react-redux"; | |
import type { AppDispatch, AppStore, RootState } from "./store"; | |
// Use throughout your app instead of plain useDispatch and useSelector | |
export const useAppDispatch = useDispatch.withTypes<AppDispatch>(); | |
export const useAppSelector = useSelector.withTypes<RootState>(); | |
export const useAppStore = useStore.withTypes<AppStore>(); | |
` | |
); | |
fs.writeFileSync( | |
"src/lib/store.ts", | |
` | |
import type { Action, ThunkAction } from "@reduxjs/toolkit"; | |
import { combineSlices, configureStore } from "@reduxjs/toolkit"; | |
import { counterSlice } from "./features/counter/counterSlice"; | |
import { quotesApiSlice } from "./features/quotes/quotesApiSlice"; | |
// combineSlices automatically combines the reducers using | |
// their reducerPaths, therefore we no longer need to call combineReducers. | |
const rootReducer = combineSlices(counterSlice, quotesApiSlice); | |
// Infer the RootState type from the root reducer | |
export type RootState = ReturnType<typeof rootReducer>; | |
// makeStore encapsulates the store configuration to allow | |
// creating unique store instances, which is particularly important for | |
// server-side rendering (SSR) scenarios. In SSR, separate store instances | |
// are needed for each request to prevent cross-request state pollution. | |
export const makeStore = () => { | |
return configureStore({ | |
reducer: rootReducer, | |
// Adding the api middleware enables caching, invalidation, polling, | |
// and other useful features of rtk-query. | |
middleware: (getDefaultMiddleware) => { | |
return getDefaultMiddleware().concat(quotesApiSlice.middleware); | |
}, | |
}); | |
}; | |
// Infer the return type of makeStore | |
export type AppStore = ReturnType<typeof makeStore>; | |
// Infer the AppDispatch type from the store itself | |
export type AppDispatch = AppStore["dispatch"]; | |
export type AppThunk<ThunkReturnType = void> = ThunkAction< | |
ThunkReturnType, | |
RootState, | |
unknown, | |
Action | |
>; | |
` | |
); | |
fs.writeFileSync( | |
"src/lib/features/counterSlice.ts", | |
` | |
import { createAppSlice } from "@/lib/createAppSlice"; | |
import type { AppThunk } from "@/lib/store"; | |
import type { PayloadAction } from "@reduxjs/toolkit"; | |
import { fetchCount } from "./counterAPI"; | |
export interface CounterSliceState { | |
value: number; | |
status: "idle" | "loading" | "failed"; | |
} | |
const initialState: CounterSliceState = { | |
value: 0, | |
status: "idle", | |
}; | |
export const counterSlice = createAppSlice({ | |
name: "counter", | |
initialState, | |
reducers: (create) => ({ | |
incrementByAmount: create.reducer( | |
(state, action: PayloadAction<number>) => { | |
state.value += action.payload; | |
} | |
), | |
incrementAsync: create.asyncThunk( | |
async (amount: number) => { | |
const response = await fetchCount(amount); | |
return response.data; | |
}, | |
{ | |
pending: (state) => { | |
state.status = "loading"; | |
}, | |
fulfilled: (state, action) => { | |
state.status = "idle"; | |
state.value += action.payload; | |
}, | |
rejected: (state) => { | |
state.status = "failed"; | |
}, | |
} | |
), | |
}), | |
selectors: { | |
selectCount: (counter) => counter.value, | |
selectStatus: (counter) => counter.status, | |
}, | |
}); | |
export const { incrementByAmount, incrementAsync } = counterSlice.actions; | |
export const { selectCount, selectStatus } = counterSlice.selectors; | |
` | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment