npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@unidev-hub/react-toolkit

v0.1.1

Published

React-specific components and utilities for Unidev Hub applications

Downloads

16

Readme

@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.