@unidev-hub/react-toolkit
TypeScript icon, indicating that this package has built-in type declarations

0.1.1 • Public • Published

@unidev-hub/react-toolkit

npm version npm downloads License TypeScript Mantine UI

A comprehensive collection of React components, hooks, and utilities built with Mantine UI to accelerate development of React applications within the Unidev Hub ecosystem.

📚 Table of Contents

✨ Features

  • 🔄 Data Fetching Hooks: Seamless integration with @unidev-hub/api-client for resource fetching
  • 📝 Form Components & Hooks: Easy-to-use form handling with Mantine Form integration
  • 🧩 UI Components: Beautiful, accessible UI components powered by Mantine
  • 🔐 Authentication Components: Ready-to-use auth providers, protected routes, and permission gates
  • 📐 Layout Components: Flexible grid systems and common page layouts
  • 🌐 Context Providers: Theme, configuration, and feature flag providers
  • 🧭 Routing Utilities: Route generation helpers and navigation guards
  • 📊 State Management: Custom hooks and utilities for efficient state management
  • ⚠️ Error Handling: Graceful error handling and recovery mechanisms

All components and utilities are:

  • ✅ Fully typed with TypeScript
  • ✅ Tree-shakable (import only what you need)
  • ✅ Well-documented with examples
  • ✅ Tested for quality and reliability
  • ✅ Accessible and responsive
  • ✅ Built with Mantine UI for consistent styling and theming

🔧 Installation

NPM

npm install @unidev-hub/react-toolkit @mantine/core @mantine/hooks @mantine/form @mantine/dates @mantine/notifications @mantine/modals @tabler/icons-react dayjs

Yarn

yarn add @unidev-hub/react-toolkit @mantine/core @mantine/hooks @mantine/form @mantine/dates @mantine/notifications @mantine/modals @tabler/icons-react dayjs

PNPM

pnpm add @unidev-hub/react-toolkit @mantine/core @mantine/hooks @mantine/form @mantine/dates @mantine/notifications @mantine/modals @tabler/icons-react dayjs

Peer Dependencies

This package has the following peer dependencies:

  • react (>=16.8.0)
  • react-dom (>=16.8.0)
  • @mantine/core and related Mantine packages (>=7.0.0)
  • @tabler/icons-react for icons

For optimal functionality, we recommend also installing:

  • @unidev-hub/api-client - For API communication
  • @unidev-hub/core-utils - For common utility functions

🚀 Setup

First, set up the Mantine providers in your application entry point:

import React from 'react';
import { MantineProvider, createTheme } from '@mantine/core';
import { Notifications } from '@mantine/notifications';
import { ModalsProvider } from '@mantine/modals';
import { BrowserRouter } from 'react-router-dom';

// Import Mantine styles
import '@mantine/core/styles.css';
import '@mantine/dates/styles.css';
import '@mantine/notifications/styles.css';

// Create your theme
const theme = createTheme({
  primaryColor: 'blue',
  // Add your customizations here
});

function App() {
  return (
    <MantineProvider theme={theme} defaultColorScheme="light">
      <Notifications />
      <ModalsProvider>
        <BrowserRouter>
          {/* Your app content */}
        </BrowserRouter>
      </ModalsProvider>
    </MantineProvider>
  );
}

export default App;

📦 Components & Hooks

Data Fetching

This toolkit provides a set of hooks for fetching data from APIs, with built-in support for pagination, sorting, filtering, and caching. It's designed to work seamlessly with the @unidev-hub/api-client package.

useResourceList

For fetching lists of resources with pagination, sorting, and filtering:

import { useResourceList } from '@unidev-hub/react-toolkit/data-fetching';
import { apiClient } from './api'; // Your api-client instance

function UserList() {
  const { 
    data, 
    loading, 
    error, 
    pagination,
    filters,
    sorting,
    refresh
  } = useResourceList(apiClient, '/users', {
    initialPagination: { page: 1, pageSize: 10 },
    initialFilters: { status: 'active' },
    initialSorting: { field: 'createdAt', direction: 'desc' }
  });
  
  // Use the data, pagination, sorting, etc.
}

useResourceItem

For fetching a single resource by ID:

import { useResourceItem } from '@unidev-hub/react-toolkit/data-fetching';

function UserDetails({ userId }) {
  const { 
    data: user, 
    loading, 
    error,
    refresh,
    update,
    remove
  } = useResourceItem(apiClient, '/users', userId);
  
  // Use the user data, update, remove functions
}

useInfiniteScroll

For implementing infinite scrolling:

import { useInfiniteScroll } from '@unidev-hub/react-toolkit/data-fetching';

function InfinitePostList() {
  const { 
    data, 
    loading, 
    error, 
    hasMore,
    loadMore,
    scrollContainerRef
  } = useInfiniteScroll(apiClient, '/posts', {
    pageSize: 10,
    sortingState: { field: 'createdAt', direction: 'desc' }
  });
  
  return (
    <div ref={scrollContainerRef} style={{ height: '400px', overflow: 'auto' }}>
      {data.map(post => (
        <PostCard key={post.id} post={post} />
      ))}
      {loading && <Loader />}
      {!hasMore && <Text>No more posts</Text>}
    </div>
  );
}

ResourceProvider

For providing a ResourceClient to child components:

import { ResourceProvider, useResource } from '@unidev-hub/react-toolkit/data-fetching';

function UsersModule() {
  return (
    <ResourceProvider
      apiClient={apiClient}
      endpoint="/users"
      name="Users"
    >
      <UsersList />
      <UserForm />
    </ResourceProvider>
  );
}

function UsersList() {
  const { resourceClient } = useResource();
  // Use the resourceClient
}

Forms

This toolkit provides comprehensive form handling with validation, field arrays, and more. The form components are built on top of Mantine's form components.

MantineForm

A complete form solution with validation:

import { MantineForm } from '@unidev-hub/react-toolkit/forms';

function UserForm({ onSubmit }) {
  return (
    <MantineForm
      initialValues={{
        name: '',
        email: '',
        role: 'user',
        isActive: true,
      }}
      validationSchema={{
        name: [{ type: 'required', message: 'Name is required' }],
        email: [
          { type: 'required', message: 'Email is required' },
          { type: 'email', message: 'Invalid email format' }
        ],
      }}
      onSubmit={onSubmit}
      fields={[
        { name: 'name', label: 'Name', type: 'text', required: true },
        { name: 'email', label: 'Email', type: 'email', required: true },
        { 
          name: 'role', 
          label: 'Role', 
          type: 'select', 
          options: [
            { value: 'user', label: 'User' },
            { value: 'admin', label: 'Admin' },
          ] 
        },
        { name: 'isActive', label: 'Active', type: 'switch' },
      ]}
      actions={[
        { type: 'submit', label: 'Save', color: 'blue' },
        { type: 'reset', label: 'Reset', variant: 'outline' }
      ]}
    />
  );
}

useForm

For more custom form handling:

import { useForm, Form, Field, ValidationMessage } from '@unidev-hub/react-toolkit/forms';

function CustomForm() {
  const form = useForm({
    initialValues: {
      name: '',
      email: '',
    },
    validationSchema: {
      name: [{ type: 'required', message: 'Name is required' }],
      email: [{ type: 'email', message: 'Invalid email format' }],
    },
    onSubmit: values => {
      console.log(values);
    }
  });
  
  return (
    <Form form={form}>
      <Field name="name" label="Name" />
      <ValidationMessage name="name" />
      
      <Field name="email" label="Email" />
      <ValidationMessage name="email" />
      
      <button type="submit">Submit</button>
    </Form>
  );
}

FieldArray

For handling arrays of form fields:

import { useForm, Form, FieldArray } from '@unidev-hub/react-toolkit/forms';
import { TextInput } from '@mantine/core';

function ContactForm() {
  const form = useForm({
    initialValues: {
      name: '',
      phoneNumbers: [{ type: 'mobile', number: '' }]
    },
    // ...
  });
  
  return (
    <Form form={form}>
      <Field name="name" label="Name" />
      
      <FieldArray
        name="phoneNumbers"
        label="Phone Numbers"
        defaultValue={{ type: 'mobile', number: '' }}
        renderItem={(item, index, { update, remove }) => (
          <Group>
            <Select
              value={item.type}
              onChange={type => update({ ...item, type })}
              data={[
                { value: 'mobile', label: 'Mobile' },
                { value: 'home', label: 'Home' },
                { value: 'work', label: 'Work' },
              ]}
            />
            <TextInput
              value={item.number}
              onChange={e => update({ ...item, number: e.target.value })}
              placeholder="Enter phone number"
            />
          </Group>
        )}
      />
      
      <button type="submit">Submit</button>
    </Form>
  );
}

UI Components

DataTable

A feature-rich data table component with sorting, filtering, and pagination:

import { DataTable } from '@unidev-hub/react-toolkit/ui';

function UsersTable({ users, loading }) {
  const columns = [
    { field: 'id', header: 'ID' },
    { field: 'name', header: 'Name', sortable: true },
    { field: 'email', header: 'Email' },
    { 
      field: 'role', 
      header: 'Role', 
      filterable: true,
      renderCell: (row) => (
        <Badge color={row.role === 'admin' ? 'red' : 'blue'}>
          {row.role}
        </Badge>
      )
    },
    {
      field: 'actions',
      header: 'Actions',
      renderCell: (row) => (
        <Group>
          <Button size="xs" onClick={() => handleEdit(row)}>Edit</Button>
          <Button size="xs" color="red" onClick={() => handleDelete(row)}>Delete</Button>
        </Group>
      )
    }
  ];

  return (
    <DataTable
      columns={columns}
      data={users}
      loading={loading}
      pagination={{
        currentPage: 1,
        pageSize: 10,
        totalItems: users.length
      }}
      onPageChange={handlePageChange}
      onSort={handleSort}
      sortable
      filterable
      highlightOnHover
      striped
    />
  );
}

Modal

A flexible modal component:

import { Modal, Toast } from '@unidev-hub/react-toolkit/ui';

function CreateUserButton() {
  const [showModal, setShowModal] = useState(false);
  
  const handleConfirm = async () => {
    // Create user logic
    await createUser(formData);
    Toast.success('User created successfully');
    // Modal will close automatically
  };
  
  return (
    <>
      <Button onClick={() => setShowModal(true)}>Create User</Button>
      
      {showModal && (
        <Modal
          title="Create User"
          onClose={() => setShowModal(false)}
          onConfirm={handleConfirm}
          confirmLabel="Create"
          cancelLabel="Cancel"
          size="md"
        >
          <UserForm />
        </Modal>
      )}
    </>
  );
}

Toast

A toast notification system:

import { Toast } from '@unidev-hub/react-toolkit/ui';

// Simple usage
Toast.success('Operation completed successfully');
Toast.error('An error occurred');
Toast.warning('Please review the information');
Toast.info('New updates available');

// With options
Toast.success('User created', {
  title: 'Success',
  duration: 5000,
  position: 'top-right'
});

// Loading toast
const id = Toast.loading('Processing your request');

// Update later
setTimeout(() => {
  Toast.update(id, {
    type: 'success',
    message: 'Processing complete!',
    loading: false,
    autoClose: true
  });
}, 3000);

LoadingIndicator

For displaying loading states:

import { LoadingIndicator, LoadingOverlay } from '@unidev-hub/react-toolkit/ui';

// Simple loading indicator
<LoadingIndicator label="Loading data..." />

// Fullscreen loading
<LoadingIndicator 
  fullscreen 
  label="Initializing application..."
/>

// Loading overlay on a container
<Box position="relative">
  <Content />
  <LoadingOverlay visible={loading} />
</Box>

Authentication

AuthProvider

Provides authentication context to your application:

import { AuthProvider } from '@unidev-hub/react-toolkit/auth';
import { apiClient } from './api';

function App() {
  return (
    <AuthProvider
      apiClient={apiClient}
      loginEndpoint="/auth/login"
      logoutEndpoint="/auth/logout"
      refreshEndpoint="/auth/refresh"
      userEndpoint="/auth/me"
      onLoginSuccess={(user) => console.log('Logged in as', user)}
      onLogout={() => console.log('Logged out')}
    >
      <Router>
        <Routes>{/* Your routes */}</Routes>
      </Router>
    </AuthProvider>
  );
}

ProtectedRoute

Protects routes that require authentication:

import { ProtectedRoute } from '@unidev-hub/react-toolkit/auth';

function AppRoutes() {
  return (
    <Routes>
      <Route path="/login" element={<LoginPage />} />
      
      {/* Basic protected route */}
      <Route element={<ProtectedRoute redirectPath="/login" />}>
        <Route path="/dashboard" element={<Dashboard />} />
      </Route>
      
      {/* With permission requirements */}
      <Route 
        element={
          <ProtectedRoute 
            requiredPermissions={['manage_users']}
            unauthorizedComponent={<UnauthorizedPage />}
          />
        }
      >
        <Route path="/users" element={<UserManagement />} />
      </Route>
      
      {/* With role requirements */}
      <Route 
        element={
          <ProtectedRoute 
            requiredRoles={['admin']}
            unauthorizedComponent={<UnauthorizedPage />}
          />
        }
      >
        <Route path="/admin" element={<AdminDashboard />} />
      </Route>
    </Routes>
  );
}

PermissionGate

Conditionally renders content based on permissions:

import { PermissionGate, useAuth } from '@unidev-hub/react-toolkit/auth';

function UserActions({ user }) {
  const { hasPermission } = useAuth();
  
  return (
    <Group>
      <Button>View Profile</Button>
      
      <PermissionGate permission="edit_users">
        <Button>Edit User</Button>
      </PermissionGate>
      
      <PermissionGate permission="delete_users" fallback={<Tooltip label="You don't have permission">
        <Button disabled>Delete User</Button>
      </PermissionGate>
    </Group>
  );
}

useAuth

Hook for accessing authentication context:

import { useAuth } from '@unidev-hub/react-toolkit/auth';

function LoginPage() {
  const { login, isLoading, error, clearError } = useAuth();
  const [credentials, setCredentials] = useState({ username: '', password: '' });
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      await login(credentials);
      // Redirect on success happens automatically in ProtectedRoute
    } catch (error) {
      // Error is handled by useAuth
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      {error && <Alert color="red">{error}</Alert>}
      <TextInput
        value={credentials.username}
        onChange={(e) => setCredentials({...credentials, username: e.target.value})}
        placeholder="Username"
        required
      />
      <PasswordInput
        value={credentials.password}
        onChange={(e) => setCredentials({...credentials, password: e.target.value})}
        placeholder="Password"
        required
      />
      <Button type="submit" loading={isLoading}>Login</Button>
    </form>
  );
}

Layout

Container, Row, Col

For creating responsive layouts:

import { Container, Row, Col } from '@unidev-hub/react-toolkit/layout';

function PageLayout() {
  return (
    <Container>
      <Row>
        <Col xs={12} md={6}>
          <Card>Left column content</Card>
        </Col>
        <Col xs={12} md={6}>
          <Card>Right column content</Card>
        </Col>
      </Row>
      
      <Row>
        <Col xs={12}>
          <Card>Full width content</Card>
        </Col>
      </Row>
    </Container>
  );
}

PageLayout

For creating page layouts with header, footer, and sidebars:

import { PageLayout } from '@unidev-hub/react-toolkit/layout';

function MyPage() {
  return (
    <PageLayout
      header={<Header />}
      footer={<Footer />}
      sidebar={<Sidebar />}
      padding="md"
    >
      <Container>
        <h1>Page Content</h1>
        <p>Main content goes here</p>
      </Container>
    </PageLayout>
  );
}

SidebarLayout

For creating layouts with a collapsible sidebar:

import { SidebarLayout } from '@unidev-hub/react-toolkit/layout';

function AppLayout() {
  return (
    <SidebarLayout
      header={<AppHeader />}
      sidebar={<NavigationMenu />}
      sidebarWidth={250}
      stickyHeader
      stickySidebar
    >
      <Outlet />
    </SidebarLayout>
  );
}

Context Providers

ThemeProvider

For managing theme preferences:

import { ThemeProvider, useTheme } from '@unidev-hub/react-toolkit/context';

function App() {
  return (
    <ThemeProvider
      initialTheme={{
        primaryColor: 'blue',
        colors: {
          // Custom colors
        }
      }}
      initialMode="system" // 'light', 'dark', or 'system'
    >
      <AppContent />
    </ThemeProvider>
  );
}

function ThemeToggle() {
  const { mode, setMode, toggleColorScheme } = useTheme();
  
  return (
    <Group>
      <Text>Current theme: {mode}</Text>
      <Button onClick={toggleColorScheme}>Toggle Theme</Button>
      <SegmentedControl
        value={mode}
        onChange={setMode}
        data={[
          { value: 'light', label: 'Light' },
          { value: 'dark', label: 'Dark' },
          { value: 'system', label: 'System' },
        ]}
      />
    </Group>
  );
}

ConfigProvider

For managing application configuration:

import { ConfigProvider, useConfig } from '@unidev-hub/react-toolkit/context';

function App() {
  return (
    <ConfigProvider
      initialConfig={{
        apiBaseUrl: 'https://api.example.com',
        itemsPerPage: 25,
        enableAnimations: true,
      }}
      persistConfig={true} // Store in localStorage
    >
      <AppContent />
    </ConfigProvider>
  );
}

function ConfigPanel() {
  const { config, updateConfig, setConfigValue } = useConfig();
  
  return (
    <Stack>
      <TextInput
        label="API URL"
        value={config.apiBaseUrl}
        onChange={(e) => setConfigValue('apiBaseUrl', e.target.value)}
      />
      <NumberInput
        label="Items Per Page"
        value={config.itemsPerPage}
        onChange={(value) => setConfigValue('itemsPerPage', value)}
      />
      <Switch
        label="Enable Animations"
        checked={config.enableAnimations}
        onChange={(e) => setConfigValue('enableAnimations', e.target.checked)}
      />
    </Stack>
  );
}

FeatureFlagProvider

For feature flag management:

import { FeatureFlagProvider, useFeatureFlag } from '@unidev-hub/react-toolkit/context';

function App() {
  return (
    <FeatureFlagProvider
      initialFlags={{
        newDashboard: true,
        betaFeatures: false,
        darkMode: true,
      }}
      persistFlags={true}
      // You can pass remote flags from your API
      remoteFlags={remoteFlags}
    >
      <AppContent />
    </FeatureFlagProvider>
  );
}

function FeatureSection() {
  const { hasFlag, toggleFlag } = useFeatureFlag();
  
  return (
    <div>
      {hasFlag('newDashboard') && <NewDashboard />}
      
      {hasFlag('betaFeatures') && <BetaFeatures />}
      
      <Button onClick={() => toggleFlag('betaFeatures')}>
        {hasFlag('betaFeatures') ? 'Disable' : 'Enable'} Beta Features
      </Button>
    </div>
  );
}

Routing

useNavigation

For programmatic navigation with route definitions:

import { useNavigation } from '@unidev-hub/react-toolkit/routing';

// Define your routes
const routeConfig = {
  routes: [
    { name: 'home', path: '/' },
    { name: 'users', path: '/users' },
    { name: 'userDetails', path: '/users/:id' },
    { name: 'userEdit', path: '/users/:id/edit' },
  ]
};

function UserList() {
  const { navigateTo, goBack } = useNavigation(routeConfig);
  
  const handleViewUser = (userId) => {
    navigateTo('userDetails', { id: userId });
  };
  
  const handleEditUser = (userId) => {
    navigateTo('userEdit', { id: userId });
  };
  
  return (
    <div>
      <Button onClick={goBack}>Back</Button>
      
      {users.map(user => (
        <Group key={user.id}>
          <Text>{user.name}</Text>
          <Button onClick={() => handleViewUser(user.id)}>View</Button>
          <Button onClick={() => handleEditUser(user.id)}>Edit</Button>
        </Group>
      ))}
    </div>
  );
}

NavigationGuard

For protecting routes with custom conditions:

import { NavigationGuard } from '@unidev-hub/react-toolkit/routing';

function CheckoutRoute() {
  // Check if cart has items
  const hasItems = useSelector(state => state.cart.items.length > 0);
  
  return (
    <NavigationGuard
      condition={hasItems}
      redirectTo="/cart"
      onRedirect={() => Toast.warning('Your cart is empty')}
    >
      <CheckoutPage />
    </NavigationGuard>
  );
}

breadcrumbGenerator

For generating breadcrumbs:

import { generateBreadcrumbs } from '@unidev-hub/react-toolkit/routing';

function UserDetailsPage({ userId }) {
  const routeConfig = {
    routes: [
      { name: 'home', path: '/', meta: { title: 'Home' } },
      { name: 'users', path: '/users', meta: { title: 'Users' } },
      { name: 'userDetails', path: '/users/:id', meta: { title: 'User Details' }, parent: 'users' },
    ]
  };
  
  const breadcrumbs = generateBreadcrumbs('userDetails', routeConfig, {
    params: { id: userId },
  });
  
  return (
    <div>
      <Breadcrumbs>
        {breadcrumbs.map((crumb) => (
          <Link key={crumb.path} to={crumb.path}>
            {crumb.label}
          </Link>
        ))}
      </Breadcrumbs>
      
      <h1>User Details</h1>
      {/* Page content */}
    </div>
  );
}

State Management

useStore

For creating and using Zustand stores:

import { createStore, useStore } from '@unidev-hub/react-toolkit/state';

// Create a store
const useCounterStore = createStore({
  count: 0,
  increment: (state, set) => set(state => ({ count: state.count + 1 })),
  decrement: (state, set) => set(state => ({ count: state.count - 1 })),
  reset: (state, set) => set({ count: 0 }),
}, {
  withImmer: true, // Enable Immer for simpler state updates
  persist: true,   // Persist to localStorage
  storageKey: 'counter',
});

// Use the store in components
function Counter() {
  const count = useStore(useCounterStore, state => state.count);
  const { increment, decrement, reset } = useStore(useCounterStore, state => ({
    increment: state.increment,
    decrement: state.decrement,
    reset: state.reset,
  }));
  
  return (
    <Group>
      <Text>Count: {count}</Text>
      <Button onClick={increment}>+</Button>
      <Button onClick={decrement}>-</Button>
      <Button onClick={reset}>Reset</Button>
    </Group>
  );
}

useSelector

For creating and using selectors:

import { 
  useSelector, 
  createSelector, 
  createStructuredSelector 
} from '@unidev-hub/react-toolkit/state';

// Create selectors
const getTodos = state => state.todos;
const getFilter = state => state.filter;

// Create a memoized selector
const getFilteredTodos = createSelector(
  [getTodos, getFilter],
  (todos, filter) => {
    if (filter === 'all') return todos;
    return todos.filter(todo => todo.status === filter);
  }
);

// Use selectors in components
function TodoList() {
  const filteredTodos = useSelector(useAppStore, getFilteredTodos);
  
  // Using multiple selectors at once
  const { todos, filter, loading } = useSelectors(useAppStore, {
    todos: getTodos,
    filter: getFilter,
    loading: state => state.loading,
  });
  
  return (
    <div>
      {filteredTodos.map(todo => (
        <TodoItem key={todo.id} todo={todo} />
      ))}
    </div>
  );
}

Error Handling

ErrorBoundary

For catching and handling React errors:

import { ErrorBoundary, ErrorFallback } from '@unidev-hub/react-toolkit/error';

function App() {
  return (
    <ErrorBoundary
      fallback={<ErrorFallback />}
      onError={(error, errorInfo) => {
        // Log to error reporting service
        console.error('Caught error:', error, errorInfo);
      }}
    >
      <YourApplication />
    </ErrorBoundary>
  );
}

useErrorHandler

For manual error handling:

import { useErrorHandler } from '@unidev-hub/react-toolkit/error';

function DataLoader() {
  const handleError = useErrorHandler();
  
  const fetchData = async () => {
    try {
      const data = await api.fetchData();
      return data;
    } catch (error) {
      // This will be caught by the nearest ErrorBoundary
      handleError(error);
      
      // Or you can handle it inline
      return [];
    }
  };
  
  return (
    <div>
      <Button onClick={fetchData}>Load Data</Button>
    </div>
  );
}

🔄 Integration with Unidev Hub Ecosystem

This toolkit is designed to work seamlessly with other packages in the Unidev Hub ecosystem:

Integration with @unidev-hub/api-client

The data fetching hooks use the BaseApiClient and ResourceClient from @unidev-hub/api-client:

import { BaseApiClient } from '@unidev-hub/api-client';
import { useResourceList, useResourceItem } from '@unidev-hub/react-toolkit/data-fetching';

// Create API client instance
const apiClient = new BaseApiClient({
  baseURL: 'https://api.example.com',
  // Other options
});

// Use it with data fetching hooks
function UsersPage() {
  const { data, loading } = useResourceList(apiClient, '/users');
  
  // ...
}

Integration with @unidev-hub/core-utils

Many components use utilities from @unidev-hub/core-utils:

import { useResourceList } from '@unidev-hub/react-toolkit/data-fetching';
import { QueryBuilder } from '@unidev-hub/api-client';
import { string, array } from '@unidev-hub/core-utils';

function SearchableUserList({ query }) {
  const { data, loading } = useResourceList(apiClient, '/users', {
    queryParams: {
      search: string.trim(query),
      sort: 'createdAt',
      order: 'desc'
    }
  });
  
  // Use array utilities on the data
  const uniqueRoles = array.unique(data.map(user => user.role));
  
  // ...
}

🎨 Theming

The toolkit uses Mantine UI's theming system. You can customize the look and feel of all components by configuring the theme in the MantineProvider:

import { MantineProvider, createTheme } from '@mantine/core';
import { ThemeProvider } from '@unidev-hub/react-toolkit/context';

// Create a custom theme
const mantineTheme = createTheme({
  primaryColor: 'indigo',
  colors: {
    brand: [
      '#f0f4ff', /* 0 */
      '#e0e7ff', /* 1 */
      '#c7d2fe', /* 2 */
      '#a5b4fc', /* 3 */
      '#818cf8', /* 4 */
      '#6366f1', /* 5 - Primary */
      '#4f46e5', /* 6 */
      '#4338ca', /* 7 */
      '#3730a3', /* 8 */
      '#312e81', /* 9 */
    ],
  },
  fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
  defaultRadius: 'md',
  components: {
    Button: {
      defaultProps: {
        radius: 'md',
      },
    },
    Card: {
      defaultProps: {
        p: 'lg',
        withBorder: true,
      },
    },
    // Other component overrides
  },
});

function App() {
  return (
    <ThemeProvider initialTheme={mantineTheme} initialMode="system">
      <MantineProvider theme={mantineTheme}>
        <YourApp />
      </MantineProvider>
    </ThemeProvider>
  );
}

State Management

The toolkit provides a powerful, type-safe state management solution based on Zustand with additional utilities for selectors, persistence, and more.

createStore

For creating custom Zustand stores with middleware support:

import { createStore } from '@unidev-hub/react-toolkit/state';

// Create a store with middleware options
const useCounterStore = createStore(
  { count: 0 }, // Initial state
  {
    immer: true,      // Enable Immer for simpler state updates
    persist: true,    // Enable persistence
    persistOptions: {
      name: 'counter-storage', // Storage key name
    },
    onInit: (store) => {
      // Run initialization logic when store is created
      console.log('Store initialized with:', store.getState());
    }
  }
);

// Create a store with actions
const useTodoStore = createStore(
  { 
    todos: [],
    loading: false,
    error: null
  },
  {
    immer: true,
    initializers: [
      // Add slices of functionality
      createStoreSlice((set, get) => ({
        // Actions can be added here
        addTodo: (text) => set(state => {
          state.todos.push({ id: Date.now(), text, completed: false });
        }),
        toggleTodo: (id) => set(state => {
          const todo = state.todos.find(t => t.id === id);
          if (todo) todo.completed = !todo.completed;
        }),
        removeTodo: (id) => set(state => {
          state.todos = state.todos.filter(t => t.id !== id);
        })
      }))
    ]
  }
);

useStore

For accessing store state and actions:

import { useStore } from '@unidev-hub/react-toolkit/state';

function Counter() {
  // Get the entire store state
  const state = useStore(useCounterStore);
  
  // Get a specific state value
  const count = useStore(useCounterStore, state => state.count);
  
  // Manual updates using setState
  const increment = () => {
    useCounterStore.setState(state => ({ count: state.count + 1 }));
  };
  
  return (
    <Button onClick={increment}>
      Count: {count}
    </Button>
  );
}

useSelector and useSelectors

For optimized state selection with memoization:

import { useSelector, useSelectors } from '@unidev-hub/react-toolkit/state';

function TodoApp() {
  // Select a single derived value
  const incompleteTodos = useSelector(
    useTodoStore,
    state => state.todos.filter(todo => !todo.completed)
  );
  
  // Select multiple values at once with a single subscription
  const { todos, loading, error } = useSelectors(useTodoStore, {
    todos: state => state.todos,
    loading: state => state.loading,
    error: state => state.error
  });
  
  // Select values with custom equality comparison
  const todoCount = useSelector(
    useTodoStore,
    state => state.todos.length,
    (prev, next) => prev === next // Custom equality function
  );
  
  return (
    <div>
      <Text>Incomplete todos: {incompleteTodos.length}</Text>
      <Text>Total todos: {todoCount}</Text>
      {loading && <Loader />}
      {error && <Alert color="red">{error}</Alert>}
    </div>
  );
}

Selector Utilities

For creating memoized and composed selectors:

import { 
  createSelector, 
  createStructuredSelector,
  createMemoizedSelector
} from '@unidev-hub/react-toolkit/state';

// Basic selectors
const getTodos = state => state.todos;
const getFilter = state => state.filter;

// Composed selector that combines other selectors
const getFilteredTodos = createSelector(
  [getTodos, getFilter],
  (todos, filter) => {
    switch(filter) {
      case 'completed':
        return todos.filter(todo => todo.completed);
      case 'active':
        return todos.filter(todo => !todo.completed);
      default:
        return todos;
    }
  }
);

// Structured selector that creates an object of results
const getTodoStats = createStructuredSelector({
  all: getTodos,
  active: state => getTodos(state).filter(todo => !todo.completed),
  completed: state => getTodos(state).filter(todo => todo.completed),
  counts: state => ({
    total: getTodos(state).length,
    active: getTodos(state).filter(todo => !todo.completed).length,
    completed: getTodos(state).filter(todo => todo.completed).length
  })
});

// Memoized selector for expensive computations
const getExpensiveComputation = createMemoizedSelector(
  state => {
    // This will only recompute when state.data changes
    console.log('Computing expensive result...');
    return expensiveOperation(state.data);
  }
);

// Use these selectors in your components
function TodoStats() {
  // Get all the todo stats at once with a single subscription
  const stats = useSelector(useTodoStore, getTodoStats);
  
  return (
    <div>
      <Text>Total: {stats.counts.total}</Text>
      <Text>Active: {stats.counts.active}</Text>
      <Text>Completed: {stats.counts.completed}</Text>
    </div>
  );
}

Advanced Store Techniques

Creating modular store slices:

import { createStore, createStoreSlice } from '@unidev-hub/react-toolkit/state';

// Create reusable slices of state
const createUserSlice = createStoreSlice((set, get) => ({
  user: null,
  loading: false,
  error: null,
  
  fetchUser: async (id) => {
    set({ loading: true, error: null });
    try {
      const user = await api.getUser(id);
      set({ user, loading: false });
    } catch (error) {
      set({ error: error.message, loading: false });
    }
  },
  
  logout: () => set({ user: null })
}));

const createSettingsSlice = createStoreSlice((set, get) => ({
  settings: {
    darkMode: false,
    fontSize: 'medium',
    notifications: true
  },
  
  updateSettings: (newSettings) => set(state => {
    state.settings = { ...state.settings, ...newSettings };
  }),
  
  resetSettings: () => set(state => {
    state.settings = {
      darkMode: false,
      fontSize: 'medium',
      notifications: true
    };
  })
}));

// Combine slices into a complete store
const useAppStore = createStore(
  {}, // Empty initial state, slices provide their own
  {
    immer: true,
    persist: true,
    persistOptions: {
      name: 'app-storage',
      partialize: (state) => ({
        // Only persist certain parts of the state
        settings: state.settings,
        // Don't persist user for security
      })
    },
    initializers: [
      createUserSlice,
      createSettingsSlice
    ]
  }
);

useDerivedValue

For efficiently computing derived values:

import { useDerivedValue } from '@unidev-hub/react-toolkit/state';

function TodoSummary() {
  // Compute a derived value from store state with dependencies
  const summary = useDerivedValue(
    useTodoStore,
    (state) => {
      const total = state.todos.length;
      const completed = state.todos.filter(t => t.completed).length;
      
      return {
        total,
        completed,
        active: total - completed,
        percentComplete: total > 0 ? (completed / total) * 100 : 0
      };
    },
    [] // Dependencies - recompute if they change
  );
  
  return (
    <Stack>
      <Text>Total: {summary.total}</Text>
      <Text>Completed: {summary.completed}</Text>
      <Text>Active: {summary.active}</Text>
      <Progress value={summary.percentComplete} />
    </Stack>
  );
}

useStoreReset

For easily resetting store state:

import { useStoreReset } from '@unidev-hub/react-toolkit/state';

function ResetButton() {
  // Create a reset function for the store
  const resetTodos = useStoreReset(useTodoStore, { todos: [], loading: false, error: null });
  
  return (
    <Button onClick={resetTodos} color="red">
      Reset All Todos
    </Button>
  );
}

📖 API Reference

For complete details on all components, hooks, and utilities, see our API documentation.

Components Structure

The toolkit is organized into the following main sections:

@unidev-hub/react-toolkit/
├── data-fetching/       - Data fetching hooks and components
├── forms/               - Form components and hooks
├── ui/                  - UI components (tables, modals, etc.)
├── auth/                - Authentication components
├── layout/              - Layout components
├── context/             - Context providers 
├── routing/             - Routing utilities
├── state/               - State management utilities
├── error/               - Error handling components
└── types/               - TypeScript types

Each section exports its own set of components, hooks, and utilities. You can import them directly from the specific section to optimize your bundle size through tree-shaking.

🌐 Browser Compatibility

This toolkit is compatible with all modern browsers. For older browsers, appropriate polyfills may be required.

  • Chrome 60+
  • Firefox 55+
  • Safari 10.1+
  • Edge 16+

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Please make sure to update tests as appropriate and follow the existing code style.

Development

To start development:

# Clone the repository
git clone https://github.com/unidev-hub/react-toolkit.git
cd react-toolkit

# Install dependencies
npm install

# Start development environment
npm run dev

# Run tests
npm test

# Build the library
npm run build

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.


Built with ❤️ by the Unidev Hub team.

Package Sidebar

Install

npm i @unidev-hub/react-toolkit

Weekly Downloads

7

Version

0.1.1

License

MIT

Unpacked Size

1.02 MB

Total Files

8

Last publish

Collaborators

  • mayu03
  • samyak1988