Skip to content

Commit 9c1897f

Browse files
committed
fix: Update cacheTime assignment to use logical OR for better clarity + add pagination & infinite scrolling example tests
1 parent 3d5951a commit 9c1897f

File tree

3 files changed

+649
-3
lines changed

3 files changed

+649
-3
lines changed

src/react/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export function useFetcher<
103103
],
104104
);
105105
const dedupeTime = config.dedupeTime ?? DEFAULT_DEDUPE_TIME_MS;
106-
const cacheTime = config.cacheTime ?? INFINITE_CACHE_TIME;
106+
const cacheTime = config.cacheTime || INFINITE_CACHE_TIME;
107107

108108
const shouldTriggerOnMount = useMemo(
109109
() => (config.immediate === undefined ? true : config.immediate),
@@ -182,10 +182,10 @@ export function useFetcher<
182182
const cacheBuster = forceRefresh ? () => true : config.cacheBuster;
183183

184184
return await fetchf(url, {
185-
dedupeTime,
186-
cacheTime,
187185
cacheKey,
188186
...config,
187+
dedupeTime,
188+
cacheTime,
189189
cacheBuster,
190190
// Ensure that errors are handled gracefully and not thrown by default
191191
strategy: 'softFail',

test/mocks/test-components.tsx

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useEffect, useState } from 'react';
12
import { useFetcher } from '../../src/react/index';
23
import type { RequestConfig } from '../../src/types/request-handler';
34

@@ -404,3 +405,209 @@ export const ErrorTypesComponent = ({
404405
</div>
405406
);
406407
};
408+
409+
export const PaginationComponent = () => {
410+
const [page, setPage] = useState(1);
411+
const limit = 10;
412+
413+
const { data, isLoading, error } = useFetcher<{
414+
data: Array<{ id: number; title: string }>;
415+
pagination: {
416+
page: number;
417+
limit: number;
418+
total: number;
419+
totalPages: number;
420+
hasNext: boolean;
421+
hasPrev: boolean;
422+
};
423+
}>('/api/posts', {
424+
params: { page, limit },
425+
cacheTime: 30,
426+
cacheKey: `posts-page-${page}`,
427+
});
428+
429+
return (
430+
<div>
431+
<div data-testid="pagination-loading">
432+
{isLoading ? 'Loading' : 'Not Loading'}
433+
</div>
434+
<div data-testid="pagination-error">
435+
{error ? error.message : 'No Error'}
436+
</div>
437+
<div data-testid="pagination-data">
438+
{data?.data ? JSON.stringify(data.data) : 'No Data'}
439+
</div>
440+
<div data-testid="pagination-info">
441+
{data?.pagination
442+
? `Page ${data.pagination.page} of ${data.pagination.totalPages}`
443+
: 'No Pagination Info'}
444+
</div>
445+
<button
446+
onClick={() => setPage(page - 1)}
447+
disabled={!data?.pagination?.hasPrev}
448+
data-testid="prev-page"
449+
>
450+
Previous
451+
</button>
452+
<button
453+
onClick={() => setPage(page + 1)}
454+
disabled={!data?.pagination?.hasNext}
455+
data-testid="next-page"
456+
>
457+
Next
458+
</button>
459+
<div data-testid="current-page">{page}</div>
460+
</div>
461+
);
462+
};
463+
464+
export const InfiniteScrollComponent = () => {
465+
const [allItems, setAllItems] = useState<
466+
Array<{ id: number; content: string }>
467+
>([]);
468+
const [offset, setOffset] = useState(0);
469+
const [hasMore, setHasMore] = useState(true);
470+
471+
const { data, isLoading } = useFetcher<{
472+
items: Array<{ id: number; content: string }>;
473+
hasMore: boolean;
474+
nextOffset: number | null;
475+
}>('/api/feed', {
476+
params: { offset, limit: 5 },
477+
cacheTime: 0, // Don't cache for infinite scroll
478+
immediate: hasMore, // Only fetch if there's more data
479+
});
480+
481+
useEffect(() => {
482+
if (data?.items) {
483+
setAllItems((prev) => [...prev, ...data.items]);
484+
setHasMore(data.hasMore);
485+
if (data.nextOffset !== null) {
486+
// Don't auto-advance here, wait for user action
487+
}
488+
}
489+
}, [data]);
490+
491+
const loadMore = () => {
492+
if (data && data.nextOffset !== null && hasMore) {
493+
setOffset(data.nextOffset);
494+
}
495+
};
496+
497+
return (
498+
<div>
499+
<div data-testid="infinite-items">
500+
{allItems.map((item) => (
501+
<div key={item.id} data-testid={`item-${item.id}`}>
502+
{item.content}
503+
</div>
504+
))}
505+
</div>
506+
<div data-testid="infinite-loading">
507+
{isLoading ? 'Loading More' : 'Not Loading'}
508+
</div>
509+
<div data-testid="items-count">{allItems.length}</div>
510+
<button
511+
onClick={loadMore}
512+
disabled={!hasMore || isLoading}
513+
data-testid="load-more"
514+
>
515+
Load More
516+
</button>
517+
<div data-testid="has-more">{hasMore ? 'Has More' : 'No More'}</div>
518+
</div>
519+
);
520+
};
521+
522+
export const SearchPaginationComponent = () => {
523+
const [search, setSearch] = useState('john');
524+
const [status, setStatus] = useState('active');
525+
const [page, setPage] = useState(1);
526+
527+
const { data, isLoading } = useFetcher<{
528+
users: Array<{ id: number; name: string; status: string }>;
529+
pagination: {
530+
page: number;
531+
limit: number;
532+
total: number;
533+
totalPages: number;
534+
};
535+
}>('/api/users', {
536+
params: { search, status, page, limit: 3 },
537+
cacheTime: 60,
538+
cacheKey: `users-${search}-${status}-${page}`,
539+
dedupeTime: 1000, // Dedupe rapid searches
540+
});
541+
542+
// Reset page when search changes
543+
useEffect(() => {
544+
setPage(1);
545+
}, [search, status]);
546+
547+
return (
548+
<div>
549+
<input
550+
value={search}
551+
onChange={(e) => setSearch(e.target.value)}
552+
data-testid="search-input"
553+
placeholder="Search users..."
554+
/>
555+
<select
556+
value={status}
557+
onChange={(e) => setStatus(e.target.value)}
558+
data-testid="status-filter"
559+
>
560+
<option value="active">Active</option>
561+
<option value="inactive">Inactive</option>
562+
</select>
563+
564+
<div data-testid="search-loading">
565+
{isLoading ? 'Searching' : 'Not Searching'}
566+
</div>
567+
568+
<div data-testid="search-results">
569+
{data?.users?.map((user) => (
570+
<div key={user.id} data-testid={`user-${user.id}`}>
571+
{user.name} - {user.status}
572+
</div>
573+
)) || 'No Results'}
574+
</div>
575+
576+
<div data-testid="search-total">
577+
{data?.pagination ? `Total: ${data.pagination.total}` : 'No Total'}
578+
</div>
579+
580+
<div data-testid="search-page">
581+
{data?.pagination ? `Page: ${data.pagination.page}` : 'No Page'}
582+
</div>
583+
</div>
584+
);
585+
};
586+
587+
export const ErrorPaginationComponent = ({ attemptCount = 0 }) => {
588+
const [page, setPage] = useState(1);
589+
590+
const { data, error, isLoading } = useFetcher('/api/posts-error', {
591+
params: { page },
592+
retry: { retries: 3, delay: 100, backoff: 1.5 },
593+
cacheTime: 0, // Don't cache error responses
594+
});
595+
596+
return (
597+
<div>
598+
<div data-testid="error-pagination-data">
599+
{data?.data ? JSON.stringify(data.data) : 'No Data'}
600+
</div>
601+
<div data-testid="error-pagination-error">
602+
{error ? `Error: ${error.status}` : 'No Error'}
603+
</div>
604+
<div data-testid="error-pagination-loading">
605+
{isLoading ? 'Loading' : 'Not Loading'}
606+
</div>
607+
<button onClick={() => setPage(2)} data-testid="goto-page-2">
608+
Go to Page 2
609+
</button>
610+
<div data-testid="error-attempt-count">{attemptCount}</div>
611+
</div>
612+
);
613+
};

0 commit comments

Comments
 (0)