React REST API framework + TypeScript + Styled-Components

Good% time_day, khabrovites!





I have not written any articles yet, there is no experience in this from the word at all. Therefore, please do not judge strictly and I apologize in advance if I make mistakes somewhere.





This is a completely new experience for me, I have never had the opportunity to share my experience and best practices with the community.





Foreword

React, , - , , , .



- , , , - "", "" .., , , , , , , , .



, , . , "Core". , , , , - , ( , ), .





(!) , , , , - - , . , ( , React), .



, , .





Components

, , .





, , :





  • (Smart)





  • (Ordinary)





  • (Simple)





  • UI (UI, )





  • (Containers)





  • (Pages)





(Smart, Ordinary, Simple UI) Components.





:





  • UI - , () : button, input, textarea, select .





    • .





  • Simple - , , , - , - .





    • .





    • , , React ( useState).





    • UI .





  • Ordinary - , - , -.





    • , .





    • , , React ( useState).





    • Simple UI .





  • Smart - , , -





    • , ( )





    • , ,





    • Ordinary, Simple UI .





Componets:





.
└── src/
    β”œβ”€β”€ components/
    β”‚   β”œβ”€β”€ ordinary
    β”‚   β”œβ”€β”€ simple
    β”‚   β”œβ”€β”€ smart
    β”‚   └── ui
    └── ...
      
      



(Containers Pages) ( src).





  • Containers - , , , , , , , , - , , .





  • Pages - , Components, . , , .





:





.
└── src/
    β”œβ”€β”€ components/
    β”‚   β”œβ”€β”€ ordinary
    β”‚   β”œβ”€β”€ simple
    β”‚   β”œβ”€β”€ smart
    β”‚   └── ui
    β”œβ”€β”€ containers
    β”œβ”€β”€ pages
    └── ...
      
      



, 2 ( ) :





  • index.tsx - ,





  • styled.ts - , ( styles.sss, styles.css, , )





Align. , "Simple", ( ) , , UI .





// index.tsx

import React, { memo } from "react";
import * as S from "./styled"; //   

const Align = memo(({ children, axis, isAdaptable = false }: Readonly<Props>) => {
	return (
		<S.Align $axis={axis} $isAdaptable={isAdaptable}>
			{children}
		</S.Align>
	);
});

export { Align };
export interface Props {
	axis: S.Axis;
	children?: React.ReactNode;
	isAdaptable?: boolean;
}
      
      



// styled.ts

import styled, { css } from "styled-components";

const notAdaptableMixin = css`
	width: 100%;
	height: 100%;
	max-height: 100%;
	max-width: 100%;
`;

const adaptableMixin = css<AlignProps>`
	width: ${(props) => !props.$axis.includes("x") && "100%"};
	height: ${(props) => !props.$axis.includes("y") && "100%"};
	min-width: ${(props) => props.$axis.includes("x") && "100%"};
	min-height: ${(props) => props.$axis.includes("y") && "100%"};
`;

export const Align = styled.div<AlignProps>`
	display: flex;
	flex-grow: 1;
	justify-content: ${(props) => (props.$axis.includes("x") ? "center" : "start")};
	align-items: ${(props) => (props.$axis.includes("y") ? "center" : "start")};
	${(props) => (props.$isAdaptable ? adaptableMixin : notAdaptableMixin)};
`;

export interface AlignProps {
	$axis: Axis;
	$isAdaptable?: boolean;
}

export type Axis = ("y" | "x")[] | "x" | "y";

      
      



, ...





Core

"" . , , , ..





:





  • Config - ( , )





  • Constants - , ( )





  • Hooks - (, ).





  • Models - , .





  • Schemes - , ..





  • Services - , .





  • Store - ( MobX), Redux, , ..





  • Theme ( Styled-Components) - .





  • Types - , .





  • Utils - , , , , .





  • api.ts - HTTP ( axios), - ( - , ).





// config/api.config.ts

export const serverURI = "http://localhost:8080";
export const routesPrefix = '/api/v1';


// config/routes.config.ts

import { routesPrefix } from "./api.config";

export const productBrowserRoutes = {
	getOne: (to: string = ":code") => `/product/${to}`,
	search: (param: string = ":search") => `/search/${param}`,
};

export const productAPIRoutes = {
	getOne: (code: string) => `${routesPrefix}/product/code/${code}`,
	search: () => `${routesPrefix}/product/search`,
};
      
      



- . , , , , .





// constants/message.constants.ts

export const UNKNOWN_ERROR = " ";
      
      



// hooks/useAPI.ts
//     
/* eslint-disable react-hooks/exhaustive-deps */

import { useCallback, useEffect } from "react";
import { useLocalObservable } from "mobx-react-lite";
import type { API, Schema, Take } from "@core/types";

function useAPI<
	F extends API.Service.Function<API.Response<any>>,
	R extends Take.FromServiceFunction.Response<F>,
	P extends Parameters<F>
>(service: F, { isPendingAfterMount = false, isIgnoreHTTPErrors = false }: Options = {}) {
	const localStore = useLocalObservable<Store>(() => ({
		isPending: {
			value: isPendingAfterMount,
			set: function (value) {
				this.value = value;
			},
		},
	}));

	const call = useCallback(
		async (...params: P): Promise<R["result"]> => {
			localStore.isPending.set(true);

			try {
				const { data } = await service(...params);
				const { result } = data;

				localStore.isPending.set(false);

				return result;
			} catch (error) {
				if (isIgnoreHTTPErrors === false) {
					console.error(error);
				}

				localStore.isPending.set(false);

				throw error;
			}
		},
		[service, isIgnoreHTTPErrors]
	);

	const isPending = useCallback(() => {
		return localStore.isPending.value;
	}, []);

	useEffect(() => {
		localStore.isPending.set(isPendingAfterMount);
	}, [isPendingAfterMount]);

	return {
		call,
		isPending,
	};
}

export { useAPI };
export interface Options {
	isPendingAfterMount?: boolean;
	isIgnoreHTTPErrors?: boolean;
}

type Store = Schema.Store<{ isPending: boolean }>;
      
      



// models/product.model.ts
//   

export interface ProductModel {
	id: number;
	name: string;
	code: string;
	info: {
		description: string;
		note: string;
	};
	config: {
		isAllowedForPurchaseIfInStockZero: boolean;
		isInStock: boolean;
	};
	seo: {
		title: string;
		keywords: string;
		description: string;
	};
}
      
      



// services/product.service.ts
//     

import { api } from "../api";
import { routesConfig } from "../config";
import type { ProductModel } from "../models";
import type { API } from "../types";

export function getOne(code: string) {
	return api.get<API.Service.Response.GetOne<ProductModel>>(
		routesConfig.productAPIRoutes.getOne(code)
	);
}
      
      



// theme/index.ts
//  

import { DefaultTheme } from "styled-components";

export const theme: DefaultTheme = {
	colors: {
			primary: "#2648f1",
			intense: "#151e27",
			green: "#53d769",
			grey: "#626b73",
			red: "#f73d34",
			orange: "#fdb549",
			yellow: "#ffe243",
			white: "white",
		},
};
      
      



// types/index.tsx
//  

import type { AxiosResponse } from "axios";

export namespace API {
	export namespace Service {
		export namespace Response {
			export type Upsert<T> = Response<T | null>;
			export type GetOne<T> = Response<T | null>;
			export type GetMany<T> = Response<{
				rows: T[];
				totalRowCount: number;
				totalPageCount: number;
			}>;
		}
		export type Function<T extends API.Response<any>, U extends any[] = any[]> = (
			...params: U
		) => Promise<AxiosResponse<T>>;
	}
	export type Response<T> = {
		status: number;
		result: T;
	};
}
      
      



// utils/throttle.ts

function throttle<P extends any[]>(func: (...params: P) => any, limit: number) {
	let inThrottle: boolean;

	return function (...params: P): any {
		if (!inThrottle) {
			inThrottle = true;
			func(...params);
			setTimeout(() => (inThrottle = false), limit);
		}
	};
}

export { throttle };
      
      



// store/index.tsx

import { createContext } from "react";
import { useLocalObservable } from "mobx-react-lite";

import { app, App } from "./segments/app";
import { layout, Layout } from "./segments/layout";
import { counters, Counters } from "./segments/counters";

export const combinedStore = { layout, app, counters };
export const storeContext = createContext<StoreContext>(combinedStore);
export function StoreProvider({ children }: { children: React.ReactNode }) {
	const store = useLocalObservable(() => combinedStore);

	return <storeContext.Provider value={store}>{children}</storeContext.Provider>;
}

export type StoreContext = {
	app: App;
	layout: Layout;
	counters: Counters;
};
      
      



// api.ts
//  AXIOS    

import axios from "axios";
import { apiConfig } from "./config";

const api = axios.create({
	baseURL: apiConfig.serverURI,
});

api.interceptors.request.use((req) => {
	return {
		...req,
		baseURL: apiConfig.serverURI,
	};
});

export { api };
      
      



! .





...

, , :





  • Assets - , : , , .. (, , )





  • Routes - ( , ) ( ).





  • Styles - , , .





// routes/index.tsx

import { Switch, Route } from "react-router-dom";

//  
import { Product } from "../pages/Product";
...
import { NotFound } from "../pages/NotFound";

import { routesConfig } from "../core/config";

const Routes = () => {
	return (
		<Switch>
			<Route exact path={routesConfig.productBrowserRoutes.getOne()}>
				<Product />
			</Route>
			{/*  -  */}
			<Route>
				<NotFound />
			</Route>
		</Switch>
	);
};

export { Routes };

      
      



2 :





  • app.tsx -





:





// app.tsx

import React, { useEffect } from "react";

//  
import { Routes } from "./routes";

const App = () => {
	return (
			<Routes />
	);
};

export { App };

      
      



  • index.tsx -





:





import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import { ThemeProvider } from "styled-components";

//  
import { App } from "./app";
//   
import { BodyStyles } from "./styles";

import { StoreProvider } from "../core/store";
//  
import { theme } from "../core/theme";

import reportWebVitals from "./reportWebVitals";

const app = document.getElementById("app");

ReactDOM.render(
	<React.StrictMode>
		<ThemeProvider theme={theme}>
			<BodyStyles />
			<BrowserRouter>
				<StoreProvider>
					<App />
				</StoreProvider>
			</BrowserRouter>
		</ThemeProvider>
	</React.StrictMode>,
	app
);

reportWebVitals();

      
      



, , .





:





.
└── src/
    β”œβ”€β”€ assets/
    β”‚   β”œβ”€β”€ fonts
    β”‚   └── icons
    β”œβ”€β”€ components/
    β”‚   β”œβ”€β”€ ordinary
    β”‚   β”œβ”€β”€ simple
    β”‚   β”œβ”€β”€ smart
    β”‚   └── ui
    β”œβ”€β”€ containers
    β”œβ”€β”€ core/
    β”‚   β”œβ”€β”€ config
    β”‚   β”œβ”€β”€ constants
    β”‚   β”œβ”€β”€ hooks
    β”‚   β”œβ”€β”€ models
    β”‚   β”œβ”€β”€ schemes
    β”‚   β”œβ”€β”€ services
    β”‚   β”œβ”€β”€ store
    β”‚   β”œβ”€β”€ theme
    β”‚   β”œβ”€β”€ types
    β”‚   β”œβ”€β”€ utils
    β”‚   └── api.ts
    β”œβ”€β”€ pages
    β”œβ”€β”€ routes
    β”œβ”€β”€ styles
    β”œβ”€β”€ app.tsx
    └── index.tsx
      
      



- , .





( , ).





.








All Articles