Files
study-bible-project/src/hooks/useBibleSearch.js
T
nmemmertandClaude Sonnet 5.5 c11b6a60bf
Build and Push Docker Image / build-and-push (push) Failing after 20s
Reader: whole-Bible search in every translation
BSB and NET are indexed in the browser; ESV, NLT, CSB and NASB are searched
through a server proxy (keys stay server-side). Results page in 25 at a time
and jump to the verse. Also fixes a stray trailing comma in CSB chapter text.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-29 09:25:00 -04:00

50 lines
2.0 KiB
JavaScript

// Whole-Bible search for translations that can't be downloaded and indexed in the
// browser (ESV, NLT, CSB, NASB): queries go through the server's search proxy.
// Debounced, cancels stale requests, and pages in 25 verses at a time.
import { useCallback, useEffect, useRef, useState } from 'react';
const DEBOUNCE_MS = 450;
const EMPTY = { results: [], total: 0, page: 0, hasMore: false, loading: false, error: '' };
export function useBibleSearch(translation, query, enabled) {
const [state, setState] = useState(EMPTY);
const controllerRef = useRef(null);
const q = query.trim();
const run = useCallback(async (page) => {
controllerRef.current?.abort();
const controller = new AbortController();
controllerRef.current = controller;
setState((s) => ({ ...s, loading: true, error: '' }));
try {
const res = await fetch(`/api/bible/${translation}/search?q=${encodeURIComponent(q)}&page=${page}`, {
credentials: 'include', signal: controller.signal,
});
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.error || 'Search failed.');
setState((s) => ({
results: page === 1 ? data.results : [...s.results, ...data.results],
total: data.total, page: data.page, hasMore: data.hasMore, loading: false, error: '',
}));
} catch (err) {
if (err.name === 'AbortError') return;
setState((s) => ({ ...s, loading: false, error: err.message || 'Search failed.' }));
}
}, [translation, q]);
useEffect(() => {
if (!enabled || q.length < 2) {
controllerRef.current?.abort();
setState(EMPTY);
return undefined;
}
const timer = window.setTimeout(() => run(1), DEBOUNCE_MS);
return () => window.clearTimeout(timer);
}, [enabled, q, run]);
useEffect(() => () => controllerRef.current?.abort(), []);
const loadMore = () => { if (!state.loading && state.hasMore) run(state.page + 1); };
return { ...state, loadMore, tooShort: q.length < 2 };
}