@geee-be/react-utils 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.copilot-instructions.md +371 -0
- package/CHANGELOG.md +12 -0
- package/README.md +413 -0
- package/dist/hooks/use-history-state.js +0 -3
- package/package.json +6 -6
- package/src/helpers/monitor.tsx +1 -1
- package/src/helpers/sub-component.tsx +2 -2
- package/src/hooks/use-history-state.ts +0 -4
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# @geee-be/react-utils - Copilot Instructions
|
|
2
|
+
|
|
3
|
+
## Package Overview
|
|
4
|
+
A collection of reusable React hooks and utility functions designed to enhance React applications with common functionality patterns. This package provides lightweight, well-tested utilities that work seamlessly with modern React patterns, SSR frameworks, and TypeScript. Focuses on performance, type safety, and developer experience.
|
|
5
|
+
|
|
6
|
+
## Package Structure
|
|
7
|
+
```
|
|
8
|
+
src/
|
|
9
|
+
├── helpers/ # Utility functions and components
|
|
10
|
+
│ ├── index.ts # Helper exports
|
|
11
|
+
│ ├── client-only.tsx # Client-side only rendering wrapper
|
|
12
|
+
│ ├── monitor.tsx # Performance monitoring utilities
|
|
13
|
+
│ ├── ripple.ts # Material Design ripple effect
|
|
14
|
+
│ └── sub-component.tsx # Component composition helpers
|
|
15
|
+
├── hooks/ # Custom React hooks
|
|
16
|
+
│ ├── index.ts # Hook exports
|
|
17
|
+
│ ├── state.util.ts # State management utilities
|
|
18
|
+
│ ├── use-broadcast-channel.ts # Cross-tab communication
|
|
19
|
+
│ ├── use-history-state.ts # Browser history state management
|
|
20
|
+
│ ├── use-is-client.ts # Client-side detection
|
|
21
|
+
│ ├── use-is-mobile.ts # Mobile device detection
|
|
22
|
+
│ ├── use-local-state.ts # Enhanced local state management
|
|
23
|
+
│ └── use-local-state.stories.tsx # Storybook examples
|
|
24
|
+
└── index.ts # Package main export
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Package Structure
|
|
28
|
+
```
|
|
29
|
+
src/
|
|
30
|
+
├── helpers/ # Utility functions and components
|
|
31
|
+
│ ├── client-only.tsx # Client-side only rendering wrapper
|
|
32
|
+
│ ├── monitor.tsx # Performance monitoring utilities
|
|
33
|
+
│ ├── ripple.ts # Material Design ripple effect
|
|
34
|
+
│ └── sub-component.tsx # Component composition helpers
|
|
35
|
+
└── hooks/ # Custom React hooks
|
|
36
|
+
├── state.util.ts # State management utilities
|
|
37
|
+
├── use-broadcast-channel.ts # Cross-tab communication
|
|
38
|
+
├── use-history-state.ts # Browser history state management
|
|
39
|
+
├── use-is-client.ts # Client-side detection
|
|
40
|
+
├── use-is-mobile.ts # Mobile device detection
|
|
41
|
+
└── use-local-state.ts # Enhanced local state management
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Key Dependencies
|
|
45
|
+
- **@uidotdev/usehooks**: Additional utility hooks
|
|
46
|
+
- **React 18+**: Modern React features and patterns
|
|
47
|
+
|
|
48
|
+
## Hook Implementation Patterns
|
|
49
|
+
|
|
50
|
+
### State Management Hooks
|
|
51
|
+
- `useLocalState`: Enhanced useState with localStorage persistence and cross-tab sync
|
|
52
|
+
- `useHistoryState`: State management with browser history integration
|
|
53
|
+
- State utilities for common patterns and transformations
|
|
54
|
+
|
|
55
|
+
### Environment Detection Hooks
|
|
56
|
+
- `useIsClient`: Detect client-side rendering vs SSR (prevents hydration mismatches)
|
|
57
|
+
- `useIsMobile`: Responsive mobile device detection with customizable breakpoints
|
|
58
|
+
|
|
59
|
+
### Communication Hooks
|
|
60
|
+
- `useBroadcastChannel`: Cross-tab/window communication using Broadcast Channel API
|
|
61
|
+
|
|
62
|
+
### Performance Considerations
|
|
63
|
+
- All hooks use proper dependency arrays and cleanup
|
|
64
|
+
- Memoization implemented where beneficial for performance
|
|
65
|
+
- Automatic event listener cleanup on component unmount
|
|
66
|
+
- SSR-safe initialization patterns to prevent hydration issues
|
|
67
|
+
|
|
68
|
+
## Helper Functions
|
|
69
|
+
|
|
70
|
+
### Component Utilities
|
|
71
|
+
- `ClientOnly`: Wrapper for client-side only rendering
|
|
72
|
+
- `SubComponent`: Utilities for component composition patterns
|
|
73
|
+
- `Monitor`: Performance monitoring and debugging tools
|
|
74
|
+
|
|
75
|
+
### Effects and Interactions
|
|
76
|
+
- `ripple`: Material Design ripple effect implementation
|
|
77
|
+
|
|
78
|
+
## Development Patterns
|
|
79
|
+
|
|
80
|
+
### Hook Implementation
|
|
81
|
+
- Follow React hooks rules and conventions
|
|
82
|
+
- Use TypeScript for type safety
|
|
83
|
+
- Implement proper cleanup in useEffect
|
|
84
|
+
- Support SSR/client-side rendering differences
|
|
85
|
+
- Provide sensible default values
|
|
86
|
+
|
|
87
|
+
### Utility Functions
|
|
88
|
+
- Pure functions where possible
|
|
89
|
+
- Proper error handling
|
|
90
|
+
- TypeScript strict mode compliance
|
|
91
|
+
- Minimal external dependencies
|
|
92
|
+
|
|
93
|
+
## TypeScript Support
|
|
94
|
+
- Full TypeScript definitions for all exports
|
|
95
|
+
- Generic types for flexible hook APIs
|
|
96
|
+
- Proper inference for hook return types
|
|
97
|
+
- Strict type checking enabled
|
|
98
|
+
|
|
99
|
+
## Usage Examples
|
|
100
|
+
|
|
101
|
+
### Enhanced State Management
|
|
102
|
+
```typescript
|
|
103
|
+
import { useLocalState, useHistoryState } from '@geee-be/react-utils';
|
|
104
|
+
|
|
105
|
+
// Persistent state with localStorage
|
|
106
|
+
function UserPreferences() {
|
|
107
|
+
const [settings, setSettings] = useLocalState('user-settings', {
|
|
108
|
+
theme: 'light',
|
|
109
|
+
language: 'en',
|
|
110
|
+
notifications: true
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const toggleTheme = () => {
|
|
114
|
+
setSettings(prev => ({
|
|
115
|
+
...prev,
|
|
116
|
+
theme: prev.theme === 'light' ? 'dark' : 'light'
|
|
117
|
+
}));
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return <button onClick={toggleTheme}>Theme: {settings.theme}</button>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Browser history integration
|
|
124
|
+
function SearchPage() {
|
|
125
|
+
const [query, setQuery] = useHistoryState('search', '');
|
|
126
|
+
const [filters, setFilters] = useHistoryState('filters', {});
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<div>
|
|
130
|
+
<input
|
|
131
|
+
value={query}
|
|
132
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
133
|
+
placeholder="Search..."
|
|
134
|
+
/>
|
|
135
|
+
{/* Filters persist in browser history */}
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### SSR-Safe Environment Detection
|
|
142
|
+
```typescript
|
|
143
|
+
import { useIsClient, useIsMobile } from '@geee-be/react-utils';
|
|
144
|
+
|
|
145
|
+
function ResponsiveComponent() {
|
|
146
|
+
const isClient = useIsClient();
|
|
147
|
+
const isMobile = useIsMobile(768); // Custom breakpoint
|
|
148
|
+
|
|
149
|
+
// Prevent hydration mismatch
|
|
150
|
+
if (!isClient) {
|
|
151
|
+
return <div>Loading...</div>;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<div className={isMobile ? 'mobile-layout' : 'desktop-layout'}>
|
|
156
|
+
<h1>Welcome</h1>
|
|
157
|
+
{isMobile ? <MobileNavigation /> : <DesktopNavigation />}
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Cross-Tab Communication
|
|
164
|
+
```typescript
|
|
165
|
+
import { useBroadcastChannel } from '@geee-be/react-utils';
|
|
166
|
+
|
|
167
|
+
function MultiTabSync() {
|
|
168
|
+
const [message, postMessage] = useBroadcastChannel('app-sync');
|
|
169
|
+
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
if (message) {
|
|
172
|
+
console.log('Received from another tab:', message);
|
|
173
|
+
// Handle cross-tab state synchronization
|
|
174
|
+
}
|
|
175
|
+
}, [message]);
|
|
176
|
+
|
|
177
|
+
const notifyOtherTabs = () => {
|
|
178
|
+
postMessage({
|
|
179
|
+
type: 'USER_LOGIN',
|
|
180
|
+
user: { id: 1, name: 'John' },
|
|
181
|
+
timestamp: Date.now()
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
return <button onClick={notifyOtherTabs}>Login & Sync</button>;
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Utility Components
|
|
190
|
+
```typescript
|
|
191
|
+
import { ClientOnly, ripple } from '@geee-be/react-utils';
|
|
192
|
+
|
|
193
|
+
// Client-only rendering
|
|
194
|
+
function App() {
|
|
195
|
+
return (
|
|
196
|
+
<div>
|
|
197
|
+
<h1>Server + Client Rendered</h1>
|
|
198
|
+
<ClientOnly>
|
|
199
|
+
<ExpensiveClientComponent />
|
|
200
|
+
<BrowserAPIComponent />
|
|
201
|
+
</ClientOnly>
|
|
202
|
+
</div>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Material Design ripple effect
|
|
207
|
+
function RippleButton() {
|
|
208
|
+
const handleClick = (event) => {
|
|
209
|
+
ripple(event.currentTarget, {
|
|
210
|
+
duration: 600,
|
|
211
|
+
color: 'rgba(59, 130, 246, 0.3)'
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<button
|
|
217
|
+
onClick={handleClick}
|
|
218
|
+
className="relative overflow-hidden bg-blue-500 text-white px-4 py-2 rounded"
|
|
219
|
+
>
|
|
220
|
+
Click for Ripple
|
|
221
|
+
</button>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Build Configuration
|
|
227
|
+
- **TypeScript Compilation**: Direct tsc build to `dist/`
|
|
228
|
+
- **Type Exports**: Source types exported directly (`src/index.ts`)
|
|
229
|
+
- **Development Mode**: Watch mode with `tsc --watch`
|
|
230
|
+
- **ES Modules**: Modern ESM output format
|
|
231
|
+
|
|
232
|
+
## Testing & Development
|
|
233
|
+
- **Storybook Integration**: Stories for demonstrating hook usage
|
|
234
|
+
- **Type Safety**: Strict TypeScript configuration
|
|
235
|
+
- **Linting**: Biome for code quality
|
|
236
|
+
- **Development Server**: Watch mode for rapid iteration
|
|
237
|
+
|
|
238
|
+
## Best Practices
|
|
239
|
+
|
|
240
|
+
### Hook Development Guidelines
|
|
241
|
+
- Keep hooks focused on single responsibilities
|
|
242
|
+
- Follow React hooks rules and conventions
|
|
243
|
+
- Use TypeScript for comprehensive type safety
|
|
244
|
+
- Implement proper cleanup in useEffect hooks
|
|
245
|
+
- Handle edge cases (SSR, cleanup, errors gracefully)
|
|
246
|
+
- Provide sensible default values for all parameters
|
|
247
|
+
- Consider performance implications and optimize re-renders
|
|
248
|
+
- Support both development and production environments
|
|
249
|
+
|
|
250
|
+
### API Design Principles
|
|
251
|
+
- Provide TypeScript definitions for all public APIs
|
|
252
|
+
- Use proper dependency arrays in useEffect
|
|
253
|
+
- Implement consistent return patterns across similar hooks
|
|
254
|
+
- Support generic types for flexible, reusable APIs
|
|
255
|
+
- Follow React's built-in hook naming conventions
|
|
256
|
+
- Document complex hook behaviors with JSDoc comments
|
|
257
|
+
|
|
258
|
+
### Performance Optimization
|
|
259
|
+
- Use useCallback and useMemo appropriately
|
|
260
|
+
- Implement proper event listener cleanup
|
|
261
|
+
- Avoid unnecessary re-renders through careful state management
|
|
262
|
+
- Use refs for values that don't trigger re-renders
|
|
263
|
+
- Implement debouncing for expensive operations
|
|
264
|
+
- Consider memory usage in long-running applications
|
|
265
|
+
|
|
266
|
+
### Error Handling
|
|
267
|
+
- Provide graceful fallbacks for failed operations
|
|
268
|
+
- Handle browser API unavailability
|
|
269
|
+
- Use try-catch blocks for potentially failing operations
|
|
270
|
+
- Log errors appropriately for debugging
|
|
271
|
+
- Provide meaningful error messages for developers
|
|
272
|
+
- Support error boundaries where applicable
|
|
273
|
+
|
|
274
|
+
### Testing Strategies
|
|
275
|
+
- Test hooks in isolation using React Testing Library
|
|
276
|
+
- Verify SSR compatibility with server-side rendering tests
|
|
277
|
+
- Test browser API integration with appropriate mocks
|
|
278
|
+
- Validate TypeScript types compile correctly
|
|
279
|
+
- Test edge cases and error conditions
|
|
280
|
+
- Ensure cleanup functions work properly
|
|
281
|
+
|
|
282
|
+
## Framework-Specific Usage
|
|
283
|
+
|
|
284
|
+
### Next.js Integration
|
|
285
|
+
```typescript
|
|
286
|
+
// App Router (app directory)
|
|
287
|
+
import { useLocalState, useIsClient } from '@geee-be/react-utils';
|
|
288
|
+
|
|
289
|
+
export default function ClientComponent() {
|
|
290
|
+
const isClient = useIsClient();
|
|
291
|
+
const [data, setData] = useLocalState('app-data', null);
|
|
292
|
+
|
|
293
|
+
if (!isClient) return <div>Loading...</div>;
|
|
294
|
+
return <div>{data}</div>;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Pages Router
|
|
298
|
+
import { useIsMobile } from '@geee-be/react-utils';
|
|
299
|
+
|
|
300
|
+
export default function Page() {
|
|
301
|
+
const isMobile = useIsMobile();
|
|
302
|
+
return <div>{isMobile ? 'Mobile' : 'Desktop'}</div>;
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Performance Monitoring
|
|
307
|
+
```typescript
|
|
308
|
+
import { Monitor } from '@geee-be/react-utils';
|
|
309
|
+
|
|
310
|
+
// Use Monitor utilities for performance tracking
|
|
311
|
+
function App() {
|
|
312
|
+
useEffect(() => {
|
|
313
|
+
Monitor.trackRender('App');
|
|
314
|
+
return () => Monitor.trackUnmount('App');
|
|
315
|
+
}, []);
|
|
316
|
+
|
|
317
|
+
return <div>App Content</div>;
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## SSR Considerations
|
|
322
|
+
|
|
323
|
+
### Hydration Safety Patterns
|
|
324
|
+
All hooks are designed to work with server-side rendering frameworks without hydration mismatches:
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
// Safe pattern for SSR
|
|
328
|
+
function ThemeProvider({ children }) {
|
|
329
|
+
const [theme, setTheme] = useLocalState('theme', 'light');
|
|
330
|
+
const isClient = useIsClient();
|
|
331
|
+
|
|
332
|
+
// Use fallback during SSR to prevent hydration mismatch
|
|
333
|
+
const currentTheme = isClient ? theme : 'light';
|
|
334
|
+
|
|
335
|
+
return (
|
|
336
|
+
<div data-theme={currentTheme} className={currentTheme}>
|
|
337
|
+
{children}
|
|
338
|
+
</div>
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Safe mobile detection
|
|
343
|
+
function ResponsiveLayout() {
|
|
344
|
+
const isClient = useIsClient();
|
|
345
|
+
const isMobile = useIsMobile();
|
|
346
|
+
|
|
347
|
+
// Always render the same structure on server
|
|
348
|
+
if (!isClient) {
|
|
349
|
+
return <div className="responsive-container">Loading...</div>;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return (
|
|
353
|
+
<div className="responsive-container">
|
|
354
|
+
{isMobile ? <MobileLayout /> : <DesktopLayout />}
|
|
355
|
+
</div>
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Framework Integration
|
|
361
|
+
- **Next.js**: Full compatibility with App Router and Pages Router
|
|
362
|
+
- **Remix**: Works with Remix's SSR patterns and hydration
|
|
363
|
+
- **Gatsby**: Compatible with static site generation
|
|
364
|
+
- **Vite SSR**: Supports Vite's SSR implementation
|
|
365
|
+
- **Client-only apps**: Works perfectly in SPA environments
|
|
366
|
+
|
|
367
|
+
### Browser API Safety
|
|
368
|
+
- Hooks gracefully handle browser API availability
|
|
369
|
+
- Proper feature detection prevents runtime errors
|
|
370
|
+
- Fallback behavior for server environments
|
|
371
|
+
- Progressive enhancement patterns supported
|
package/CHANGELOG.md
CHANGED
package/README.md
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
# @geee-be/react-utils
|
|
2
|
+
|
|
3
|
+
A collection of powerful, type-safe React hooks and utility functions designed to enhance modern React applications. Built with TypeScript, SSR compatibility, and performance in mind.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@geee-be/react-utils)
|
|
6
|
+
[](https://www.typescriptlang.org)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
## ✨ Features
|
|
10
|
+
|
|
11
|
+
- 🪝 **Custom React Hooks** - Enhanced state management and utility hooks
|
|
12
|
+
- 🔒 **Type Safe** - Full TypeScript support with comprehensive type definitions
|
|
13
|
+
- 🌐 **SSR Compatible** - Works seamlessly with Next.js and other SSR frameworks
|
|
14
|
+
- ⚡ **Performance Focused** - Optimized for minimal re-renders and memory usage
|
|
15
|
+
- 🧩 **Modular** - Tree-shakeable exports for optimal bundle size
|
|
16
|
+
- 🔧 **Browser API Wrappers** - Safe abstractions for modern browser features
|
|
17
|
+
- 📱 **Device Detection** - Responsive utilities for different environments
|
|
18
|
+
- 🔄 **State Persistence** - Local storage integration with sync capabilities
|
|
19
|
+
|
|
20
|
+
## 📋 Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @geee-be/react-utils
|
|
24
|
+
# or
|
|
25
|
+
pnpm add @geee-be/react-utils
|
|
26
|
+
# or
|
|
27
|
+
yarn add @geee-be/react-utils
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Peer Dependencies
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install react react-dom
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Required versions:**
|
|
37
|
+
- React >= 18.0.0
|
|
38
|
+
|
|
39
|
+
## 🚀 Quick Start
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { useLocalState, useIsClient, useIsMobile } from '@geee-be/react-utils';
|
|
43
|
+
|
|
44
|
+
function MyComponent() {
|
|
45
|
+
const [theme, setTheme] = useLocalState('theme', 'light');
|
|
46
|
+
const isClient = useIsClient();
|
|
47
|
+
const isMobile = useIsMobile();
|
|
48
|
+
|
|
49
|
+
if (!isClient) {
|
|
50
|
+
return <div>Loading...</div>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className={`theme-${theme} ${isMobile ? 'mobile' : 'desktop'}`}>
|
|
55
|
+
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
|
|
56
|
+
Toggle Theme
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 🪝 Hooks Overview
|
|
64
|
+
|
|
65
|
+
### State Management
|
|
66
|
+
|
|
67
|
+
#### `useLocalState`
|
|
68
|
+
Enhanced `useState` with localStorage persistence and cross-tab synchronization.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { useLocalState } from '@geee-be/react-utils';
|
|
72
|
+
|
|
73
|
+
function Settings() {
|
|
74
|
+
const [settings, setSettings] = useLocalState('user-settings', {
|
|
75
|
+
theme: 'light',
|
|
76
|
+
language: 'en'
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div>
|
|
81
|
+
<button onClick={() => setSettings(prev => ({ ...prev, theme: 'dark' }))}>
|
|
82
|
+
Enable Dark Mode
|
|
83
|
+
</button>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Features:**
|
|
90
|
+
- Automatic localStorage synchronization
|
|
91
|
+
- Cross-tab state updates
|
|
92
|
+
- SSR-safe initialization
|
|
93
|
+
- Type-safe with TypeScript inference
|
|
94
|
+
|
|
95
|
+
#### `useHistoryState`
|
|
96
|
+
State management with browser history integration for navigation state persistence.
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
import { useHistoryState } from '@geee-be/react-utils';
|
|
100
|
+
|
|
101
|
+
function SearchPage() {
|
|
102
|
+
const [query, setQuery] = useHistoryState('search', '');
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<input
|
|
106
|
+
value={query}
|
|
107
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
108
|
+
placeholder="Search..."
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Environment Detection
|
|
115
|
+
|
|
116
|
+
#### `useIsClient`
|
|
117
|
+
Detect client-side rendering vs server-side rendering to prevent hydration mismatches.
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
import { useIsClient } from '@geee-be/react-utils';
|
|
121
|
+
|
|
122
|
+
function ClientOnlyFeature() {
|
|
123
|
+
const isClient = useIsClient();
|
|
124
|
+
|
|
125
|
+
if (!isClient) {
|
|
126
|
+
return <div>Loading...</div>; // Server-side fallback
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return <ExpensiveClientComponent />;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### `useIsMobile`
|
|
134
|
+
Responsive device detection with customizable breakpoints.
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
import { useIsMobile } from '@geee-be/react-utils';
|
|
138
|
+
|
|
139
|
+
function ResponsiveNav() {
|
|
140
|
+
const isMobile = useIsMobile(768); // Custom breakpoint
|
|
141
|
+
|
|
142
|
+
return isMobile ? <MobileNav /> : <DesktopNav />;
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Communication
|
|
147
|
+
|
|
148
|
+
#### `useBroadcastChannel`
|
|
149
|
+
Cross-tab/window communication using the Broadcast Channel API with fallback.
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
import { useBroadcastChannel } from '@geee-be/react-utils';
|
|
153
|
+
|
|
154
|
+
function ChatApp() {
|
|
155
|
+
const [message, postMessage] = useBroadcastChannel('chat-channel');
|
|
156
|
+
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (message) {
|
|
159
|
+
console.log('Received message:', message);
|
|
160
|
+
// Handle incoming message from other tabs
|
|
161
|
+
}
|
|
162
|
+
}, [message]);
|
|
163
|
+
|
|
164
|
+
const sendMessage = () => {
|
|
165
|
+
postMessage({ text: 'Hello from another tab!', timestamp: Date.now() });
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
return <button onClick={sendMessage}>Send Message</button>;
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## 🛠️ Utility Functions
|
|
173
|
+
|
|
174
|
+
### Component Helpers
|
|
175
|
+
|
|
176
|
+
#### `ClientOnly`
|
|
177
|
+
Wrapper component for client-side only rendering.
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
import { ClientOnly } from '@geee-be/react-utils';
|
|
181
|
+
|
|
182
|
+
function App() {
|
|
183
|
+
return (
|
|
184
|
+
<div>
|
|
185
|
+
<h1>Server and Client Rendered</h1>
|
|
186
|
+
<ClientOnly>
|
|
187
|
+
<ExpensiveClientComponent />
|
|
188
|
+
</ClientOnly>
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
#### `withSubComponents`
|
|
195
|
+
Utility for creating compound components with sub-components.
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
import { withSubComponents } from '@geee-be/react-utils';
|
|
199
|
+
|
|
200
|
+
// Create compound components easily
|
|
201
|
+
const Card = withSubComponents(CardRoot, {
|
|
202
|
+
Header: CardHeader,
|
|
203
|
+
Content: CardContent,
|
|
204
|
+
Footer: CardFooter
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Usage
|
|
208
|
+
<Card>
|
|
209
|
+
<Card.Header>Title</Card.Header>
|
|
210
|
+
<Card.Content>Content here</Card.Content>
|
|
211
|
+
</Card>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
#### `Monitor`
|
|
215
|
+
Development component for monitoring component renders and lifecycle.
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
import { Monitor } from '@geee-be/react-utils';
|
|
219
|
+
|
|
220
|
+
function DebugComponent() {
|
|
221
|
+
return (
|
|
222
|
+
<Monitor label="MyComponent">
|
|
223
|
+
<MyComponent />
|
|
224
|
+
</Monitor>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
// Logs: "MyComponent mounted", "MyComponent rendered 1", etc.
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Effects and Interactions
|
|
231
|
+
|
|
232
|
+
#### `createRipple`
|
|
233
|
+
Material Design ripple effect implementation for click interactions.
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
import { createRipple } from '@geee-be/react-utils';
|
|
237
|
+
|
|
238
|
+
function RippleButton() {
|
|
239
|
+
return (
|
|
240
|
+
<button
|
|
241
|
+
onClick={createRipple}
|
|
242
|
+
className="relative overflow-hidden"
|
|
243
|
+
>
|
|
244
|
+
Click for Ripple Effect
|
|
245
|
+
</button>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Or use with custom event handler
|
|
250
|
+
function CustomButton() {
|
|
251
|
+
const handleClick = (event) => {
|
|
252
|
+
createRipple(event);
|
|
253
|
+
// Your custom logic here
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
return (
|
|
257
|
+
<button
|
|
258
|
+
onClick={handleClick}
|
|
259
|
+
className="relative overflow-hidden"
|
|
260
|
+
>
|
|
261
|
+
Custom Click Handler
|
|
262
|
+
</button>
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
NOTE: it is important that the button has `className="relative"`
|
|
268
|
+
|
|
269
|
+
## 🌐 SSR Compatibility
|
|
270
|
+
|
|
271
|
+
All hooks and utilities are designed to work seamlessly with server-side rendering:
|
|
272
|
+
|
|
273
|
+
### Next.js Integration
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
// pages/_app.tsx or app/layout.tsx
|
|
277
|
+
import { useIsClient } from '@geee-be/react-utils';
|
|
278
|
+
|
|
279
|
+
function MyApp({ Component, pageProps }) {
|
|
280
|
+
const isClient = useIsClient();
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<div>
|
|
284
|
+
<Component {...pageProps} />
|
|
285
|
+
{isClient && <ClientOnlyAnalytics />}
|
|
286
|
+
</div>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Hydration Safety
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
import { useLocalState, useIsClient } from '@geee-be/react-utils';
|
|
295
|
+
|
|
296
|
+
function ThemeProvider({ children }) {
|
|
297
|
+
const [theme, setTheme] = useLocalState('theme', 'light');
|
|
298
|
+
const isClient = useIsClient();
|
|
299
|
+
|
|
300
|
+
// Prevents hydration mismatch
|
|
301
|
+
const currentTheme = isClient ? theme : 'light';
|
|
302
|
+
|
|
303
|
+
return (
|
|
304
|
+
<div data-theme={currentTheme}>
|
|
305
|
+
{children}
|
|
306
|
+
</div>
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## 🎯 Performance Considerations
|
|
312
|
+
|
|
313
|
+
### Optimized Re-renders
|
|
314
|
+
Hooks use React's built-in optimization patterns:
|
|
315
|
+
|
|
316
|
+
```tsx
|
|
317
|
+
// useLocalState only re-renders when the actual value changes
|
|
318
|
+
const [count, setCount] = useLocalState('count', 0);
|
|
319
|
+
|
|
320
|
+
// Callbacks are memoized automatically
|
|
321
|
+
const increment = useCallback(() => setCount(c => c + 1), [setCount]);
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Memory Management
|
|
325
|
+
Automatic cleanup prevents memory leaks:
|
|
326
|
+
|
|
327
|
+
```tsx
|
|
328
|
+
// Broadcast channels are automatically cleaned up on unmount
|
|
329
|
+
const [message, postMessage] = useBroadcastChannel('channel');
|
|
330
|
+
|
|
331
|
+
// Event listeners are properly removed
|
|
332
|
+
const isMobile = useIsMobile();
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## 📱 Framework Integration
|
|
336
|
+
|
|
337
|
+
### Next.js
|
|
338
|
+
```tsx
|
|
339
|
+
// Full SSR support with App Router
|
|
340
|
+
import { useLocalState } from '@geee-be/react-utils';
|
|
341
|
+
|
|
342
|
+
export default function Page() {
|
|
343
|
+
const [data, setData] = useLocalState('page-data', null);
|
|
344
|
+
return <div>{data?.title}</div>;
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Remix
|
|
349
|
+
```tsx
|
|
350
|
+
import { useIsClient } from '@geee-be/react-utils';
|
|
351
|
+
|
|
352
|
+
export default function RemixRoute() {
|
|
353
|
+
const isClient = useIsClient();
|
|
354
|
+
return isClient ? <ClientFeature /> : <ServerFallback />;
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Vite/SPA
|
|
359
|
+
```tsx
|
|
360
|
+
// Works great in client-side apps too
|
|
361
|
+
import { useHistoryState } from '@geee-be/react-utils';
|
|
362
|
+
|
|
363
|
+
function SinglePageApp() {
|
|
364
|
+
const [route, setRoute] = useHistoryState('route', '/');
|
|
365
|
+
return <Router currentRoute={route} />;
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## 🔧 TypeScript Support
|
|
370
|
+
|
|
371
|
+
Full TypeScript support with comprehensive type definitions:
|
|
372
|
+
|
|
373
|
+
```tsx
|
|
374
|
+
import { useLocalState } from '@geee-be/react-utils';
|
|
375
|
+
|
|
376
|
+
interface UserPreferences {
|
|
377
|
+
theme: 'light' | 'dark';
|
|
378
|
+
language: string;
|
|
379
|
+
notifications: boolean;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function UserSettings() {
|
|
383
|
+
// Type is automatically inferred as [UserPreferences, (value: UserPreferences) => void]
|
|
384
|
+
const [prefs, setPrefs] = useLocalState<UserPreferences>('user-prefs', {
|
|
385
|
+
theme: 'light',
|
|
386
|
+
language: 'en',
|
|
387
|
+
notifications: true
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// TypeScript will ensure type safety
|
|
391
|
+
const toggleTheme = () => {
|
|
392
|
+
setPrefs(prev => ({
|
|
393
|
+
...prev,
|
|
394
|
+
theme: prev.theme === 'light' ? 'dark' : 'light'
|
|
395
|
+
}));
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
return <button onClick={toggleTheme}>Toggle Theme</button>;
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## 🔗 Related Packages
|
|
403
|
+
|
|
404
|
+
- **[@geee-be/react-twui](https://www.npmjs.com/package/@geee-be/react-twui)** - UI component library that uses these utilities
|
|
405
|
+
- **[@uidotdev/usehooks](https://usehooks.com/)** - Additional React hooks (included as dependency)
|
|
406
|
+
|
|
407
|
+
## 📄 License
|
|
408
|
+
|
|
409
|
+
MIT License - see [LICENSE](../../LICENSE) file for details.
|
|
410
|
+
|
|
411
|
+
## 🐛 Issues & Support
|
|
412
|
+
|
|
413
|
+
Found a bug or need help? Please [open an issue](https://github.com/geee-be/react-libraries/issues) on GitHub.
|
|
@@ -3,9 +3,6 @@ import { useEffect, useState } from 'react';
|
|
|
3
3
|
import { deserialize, getInitialValue, serialize } from './state.util.js';
|
|
4
4
|
const LOADING = Symbol('loading');
|
|
5
5
|
export const useHistoryState = (key, initialValue, replace = true, options) => {
|
|
6
|
-
if (typeof history === 'undefined') {
|
|
7
|
-
return [undefined, () => { }, false];
|
|
8
|
-
}
|
|
9
6
|
const [value, setValue] = useState(LOADING);
|
|
10
7
|
useEffect(() => {
|
|
11
8
|
setValue(deserialize(history.state?.[key], options?.fromSerializable) ??
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geee-be/react-utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "dist/index.js",
|
|
10
10
|
"types": "src/index.ts",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@uidotdev/usehooks": "^2.4.1"
|
|
13
|
+
},
|
|
11
14
|
"devDependencies": {
|
|
12
15
|
"@storybook/addon-essentials": "^7.6.20",
|
|
13
16
|
"@storybook/addon-interactions": "^7.6.20",
|
|
@@ -17,8 +20,8 @@
|
|
|
17
20
|
"@storybook/react": "^7.6.20",
|
|
18
21
|
"@storybook/react-vite": "^7.6.20",
|
|
19
22
|
"@storybook/test": "^7.6.20",
|
|
20
|
-
"@types/react": "^18.3.
|
|
21
|
-
"@types/react-dom": "^18.3.
|
|
23
|
+
"@types/react": "^18.3.23",
|
|
24
|
+
"@types/react-dom": "^18.3.7",
|
|
22
25
|
"prop-types": "^15.8.1",
|
|
23
26
|
"react": "^18.3.1",
|
|
24
27
|
"react-dom": "^18.3.1",
|
|
@@ -28,9 +31,6 @@
|
|
|
28
31
|
"react": ">=18.0.0",
|
|
29
32
|
"react-dom": ">=18.0.0"
|
|
30
33
|
},
|
|
31
|
-
"dependencies": {
|
|
32
|
-
"@uidotdev/usehooks": "^2.4.1"
|
|
33
|
-
},
|
|
34
34
|
"scripts": {
|
|
35
35
|
"prebuild": "rimraf dist/*",
|
|
36
36
|
"build": "tsc",
|
package/src/helpers/monitor.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useRenderCount } from '@uidotdev/usehooks';
|
|
4
|
-
import {
|
|
4
|
+
import { type FC, type PropsWithChildren, useEffect } from 'react';
|
|
5
5
|
|
|
6
6
|
type Props = PropsWithChildren<{ label: string }>;
|
|
7
7
|
|
|
@@ -5,8 +5,8 @@ export const withSubComponents = <P extends {}, S>(
|
|
|
5
5
|
subComponents: S,
|
|
6
6
|
) =>
|
|
7
7
|
Object.assign(
|
|
8
|
-
forwardRef<unknown, P>((props:
|
|
9
|
-
<RootComponent {...props} ref={ref} />
|
|
8
|
+
forwardRef<unknown, P>((props: unknown, ref: unknown) => (
|
|
9
|
+
<RootComponent {...(props as P)} ref={ref} />
|
|
10
10
|
)),
|
|
11
11
|
subComponents,
|
|
12
12
|
);
|
|
@@ -17,10 +17,6 @@ export const useHistoryState = <T, S = T>(
|
|
|
17
17
|
replace = true,
|
|
18
18
|
options?: SerializationOptions<T, S>,
|
|
19
19
|
): Response<T> => {
|
|
20
|
-
if (typeof history === 'undefined') {
|
|
21
|
-
return [undefined, () => {}, false];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
20
|
const [value, setValue] = useState<T | typeof LOADING>(LOADING);
|
|
25
21
|
|
|
26
22
|
useEffect(() => {
|