Skip to content

Instantly share code, notes, and snippets.

@saifmohamedsv
Last active March 10, 2024 15:41
Show Gist options
  • Save saifmohamedsv/fb15502fdfcddab8e4ca36e569e9939f to your computer and use it in GitHub Desktop.
Save saifmohamedsv/fb15502fdfcddab8e4ca36e569e9939f to your computer and use it in GitHub Desktop.
Create custom boilerplate for your react.js app
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