@gaddario98/react-core
Un framework modulare React che unifica la gestione dello stato, form, data fetching, orchestrazione delle pagine, localizzazione, autenticazione e notifiche in un unico pacchetto coeso. Costruito su Jotai, TanStack Form e TanStack Query.
Vedi su GitHub@gaddario98/react-core
A modular, type-safe React framework that unifies state management, forms, data fetching, page orchestration, localization, authentication, and notifications into a single cohesive package. Built on Jotai, TanStack Form, and TanStack Query.
Version: 2.1.5 | License: MIT | Author: Giosuè Addario
Table of Contents
- Overview
- Installation
- Architecture
- Quick Start β Unified Configuration
- Modules
- Entry Points
- Cross-Platform Support
- TypeScript Support
- Contributing
Overview
@gaddario98/react-core is composed of independent modules that share a common state layer (Jotai atoms). Each module can be imported individually via sub-path exports or consumed together through the root entry point.
Key design principles:
- Atom-based state: Every module stores its configuration and runtime state in Jotai atoms via
atomStateGenerator, enabling cross-module reactivity without React Context nesting - Platform-agnostic: No DOM or React Native imports β platform behavior is injected via configurable container components
- Tree-shakeable: 10 independent sub-path exports; import only what you use
- Zero-config defaults: Every module works out of the box with sensible defaults, customizable at any depth
- TypeScript-first: Full generic type support with strict inference
Installation
npm install @gaddario98/react-core
Peer Dependencies
npm install react@">=18.0.0 <20.0.0"
All other dependencies (@tanstack/react-form, @tanstack/react-query, jotai, axios, fast-deep-equal, fflate) are bundled.
Architecture
@gaddario98/react-core
βββ state/ β Jotai atom factory + compressed storage
βββ auth/ β Authentication state (built on state/)
βββ notifications/ β Toast/notification state (built on state/)
βββ localization/ β i18n engine with ICU formatting (built on state/)
βββ form/ β Dynamic form builder (built on TanStack Form + state/)
βββ queries/ β Data fetching layer (built on TanStack Query + state/)
βββ pages/ β Page orchestrator (composes form/ + queries/ + state/)
βββ providers/ β Generic provider compositor
βββ utiles/ β Utility functions (classnames, memoization)
βββ config/ β useCoreConfig β unified setup hook
The dependency flow is bottom-up: state/ is the foundation, auth/, notifications/, and localization/ are state slices, form/ and queries/ are feature layers, and pages/ orchestrates everything. config/useCoreConfig wires all modules together in a single hook.
Quick Start β Unified Configuration
The useCoreConfig hook initializes all modules at once. Call it near the root of your app:
import { useCoreConfig, AppProviders, QueriesProvider } from "@gaddario98/react-core"; function CoreProvider({ children }: { children: React.ReactNode }) { useCoreConfig({ localization: { defaultLocale: "en", supportedLocales: ["en", "it"], locales: { en: { common: { welcome: "Welcome" } }, it: { common: { welcome: "Benvenuto" } }, }, }, pages: { PageContainer: ({ children, id }) => <main id={id}>{children}</main>, BodyContainer: ({ children }) => <div className="body">{children}</div>, defaultMetadata: { title: "My App" }, }, form: { formFieldContainer: ({ children }) => <div className="field">{children}</div>, }, apiConfig: { endpoints: { api: "https://api.example.com" }, }, }); return <>{children}</>; } export default function App() { return ( <AppProviders providers={[QueriesProvider]}> <CoreProvider> {/* your app */} </CoreProvider> </AppProviders> ); }
useCoreConfig automatically:
- Wires
translateTextfrom the localization module into forms and pages - Wires
showNotificationinto forms and queries - Sets the
Authorizationheader fromauth.tokenon all API requests - Passes
authValuesinto pages for access control
Modules
State (/state)
The foundational layer. Provides a factory function to create Jotai atoms with optional compressed persistence to localStorage.
import { atomStateGenerator } from "@gaddario98/react-core/state"; const { atom: themeAtom, useValue: useThemeValue, // read-only hook useState: useThemeState, // [value, setter] hook useReset: useThemeReset, // reset to default } = atomStateGenerator<"light" | "dark">({ key: "app-theme", defaultValue: "light", persist: true, // compressed localStorage persistence });
Storage features:
- Data < 1 KB stored as raw JSON; larger payloads are deflated (fflate) and base64-encoded
- Writes are debounced (50 ms) and flushed on
beforeunload/visibilitychange - Swap the storage backend via
setCustomStorage(myStorage)(e.g., AsyncStorage for React Native)
Exports:
| Export | Description |
|---|---|
atomStateGenerator<T>(options) | Creates an atom with useValue, useState, useReset hooks |
storage | Default compressed storage singleton |
setCustomStorage(s) | Replace the storage backend |
Auth (/auth)
A persisted authentication state slice.
import { useAuthState, useAuthValue } from "@gaddario98/react-core/auth"; // Read auth state const auth = useAuthValue(); console.log(auth?.token, auth?.isLogged); // Update auth state const [auth, setAuth] = useAuthState(); setAuth({ id: "user-1", token: "jwt...", isLogged: true }); // Clear on logout setAuth(null);
AuthState type:
type AuthState = { id: string; accountVerified?: boolean; isLogged?: boolean; token?: string; phoneNumber?: string; email?: string; }
The atom is persisted under the key "reactAuthStore" using compressed storage.
Notifications (/notifications)
In-memory notification state for toast/snackbar systems.
import { useNotification } from "@gaddario98/react-core/notifications"; const { showNotification, clearNotification } = useNotification("myPage"); showNotification({ message: "Profile updated!", type: "success", autoHideDuration: 3000, });
NotificationMessage type:
interface NotificationMessage { id: string; message: string; type: "success" | "error" | "info" | "warning"; autoHideDuration?: number; textTransOption?: Record<string, unknown>; ns?: string; }
Exports:
| Export | Description |
|---|---|
useNotification(ns?) | Returns { showNotification, clearNotification } |
useNotificationValue() | Read current notification |
useNotificationState() | [notification, setter] tuple |
notificationAtom | Raw Jotai atom |
Localization (/localization)
A built-in i18n engine with no external library dependencies. Supports ICU-style interpolation, pluralization, gender selection, and number/date/currency formatting.
import { useTranslation, useLocalizationActions } from "@gaddario98/react-core/localization"; // Initialize locales at app startup const { initializeLocale, switchLocale, addLocale } = useLocalizationActions(); initializeLocale({ defaultLocale: "en", supportedLocales: ["en", "it"], locales: { en: { shop: { items: "{{count, plural, =0{No items} one{1 item} other{# items}}}" } }, it: { shop: { items: "{{count, plural, =0{Nessun articolo} one{1 articolo} other{# articoli}}}" } }, }, }); // Use translations const { t, locale } = useTranslation("shop"); t("items", { count: 5 }); // "5 items"
Supported interpolation patterns:
{{name}}β simple variable substitution{{count, number}}β number formatting (locale-aware){{date, date}}β date formatting{{price, currency}}β currency formatting{{count, plural, =0{...} one{...} other{...}}}β ICU plural rules{{gender, select, male{...} female{...} other{...}}}β gender/select
Server-side usage (outside React):
import { createServerTranslator } from "@gaddario98/react-core/localization"; const { t } = createServerTranslator(resources, "en"); t("shop.items", { count: 3 }); // "3 items"
Form (/form)
A dynamic, type-safe form builder on top of TanStack React Form. Renders fields from a declarative configuration array.
import { FormManager } from "@gaddario98/react-core/form"; interface ContactForm { name: string; email: string; } <FormManager<ContactForm> defaultValues={{ name: "", email: "" }} data={[ { name: "name", label: "Full Name", rules: { onChange: (val) => (!val ? "Required" : undefined) }, component: (props) => <input value={props.value} onChange={(e) => props.onChange(e.target.value)} />, }, { name: "email", label: "Email", component: (props) => <input value={props.value} onChange={(e) => props.onChange(e.target.value)} />, }, ]} submit={[ { component: ({ onClick }) => <button onClick={onClick}>Save</button>, onSuccess: async (values) => console.log(values), }, ]} />
Key features:
- Static or dynamic field definitions (factory functions with
{ get, set }access to current values) - Partial form submission β validate only a subset of fields via
values: ["field1", "field2"] - Custom layout containers via
viewSettings(dialogs, cards, drawers) - Built-in notification integration for success/error feedback
- Headless alternative via
useFormManagerhook
Global configuration:
import { useFormConfigState } from "@gaddario98/react-core/form"; const [, setFormConfig] = useFormConfigState(); setFormConfig((prev) => ({ ...prev, translateText: (key, opts) => t(key, opts), formFieldContainer: MyFieldWrapper, showNotification: (msg) => toast(msg.message), }));
For full API details, see github.com/gaddario98/react-form.
Queries (/queries)
A unified data fetching layer on top of TanStack React Query and Jotai. Manages queries, mutations, and WebSockets through a single declarative API.
import { useApi } from "@gaddario98/react-core/queries"; import type { QueriesArray } from "@gaddario98/react-core/queries"; const queries = [ { type: "query", key: "products", queryConfig: { endpoint: ["api", "v1/products"], queryKey: ["products"], }, }, { type: "mutation", key: "addProduct", mutationConfig: { endpoint: ["api", "v1/products"], method: "POST", queryKeyToInvalidate: ["products"], }, }, ] as const satisfies QueriesArray; const { allQuery, allMutation, refreshQueries } = useApi(queries, { scopeId: "product-page", }); const products = allQuery.products.data; allMutation.addProduct.mutate({ body: { name: "New Product" } });
Key features:
- Typed
allQuery/allMutation/allWebSocketmaps from the configuration array - Automatic Jotai atom sync β query results are accessible cross-component without refetching
- Fine-grained subscriptions via
useApiValues(re-render only on specific path changes) - Built-in WebSocket support alongside REST queries
- Payload encryption/decryption (AES-GCM)
- Offline persistence via TanStack Query Persist
- Standalone
useQueryApi/useMutateApihooks for simpler one-off usage
Global configuration:
import { useApiConfigState } from "@gaddario98/react-core/queries"; const [, setApiConfig] = useApiConfigState(); setApiConfig({ endpoints: { api: "https://api.example.com" }, defaultHeaders: { "Cache-Control": "no-cache" }, validateAuthFn: () => !!localStorage.getItem("token"), queryClient: new QueryClient({ defaultOptions: { queries: { retry: 2 } } }), });
Wrap your app with QueriesProvider to initialize the TanStack QueryClient:
import { QueriesProvider } from "@gaddario98/react-core/queries"; <QueriesProvider> <App /> </QueriesProvider>
For full API details, see github.com/gaddario98/react-queries.
Pages (/pages)
A page orchestrator that composes forms, queries, metadata, lazy loading, and layout into a single PageProps configuration. Works on both web and React Native.
import { PageGenerator } from "@gaddario98/react-core/pages"; import type { PageProps, QueryDefinition } from "@gaddario98/react-core/pages"; interface MyForm { search: string } type MyQueries = [QueryDefinition<"results", "query", never, Product[]>]; const props: PageProps<MyForm, MyQueries> = { id: "search-page", meta: { title: "Search", description: "Find products" }, form: { defaultValues: { search: "" }, data: [{ name: "search", debounceDelay: 300, component: SearchInput }], }, queries: [ { type: "query", key: "results", queryConfig: ({ get }) => ({ queryKey: ["results", get("form", "search")], queryFn: () => fetchProducts(get("form", "search")), enabled: get("form", "search", "").length > 2, }), }, ], contents: [ { type: "custom", component: ({ get }) => { const results = get("query", "results.data", []); return <ProductList products={results} />; }, }, ], }; <PageGenerator<MyForm, MyQueries> {...props} />;
Key features:
get()/set()API with automatic dependency tracking (90% fewer re-renders)- Dynamic SEO metadata (Open Graph, Twitter Card, JSON-LD, AI hints, robots)
- Lazy loading with viewport, interaction, or conditional triggers
- Lifecycle callbacks (
onMountComplete,onQuerySuccess,onQueryError,onFormSubmit,onValuesChange) - Authentication gate via
enableAuthControl - Platform overrides via
platformOverrides: { web: {...}, native: {...} }
For full API details, see github.com/gaddario98/react-pages.
Providers (/providers)
A utility component that composes multiple React providers without deep nesting.
import { AppProviders } from "@gaddario98/react-core/providers"; <AppProviders providers={[ QueriesProvider, [ThemeProvider, { theme: "dark" }], [IntlProvider, { locale: "en" }], ]} > <App /> </AppProviders>
Supports both bare components and [Component, props] tuples. Providers are composed in declaration order (first = outermost).
Utilities (/utiles)
General-purpose React and JavaScript utilities.
import { cn, withMemo } from "@gaddario98/react-core/utiles";
| Export | Description |
|---|---|
cn(...inputs) | Combines clsx + tailwind-merge for safe Tailwind class merging |
withMemo(Component, areEqual?) | Type-safe React.memo wrapper that preserves generic types |
createExtractor(data, cache?, keys?) | Picks a subset of keys from an object with stable reference caching |
Config (/config)
The unified configuration hook that wires all modules together.
import { useCoreConfig } from "@gaddario98/react-core/config";
CoreConfig interface:
interface CoreConfig { form?: Partial<FormConfigProps>; localization?: LocalizationConfigProps; pages?: Partial<PageConfigProps>; apiConfig?: Partial<ApiConfig>; }
See Quick Start for usage.
What useCoreConfig wires automatically:
| Source | Target | What |
|---|---|---|
localization | form, pages | translateText function |
notifications | form, queries | showNotification handler |
auth | queries | Authorization header (Bearer token) |
auth | queries | validateAuthFn (auth validation) |
auth | pages | authValues (access control) |
Entry Points
The package exposes 10 sub-path exports for tree-shaking:
| Import Path | Module | Typical Use |
|---|---|---|
@gaddario98/react-core | All modules | Full framework access |
@gaddario98/react-core/state | State | Atom factory, storage |
@gaddario98/react-core/auth | Auth | Authentication state |
@gaddario98/react-core/notifications | Notifications | Toast state |
@gaddario98/react-core/localization | Localization | i18n engine |
@gaddario98/react-core/form | Form | Form builder |
@gaddario98/react-core/queries | Queries | Data fetching |
@gaddario98/react-core/pages | Pages | Page orchestrator |
@gaddario98/react-core/providers | Providers | Provider compositor |
@gaddario98/react-core/utiles | Utilities | Helpers |
Cross-Platform Support
The entire package is platform-agnostic. No module imports react-dom or react-native directly.
Web: Works out of the box. Metadata is written to document.head.
React Native: Replace the storage backend and layout containers:
import { setCustomStorage } from "@gaddario98/react-core/state"; import { usePageConfigState } from "@gaddario98/react-core/pages"; import AsyncStorage from "@react-native-async-storage/async-storage"; import { View, ScrollView } from "react-native"; // Swap storage for React Native setCustomStorage({ getItem: (key) => AsyncStorage.getItem(key) ?? null, setItem: (key, val) => { AsyncStorage.setItem(key, val) }, removeItem: (key) => { AsyncStorage.removeItem(key) }, }); // Swap layout containers const [, setPageConfig] = usePageConfigState(); setPageConfig((prev) => ({ ...prev, PageContainer: ({ children, id }) => <View style={{ flex: 1 }}>{children}</View>, BodyContainer: ({ children }) => <ScrollView>{children}</ScrollView>, HeaderContainer: ({ children }) => <View>{children}</View>, FooterContainer: ({ children }) => <View>{children}</View>, ItemsContainer: ({ children }) => <View>{children}</View>, }));
See the React Native Integration Strategy in the pages documentation for a full setup guide.
TypeScript Support
All modules are fully typed with generics. Key generic interfaces:
// Form β generic over field values FormManager<F extends FieldValues> FormManagerProps<F extends FieldValues> // Queries β generic over query array definition useApi<Q extends QueriesArray>(queries: Q, options) QueriesArray // tuple of query/mutation/websocket definitions // Pages β generic over form, queries, and page variables PageGenerator<F extends FieldValues, Q extends QueriesArray, V extends Record<string, unknown>> PageProps<F, Q, V> FunctionProps<F, Q, V> // the { get, set } interface // State β generic over atom value type atomStateGenerator<T>(options): AtomState<T>
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT