A comprehensive collection of React components, hooks, and utilities built with Mantine UI to accelerate development of React applications within the Unidev Hub ecosystem.
- Features
- Installation
- Setup
- Components & Hooks
- Integration with Unidev Hub Ecosystem
- Theming
- API Reference
- Browser Compatibility
- Contributing
- License
- 🔄 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
npm install @unidev-hub/react-toolkit @mantine/core @mantine/hooks @mantine/form @mantine/dates @mantine/notifications @mantine/modals @tabler/icons-react dayjs
yarn add @unidev-hub/react-toolkit @mantine/core @mantine/hooks @mantine/form @mantine/dates @mantine/notifications @mantine/modals @tabler/icons-react dayjs
pnpm add @unidev-hub/react-toolkit @mantine/core @mantine/hooks @mantine/form @mantine/dates @mantine/notifications @mantine/modals @tabler/icons-react dayjs
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
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;
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.
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.
}
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
}
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>
);
}
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
}
This toolkit provides comprehensive form handling with validation, field arrays, and more. The form components are built on top of Mantine's form components.
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' }
]}
/>
);
}
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>
);
}
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>
);
}
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
/>
);
}
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>
)}
</>
);
}
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);
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>
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
This toolkit is designed to work seamlessly with other packages in the Unidev Hub ecosystem:
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');
// ...
}
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));
// ...
}
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>
);
}
The toolkit provides a powerful, type-safe state management solution based on Zustand with additional utilities for selectors, persistence, and more.
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);
})
}))
]
}
);
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>
);
}
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>
);
}
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>
);
}
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
]
}
);
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>
);
}
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>
);
}
For complete details on all components, hooks, and utilities, see our API documentation.
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.
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+
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
Please make sure to update tests as appropriate and follow the existing code style.
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
This project is licensed under the MIT License - see the LICENSE file for details.
Built with ❤️ by the Unidev Hub team.