@plugable-io/react 0.0.3 → 0.0.5

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/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React, { ReactNode, CSSProperties } from 'react';
3
- import { BucketClient, FileObject } from '@plugable-io/js';
3
+ import { BucketClient, FileObject, ListResponse } from '@plugable-io/js';
4
4
  export { FileObject, SearchOptions, UpdateOptions } from '@plugable-io/js';
5
5
 
6
6
  type AuthProvider = 'clerk' | 'supabase' | 'firebase';
@@ -11,9 +11,15 @@ interface PlugableProviderProps {
11
11
  authProvider?: AuthProvider;
12
12
  clerkJWTTemplate?: string;
13
13
  baseUrl?: string;
14
+ staleTime?: number;
14
15
  }
15
16
  type PlugableEventType = 'file.uploaded' | 'file.deleted';
16
17
  type EventHandler = (data?: any) => void;
18
+ interface CacheEntry {
19
+ files: FileObject[];
20
+ paging: ListResponse['paging'];
21
+ timestamp: number;
22
+ }
17
23
  interface PlugableContextValue {
18
24
  client: BucketClient;
19
25
  bucketId: string;
@@ -21,8 +27,12 @@ interface PlugableContextValue {
21
27
  emit: (event: PlugableEventType, data?: any) => void;
22
28
  getToken: () => Promise<string> | string;
23
29
  baseUrl?: string;
30
+ staleTime: number;
31
+ getCache: (key: string) => CacheEntry | undefined;
32
+ setCache: (key: string, entry: CacheEntry) => void;
33
+ invalidateCache: (pattern?: string) => void;
24
34
  }
25
- declare function PlugableProvider({ bucketId, children, getToken, authProvider, clerkJWTTemplate, baseUrl, }: PlugableProviderProps): react_jsx_runtime.JSX.Element;
35
+ declare function PlugableProvider({ bucketId, children, getToken, authProvider, clerkJWTTemplate, baseUrl, staleTime, }: PlugableProviderProps): react_jsx_runtime.JSX.Element;
26
36
  declare function usePlugable(): PlugableContextValue;
27
37
 
28
38
  interface DropzoneProps {
@@ -106,6 +116,7 @@ interface UseFilesOptions {
106
116
  autoLoad?: boolean;
107
117
  orderBy?: 'created_at' | 'name' | 'byte_size';
108
118
  orderDirection?: 'asc' | 'desc';
119
+ staleTime?: number;
109
120
  }
110
121
  interface UseFilesResult {
111
122
  files: FileObject[];
@@ -120,6 +131,6 @@ interface UseFilesResult {
120
131
  setPage: (page: number) => void;
121
132
  refresh: () => Promise<void>;
122
133
  }
123
- declare function useFiles({ metadata, startPage, perPage, mediaType, autoLoad, orderBy, orderDirection, }?: UseFilesOptions): UseFilesResult;
134
+ declare function useFiles({ metadata, startPage, perPage, mediaType, autoLoad, orderBy, orderDirection, staleTime, }?: UseFilesOptions): UseFilesResult;
124
135
 
125
136
  export { type AuthProvider, Dropzone, type DropzoneProps, type DropzoneRenderProps, FileImage, type FileImageProps, FileList, type FileListProps, type FileListRenderProps, FilePreview, type FilePreviewProps, PlugableProvider, type PlugableProviderProps, type UseFilesOptions, type UseFilesResult, clearImageCache, useFiles, usePlugable };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React, { ReactNode, CSSProperties } from 'react';
3
- import { BucketClient, FileObject } from '@plugable-io/js';
3
+ import { BucketClient, FileObject, ListResponse } from '@plugable-io/js';
4
4
  export { FileObject, SearchOptions, UpdateOptions } from '@plugable-io/js';
5
5
 
6
6
  type AuthProvider = 'clerk' | 'supabase' | 'firebase';
@@ -11,9 +11,15 @@ interface PlugableProviderProps {
11
11
  authProvider?: AuthProvider;
12
12
  clerkJWTTemplate?: string;
13
13
  baseUrl?: string;
14
+ staleTime?: number;
14
15
  }
15
16
  type PlugableEventType = 'file.uploaded' | 'file.deleted';
16
17
  type EventHandler = (data?: any) => void;
18
+ interface CacheEntry {
19
+ files: FileObject[];
20
+ paging: ListResponse['paging'];
21
+ timestamp: number;
22
+ }
17
23
  interface PlugableContextValue {
18
24
  client: BucketClient;
19
25
  bucketId: string;
@@ -21,8 +27,12 @@ interface PlugableContextValue {
21
27
  emit: (event: PlugableEventType, data?: any) => void;
22
28
  getToken: () => Promise<string> | string;
23
29
  baseUrl?: string;
30
+ staleTime: number;
31
+ getCache: (key: string) => CacheEntry | undefined;
32
+ setCache: (key: string, entry: CacheEntry) => void;
33
+ invalidateCache: (pattern?: string) => void;
24
34
  }
25
- declare function PlugableProvider({ bucketId, children, getToken, authProvider, clerkJWTTemplate, baseUrl, }: PlugableProviderProps): react_jsx_runtime.JSX.Element;
35
+ declare function PlugableProvider({ bucketId, children, getToken, authProvider, clerkJWTTemplate, baseUrl, staleTime, }: PlugableProviderProps): react_jsx_runtime.JSX.Element;
26
36
  declare function usePlugable(): PlugableContextValue;
27
37
 
28
38
  interface DropzoneProps {
@@ -106,6 +116,7 @@ interface UseFilesOptions {
106
116
  autoLoad?: boolean;
107
117
  orderBy?: 'created_at' | 'name' | 'byte_size';
108
118
  orderDirection?: 'asc' | 'desc';
119
+ staleTime?: number;
109
120
  }
110
121
  interface UseFilesResult {
111
122
  files: FileObject[];
@@ -120,6 +131,6 @@ interface UseFilesResult {
120
131
  setPage: (page: number) => void;
121
132
  refresh: () => Promise<void>;
122
133
  }
123
- declare function useFiles({ metadata, startPage, perPage, mediaType, autoLoad, orderBy, orderDirection, }?: UseFilesOptions): UseFilesResult;
134
+ declare function useFiles({ metadata, startPage, perPage, mediaType, autoLoad, orderBy, orderDirection, staleTime, }?: UseFilesOptions): UseFilesResult;
124
135
 
125
136
  export { type AuthProvider, Dropzone, type DropzoneProps, type DropzoneRenderProps, FileImage, type FileImageProps, FileList, type FileListProps, type FileListRenderProps, FilePreview, type FilePreviewProps, PlugableProvider, type PlugableProviderProps, type UseFilesOptions, type UseFilesResult, clearImageCache, useFiles, usePlugable };
package/dist/index.js CHANGED
@@ -100,9 +100,12 @@ function PlugableProvider({
100
100
  getToken,
101
101
  authProvider,
102
102
  clerkJWTTemplate,
103
- baseUrl
103
+ baseUrl,
104
+ staleTime = 5 * 60 * 1e3
105
+ // Default 5 minutes
104
106
  }) {
105
107
  const listenersRef = (0, import_react.useRef)({});
108
+ const [cache, setCacheState] = (0, import_react.useState)(/* @__PURE__ */ new Map());
106
109
  const client = (0, import_react.useMemo)(() => {
107
110
  if (!getToken && !authProvider) {
108
111
  throw new Error(
@@ -128,6 +131,34 @@ function PlugableProvider({
128
131
  }, []);
129
132
  const emit = (0, import_react.useCallback)((event, data) => {
130
133
  listenersRef.current[event]?.forEach((handler) => handler(data));
134
+ if (event === "file.uploaded" || event === "file.deleted") {
135
+ setCacheState(/* @__PURE__ */ new Map());
136
+ }
137
+ }, []);
138
+ const getCache = (0, import_react.useCallback)((key) => {
139
+ return cache.get(key);
140
+ }, [cache]);
141
+ const setCache = (0, import_react.useCallback)((key, entry) => {
142
+ setCacheState((prev) => {
143
+ const next = new Map(prev);
144
+ next.set(key, entry);
145
+ return next;
146
+ });
147
+ }, []);
148
+ const invalidateCache = (0, import_react.useCallback)((pattern) => {
149
+ if (!pattern) {
150
+ setCacheState(/* @__PURE__ */ new Map());
151
+ return;
152
+ }
153
+ setCacheState((prev) => {
154
+ const next = new Map(prev);
155
+ for (const key of prev.keys()) {
156
+ if (key.includes(pattern)) {
157
+ next.delete(key);
158
+ }
159
+ }
160
+ return next;
161
+ });
131
162
  }, []);
132
163
  const value = (0, import_react.useMemo)(
133
164
  () => ({
@@ -136,9 +167,13 @@ function PlugableProvider({
136
167
  on,
137
168
  emit,
138
169
  getToken: client.tokenGetter,
139
- baseUrl
170
+ baseUrl,
171
+ staleTime,
172
+ getCache,
173
+ setCache,
174
+ invalidateCache
140
175
  }),
141
- [client, bucketId, on, emit, baseUrl]
176
+ [client, bucketId, on, emit, baseUrl, staleTime, getCache, setCache, invalidateCache]
142
177
  );
143
178
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlugableContext.Provider, { value, children });
144
179
  }
@@ -371,16 +406,19 @@ function useFiles({
371
406
  mediaType,
372
407
  autoLoad = true,
373
408
  orderBy,
374
- orderDirection
409
+ orderDirection,
410
+ staleTime
375
411
  } = {}) {
376
- const { client, on } = usePlugable();
412
+ const { client, on, getCache, setCache, staleTime: providerStaleTime } = usePlugable();
377
413
  const [files, setFiles] = (0, import_react3.useState)([]);
378
414
  const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
379
415
  const [page, setPage] = (0, import_react3.useState)(startPage);
380
416
  const [hasNext, setHasNext] = (0, import_react3.useState)(false);
417
+ const previousParamsRef = (0, import_react3.useRef)(null);
418
+ const isInitialMountRef = (0, import_react3.useRef)(true);
419
+ const effectiveStaleTime = staleTime ?? providerStaleTime;
381
420
  const metadataKey = JSON.stringify(metadata);
382
421
  const stableMetadata = (0, import_react3.useMemo)(() => metadata, [metadataKey]);
383
- const loadedParamsRef = (0, import_react3.useRef)(null);
384
422
  const paramsKeyWithPage = (0, import_react3.useMemo)(() => JSON.stringify({
385
423
  metadata: stableMetadata,
386
424
  mediaType,
@@ -389,7 +427,27 @@ function useFiles({
389
427
  orderDirection,
390
428
  page
391
429
  }), [stableMetadata, mediaType, perPage, orderBy, orderDirection, page]);
392
- const fetchFiles = (0, import_react3.useCallback)(async (pageNum) => {
430
+ const fetchFiles = (0, import_react3.useCallback)(async (pageNum, skipCache = false) => {
431
+ if (!skipCache) {
432
+ const cacheKey = JSON.stringify({
433
+ metadata: stableMetadata,
434
+ mediaType,
435
+ perPage,
436
+ orderBy,
437
+ orderDirection,
438
+ page: pageNum
439
+ });
440
+ const cachedEntry = getCache(cacheKey);
441
+ if (cachedEntry) {
442
+ const age = Date.now() - cachedEntry.timestamp;
443
+ const isStale = age > effectiveStaleTime;
444
+ if (!isStale) {
445
+ setFiles(cachedEntry.files);
446
+ setHasNext(cachedEntry.paging.has_next_page);
447
+ return;
448
+ }
449
+ }
450
+ }
393
451
  setIsLoading(true);
394
452
  try {
395
453
  const options = {
@@ -398,13 +456,11 @@ function useFiles({
398
456
  page: pageNum,
399
457
  per_page: perPage,
400
458
  with_download_url: true,
401
- order_by: orderBy,
402
- order_direction: orderDirection
459
+ ...orderBy && { order_by: orderBy },
460
+ ...orderDirection && { order_direction: orderDirection }
403
461
  };
404
462
  const response = await client.list(options);
405
- setFiles(response.files);
406
- setHasNext(response.paging.has_next_page);
407
- const currentParamsKey = JSON.stringify({
463
+ const cacheKey = JSON.stringify({
408
464
  metadata: stableMetadata,
409
465
  mediaType,
410
466
  perPage,
@@ -412,7 +468,13 @@ function useFiles({
412
468
  orderDirection,
413
469
  page: pageNum
414
470
  });
415
- loadedParamsRef.current = currentParamsKey;
471
+ setCache(cacheKey, {
472
+ files: response.files,
473
+ paging: response.paging,
474
+ timestamp: Date.now()
475
+ });
476
+ setFiles(response.files);
477
+ setHasNext(response.paging.has_next_page);
416
478
  } catch (err) {
417
479
  console.error("Failed to load files:", err);
418
480
  setFiles([]);
@@ -420,17 +482,31 @@ function useFiles({
420
482
  } finally {
421
483
  setIsLoading(false);
422
484
  }
423
- }, [client, stableMetadata, mediaType, perPage, orderBy, orderDirection]);
485
+ }, [client, stableMetadata, mediaType, perPage, orderBy, orderDirection, getCache, setCache, effectiveStaleTime]);
424
486
  (0, import_react3.useEffect)(() => {
425
- const hasLoadedForParams = loadedParamsRef.current === paramsKeyWithPage;
426
- const shouldLoad = autoLoad || !hasLoadedForParams && files.length === 0 && !isLoading;
427
- if (shouldLoad) {
428
- fetchFiles(page);
487
+ const paramsChanged = previousParamsRef.current !== null && previousParamsRef.current !== paramsKeyWithPage;
488
+ const isInitialMount = isInitialMountRef.current;
489
+ previousParamsRef.current = paramsKeyWithPage;
490
+ if (isInitialMount) {
491
+ isInitialMountRef.current = false;
492
+ }
493
+ const cachedEntry = getCache(paramsKeyWithPage);
494
+ const shouldFetch = paramsChanged || isInitialMount && autoLoad;
495
+ if (cachedEntry) {
496
+ const age = Date.now() - cachedEntry.timestamp;
497
+ const isStale = age > effectiveStaleTime;
498
+ setFiles(cachedEntry.files);
499
+ setHasNext(cachedEntry.paging.has_next_page);
500
+ if (paramsChanged || isStale && shouldFetch) {
501
+ fetchFiles(page, true);
502
+ }
503
+ } else if (shouldFetch) {
504
+ fetchFiles(page, true);
429
505
  }
430
- }, [fetchFiles, page, autoLoad, paramsKeyWithPage, isLoading]);
506
+ }, [paramsKeyWithPage, autoLoad, getCache, effectiveStaleTime, fetchFiles, page]);
431
507
  (0, import_react3.useEffect)(() => {
432
508
  const unsubscribe = on("file.uploaded", () => {
433
- fetchFiles(page);
509
+ fetchFiles(page, true);
434
510
  });
435
511
  return unsubscribe;
436
512
  }, [on, fetchFiles, page]);
@@ -443,7 +519,7 @@ function useFiles({
443
519
  setPage((p) => Math.max(1, p - 1));
444
520
  }, []);
445
521
  const refresh = (0, import_react3.useCallback)(async () => {
446
- await fetchFiles(page);
522
+ await fetchFiles(page, true);
447
523
  }, [fetchFiles, page]);
448
524
  return {
449
525
  files,
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/PlugableProvider.tsx
2
- import { createContext, useContext, useMemo, useCallback, useRef } from "react";
2
+ import { createContext, useContext, useMemo, useCallback, useRef, useState } from "react";
3
3
  import { BucketClient } from "@plugable-io/js";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  var PlugableContext = createContext(null);
@@ -57,9 +57,12 @@ function PlugableProvider({
57
57
  getToken,
58
58
  authProvider,
59
59
  clerkJWTTemplate,
60
- baseUrl
60
+ baseUrl,
61
+ staleTime = 5 * 60 * 1e3
62
+ // Default 5 minutes
61
63
  }) {
62
64
  const listenersRef = useRef({});
65
+ const [cache, setCacheState] = useState(/* @__PURE__ */ new Map());
63
66
  const client = useMemo(() => {
64
67
  if (!getToken && !authProvider) {
65
68
  throw new Error(
@@ -85,6 +88,34 @@ function PlugableProvider({
85
88
  }, []);
86
89
  const emit = useCallback((event, data) => {
87
90
  listenersRef.current[event]?.forEach((handler) => handler(data));
91
+ if (event === "file.uploaded" || event === "file.deleted") {
92
+ setCacheState(/* @__PURE__ */ new Map());
93
+ }
94
+ }, []);
95
+ const getCache = useCallback((key) => {
96
+ return cache.get(key);
97
+ }, [cache]);
98
+ const setCache = useCallback((key, entry) => {
99
+ setCacheState((prev) => {
100
+ const next = new Map(prev);
101
+ next.set(key, entry);
102
+ return next;
103
+ });
104
+ }, []);
105
+ const invalidateCache = useCallback((pattern) => {
106
+ if (!pattern) {
107
+ setCacheState(/* @__PURE__ */ new Map());
108
+ return;
109
+ }
110
+ setCacheState((prev) => {
111
+ const next = new Map(prev);
112
+ for (const key of prev.keys()) {
113
+ if (key.includes(pattern)) {
114
+ next.delete(key);
115
+ }
116
+ }
117
+ return next;
118
+ });
88
119
  }, []);
89
120
  const value = useMemo(
90
121
  () => ({
@@ -93,9 +124,13 @@ function PlugableProvider({
93
124
  on,
94
125
  emit,
95
126
  getToken: client.tokenGetter,
96
- baseUrl
127
+ baseUrl,
128
+ staleTime,
129
+ getCache,
130
+ setCache,
131
+ invalidateCache
97
132
  }),
98
- [client, bucketId, on, emit, baseUrl]
133
+ [client, bucketId, on, emit, baseUrl, staleTime, getCache, setCache, invalidateCache]
99
134
  );
100
135
  return /* @__PURE__ */ jsx(PlugableContext.Provider, { value, children });
101
136
  }
@@ -108,7 +143,7 @@ function usePlugable() {
108
143
  }
109
144
 
110
145
  // src/components/Dropzone.tsx
111
- import React, { useCallback as useCallback2, useState } from "react";
146
+ import React, { useCallback as useCallback2, useState as useState2 } from "react";
112
147
  import { BucketClient as BucketClient2 } from "@plugable-io/js";
113
148
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
114
149
  function Dropzone({
@@ -134,10 +169,10 @@ function Dropzone({
134
169
  }
135
170
  return defaultClient;
136
171
  }, [_bucketId, defaultClient, getToken, baseUrl]);
137
- const [isDragActive, setIsDragActive] = useState(false);
138
- const [isUploading, setIsUploading] = useState(false);
139
- const [uploadProgress, setUploadProgress] = useState({});
140
- const [uploadedFiles, setUploadedFiles] = useState([]);
172
+ const [isDragActive, setIsDragActive] = useState2(false);
173
+ const [isUploading, setIsUploading] = useState2(false);
174
+ const [uploadProgress, setUploadProgress] = useState2({});
175
+ const [uploadedFiles, setUploadedFiles] = useState2([]);
141
176
  const fileInputRef = React.useRef(null);
142
177
  const uploadFiles = useCallback2(
143
178
  async (files) => {
@@ -320,7 +355,7 @@ function Dropzone({
320
355
  }
321
356
 
322
357
  // src/hooks/useFiles.ts
323
- import { useState as useState2, useCallback as useCallback3, useEffect, useMemo as useMemo2, useRef as useRef2 } from "react";
358
+ import { useState as useState3, useCallback as useCallback3, useEffect, useMemo as useMemo2, useRef as useRef2 } from "react";
324
359
  function useFiles({
325
360
  metadata,
326
361
  startPage = 1,
@@ -328,16 +363,19 @@ function useFiles({
328
363
  mediaType,
329
364
  autoLoad = true,
330
365
  orderBy,
331
- orderDirection
366
+ orderDirection,
367
+ staleTime
332
368
  } = {}) {
333
- const { client, on } = usePlugable();
334
- const [files, setFiles] = useState2([]);
335
- const [isLoading, setIsLoading] = useState2(false);
336
- const [page, setPage] = useState2(startPage);
337
- const [hasNext, setHasNext] = useState2(false);
369
+ const { client, on, getCache, setCache, staleTime: providerStaleTime } = usePlugable();
370
+ const [files, setFiles] = useState3([]);
371
+ const [isLoading, setIsLoading] = useState3(false);
372
+ const [page, setPage] = useState3(startPage);
373
+ const [hasNext, setHasNext] = useState3(false);
374
+ const previousParamsRef = useRef2(null);
375
+ const isInitialMountRef = useRef2(true);
376
+ const effectiveStaleTime = staleTime ?? providerStaleTime;
338
377
  const metadataKey = JSON.stringify(metadata);
339
378
  const stableMetadata = useMemo2(() => metadata, [metadataKey]);
340
- const loadedParamsRef = useRef2(null);
341
379
  const paramsKeyWithPage = useMemo2(() => JSON.stringify({
342
380
  metadata: stableMetadata,
343
381
  mediaType,
@@ -346,7 +384,27 @@ function useFiles({
346
384
  orderDirection,
347
385
  page
348
386
  }), [stableMetadata, mediaType, perPage, orderBy, orderDirection, page]);
349
- const fetchFiles = useCallback3(async (pageNum) => {
387
+ const fetchFiles = useCallback3(async (pageNum, skipCache = false) => {
388
+ if (!skipCache) {
389
+ const cacheKey = JSON.stringify({
390
+ metadata: stableMetadata,
391
+ mediaType,
392
+ perPage,
393
+ orderBy,
394
+ orderDirection,
395
+ page: pageNum
396
+ });
397
+ const cachedEntry = getCache(cacheKey);
398
+ if (cachedEntry) {
399
+ const age = Date.now() - cachedEntry.timestamp;
400
+ const isStale = age > effectiveStaleTime;
401
+ if (!isStale) {
402
+ setFiles(cachedEntry.files);
403
+ setHasNext(cachedEntry.paging.has_next_page);
404
+ return;
405
+ }
406
+ }
407
+ }
350
408
  setIsLoading(true);
351
409
  try {
352
410
  const options = {
@@ -355,13 +413,11 @@ function useFiles({
355
413
  page: pageNum,
356
414
  per_page: perPage,
357
415
  with_download_url: true,
358
- order_by: orderBy,
359
- order_direction: orderDirection
416
+ ...orderBy && { order_by: orderBy },
417
+ ...orderDirection && { order_direction: orderDirection }
360
418
  };
361
419
  const response = await client.list(options);
362
- setFiles(response.files);
363
- setHasNext(response.paging.has_next_page);
364
- const currentParamsKey = JSON.stringify({
420
+ const cacheKey = JSON.stringify({
365
421
  metadata: stableMetadata,
366
422
  mediaType,
367
423
  perPage,
@@ -369,7 +425,13 @@ function useFiles({
369
425
  orderDirection,
370
426
  page: pageNum
371
427
  });
372
- loadedParamsRef.current = currentParamsKey;
428
+ setCache(cacheKey, {
429
+ files: response.files,
430
+ paging: response.paging,
431
+ timestamp: Date.now()
432
+ });
433
+ setFiles(response.files);
434
+ setHasNext(response.paging.has_next_page);
373
435
  } catch (err) {
374
436
  console.error("Failed to load files:", err);
375
437
  setFiles([]);
@@ -377,17 +439,31 @@ function useFiles({
377
439
  } finally {
378
440
  setIsLoading(false);
379
441
  }
380
- }, [client, stableMetadata, mediaType, perPage, orderBy, orderDirection]);
442
+ }, [client, stableMetadata, mediaType, perPage, orderBy, orderDirection, getCache, setCache, effectiveStaleTime]);
381
443
  useEffect(() => {
382
- const hasLoadedForParams = loadedParamsRef.current === paramsKeyWithPage;
383
- const shouldLoad = autoLoad || !hasLoadedForParams && files.length === 0 && !isLoading;
384
- if (shouldLoad) {
385
- fetchFiles(page);
444
+ const paramsChanged = previousParamsRef.current !== null && previousParamsRef.current !== paramsKeyWithPage;
445
+ const isInitialMount = isInitialMountRef.current;
446
+ previousParamsRef.current = paramsKeyWithPage;
447
+ if (isInitialMount) {
448
+ isInitialMountRef.current = false;
449
+ }
450
+ const cachedEntry = getCache(paramsKeyWithPage);
451
+ const shouldFetch = paramsChanged || isInitialMount && autoLoad;
452
+ if (cachedEntry) {
453
+ const age = Date.now() - cachedEntry.timestamp;
454
+ const isStale = age > effectiveStaleTime;
455
+ setFiles(cachedEntry.files);
456
+ setHasNext(cachedEntry.paging.has_next_page);
457
+ if (paramsChanged || isStale && shouldFetch) {
458
+ fetchFiles(page, true);
459
+ }
460
+ } else if (shouldFetch) {
461
+ fetchFiles(page, true);
386
462
  }
387
- }, [fetchFiles, page, autoLoad, paramsKeyWithPage, isLoading]);
463
+ }, [paramsKeyWithPage, autoLoad, getCache, effectiveStaleTime, fetchFiles, page]);
388
464
  useEffect(() => {
389
465
  const unsubscribe = on("file.uploaded", () => {
390
- fetchFiles(page);
466
+ fetchFiles(page, true);
391
467
  });
392
468
  return unsubscribe;
393
469
  }, [on, fetchFiles, page]);
@@ -400,7 +476,7 @@ function useFiles({
400
476
  setPage((p) => Math.max(1, p - 1));
401
477
  }, []);
402
478
  const refresh = useCallback3(async () => {
403
- await fetchFiles(page);
479
+ await fetchFiles(page, true);
404
480
  }, [fetchFiles, page]);
405
481
  return {
406
482
  files,
@@ -447,7 +523,7 @@ function FileList({
447
523
  }
448
524
 
449
525
  // src/components/FileImage.tsx
450
- import { useEffect as useEffect2, useState as useState3 } from "react";
526
+ import { useEffect as useEffect2, useState as useState4 } from "react";
451
527
  import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
452
528
  var imageCache = /* @__PURE__ */ new Map();
453
529
  function FileImage({
@@ -462,9 +538,9 @@ function FileImage({
462
538
  onLoad,
463
539
  onError
464
540
  }) {
465
- const [imageSrc, setImageSrc] = useState3(null);
466
- const [isLoading, setIsLoading] = useState3(true);
467
- const [error, setError] = useState3(null);
541
+ const [imageSrc, setImageSrc] = useState4(null);
542
+ const [isLoading, setIsLoading] = useState4(true);
543
+ const [error, setError] = useState4(null);
468
544
  useEffect2(() => {
469
545
  let isMounted = true;
470
546
  let objectUrl = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plugable-io/react",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "React components and hooks for Plugable File Management API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",