search on web

This commit is contained in:
Thomas Demirdjian
2025-10-02 17:53:17 +02:00
parent ea421d8eff
commit 8846ca05ff
14 changed files with 656 additions and 415 deletions
+15 -1
View File
@@ -23,14 +23,17 @@ export default function useDataFromRef({
format = null,
onUpdate = () => null,
onError = null,
}) {
const [loading, setLoading] = useState(true);
const [endReached, setEndReached] = useState(false);
const [data, setData] = useState(initialState);
const [lastVisible, setLastVisible] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
if (condition) {
setError(null);
if (listener && !usePagination) {
const subData = getListenerData();
return () => subData?.();
@@ -57,12 +60,14 @@ export default function useDataFromRef({
}
setEndReached(false);
setLastVisible(null);
setError(null);
setData(initialState);
};
const getData = async ({ paginate = false } = {}) => {
try {
setLoading(true);
setError(null);
let newData = simpleRef ? null : [];
let initialDoc = null;
@@ -115,6 +120,10 @@ export default function useDataFromRef({
} else {
console.log(e);
}
setError(e);
if (onError) {
onError(e);
}
await updateData([]);
} finally {
setLoading(false);
@@ -124,6 +133,7 @@ export default function useDataFromRef({
const getListenerData = () => {
return ref?.onSnapshot(
async (dataSnap) => {
setError(null);
let newData;
if (simpleRef) {
@@ -145,6 +155,10 @@ export default function useDataFromRef({
} else {
console.log(e);
}
setError(e);
if (onError) {
onError(e);
}
await updateData([]);
setLoading(false);
},
@@ -160,5 +174,5 @@ export default function useDataFromRef({
}
};
return { data, setData, loading, loadMore };
return { data, setData, loading, loadMore, hasMore: !endReached, error };
}
+12
View File
@@ -0,0 +1,12 @@
import { useEffect, useState } from "react";
export default function useDebounced(value, delay = 250) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
+232
View File
@@ -0,0 +1,232 @@
import { useEffect, useMemo, useState } from "react";
import { useGlobal } from "reactn";
import { usersRef, projectsRef } from "../config/firebase";
import useDataFromRef from "./useDataFromRef";
const batchSizes = {
users: 5,
projects: 6,
};
const useSearch = () => {
const [selected, setSelected] = useState(null);
const [search, setSearch] = useState("");
const queryText = useMemo(() => search.trim(), [search]);
const queryLower = useMemo(
() => (search || "").toLowerCase().trim(),
[search],
);
const [currentUID] = useGlobal("currentUID");
const [autoLoads, setAutoLoads] = useState(0);
const usersQueryRef = useMemo(() => {
try {
const lower = queryLower;
if (lower) {
return usersRef
.orderBy("userNameLower")
.startAt(lower)
.endAt(lower + "\uf8ff");
}
return usersRef.orderBy("userNameLower");
} catch (_error) {
return null;
}
}, [queryLower]);
const projectsQueryRefLower = useMemo(() => {
try {
const lower = queryText ? queryText.toLowerCase() : "";
if (lower) {
return projectsRef
.orderBy("titleLower")
.startAt(lower)
.endAt(lower + "\uf8ff");
}
return null;
} catch (_error) {
return null;
}
}, [queryText]);
const projectsQueryRefCase = useMemo(() => {
try {
const raw = queryText || "";
if (raw) {
return projectsRef
.orderBy("title")
.startAt(raw)
.endAt(raw + "\uf8ff");
}
return null;
} catch (_error) {
return null;
}
}, [queryText]);
const {
data: userResults = [],
loading: usersLoading,
loadMore: loadMoreUsers,
} = useDataFromRef({
ref: usersQueryRef,
simpleRef: false,
listener: false,
condition: !!usersQueryRef,
refreshArray: [queryLower],
usePagination: true,
batchSize: batchSizes.users,
});
const filteredUsers = useMemo(() => {
const arr = Array.isArray(userResults) ? userResults : [];
const matchingUsers = queryLower
? arr.filter((user) => {
const nameLower = (
user?.userNameLower ||
user?.userName ||
""
).toLowerCase();
return nameLower.includes(queryLower);
})
: arr;
const seen = new Set();
const unique = [];
for (const user of matchingUsers) {
const id = user?.id;
if (!id || seen.has(id)) continue;
if (id === currentUID || user?.userID === currentUID) continue;
seen.add(id);
unique.push(user);
}
return unique;
}, [userResults, queryLower, currentUID]);
const {
data: projectResultsLower = [],
loading: projectsLoadingLower,
loadMore: loadMoreProjectsLower,
} = useDataFromRef({
ref: projectsQueryRefLower,
simpleRef: false,
listener: false,
condition: !!projectsQueryRefLower,
refreshArray: [queryText],
usePagination: true,
batchSize: batchSizes.projects,
});
const {
data: projectResultsCase = [],
loading: projectsLoadingCase,
loadMore: loadMoreProjectsCase,
} = useDataFromRef({
ref: projectsQueryRefCase,
simpleRef: false,
listener: false,
condition: !!projectsQueryRefCase,
refreshArray: [queryText],
usePagination: true,
batchSize: batchSizes.projects,
});
const {
data: projectResultsDefault = [],
loading: projectsLoadingDefault,
loadMore: loadMoreProjectsDefault,
} = useDataFromRef({
ref: !queryText ? projectsRef.orderBy("updatedAt", "desc") : null,
simpleRef: false,
listener: false,
condition: !queryText,
refreshArray: [queryText],
usePagination: true,
batchSize: batchSizes.projects,
});
const projectsLoading = queryText
? projectsLoadingLower || projectsLoadingCase
: projectsLoadingDefault;
const loadMoreProjects = () => {
if (queryText) {
loadMoreProjectsLower?.();
loadMoreProjectsCase?.();
} else {
loadMoreProjectsDefault?.();
}
};
const filteredProjects = useMemo(() => {
const lowerMatches = Array.isArray(projectResultsLower)
? projectResultsLower
: [];
const caseMatches = Array.isArray(projectResultsCase)
? projectResultsCase
: [];
const defaultProjects = Array.isArray(projectResultsDefault)
? projectResultsDefault
: [];
const arr = queryText ? [...lowerMatches, ...caseMatches] : defaultProjects;
const seen = new Set();
const unique = [];
for (const project of arr) {
const id = project?.id;
if (!id || seen.has(id)) continue;
seen.add(id);
unique.push(project);
}
return unique;
}, [
queryText,
projectResultsLower,
projectResultsCase,
projectResultsDefault,
]);
useEffect(() => {
setAutoLoads(0);
}, [queryText]);
useEffect(() => {
if (
filteredUsers.length < batchSizes.users &&
usersQueryRef &&
!usersLoading
) {
if (autoLoads < 3) {
setAutoLoads((previous) => previous + 1);
loadMoreUsers?.();
}
}
}, [
filteredUsers.length,
usersLoading,
usersQueryRef,
autoLoads,
loadMoreUsers,
]);
const toggleSelected = (item) => {
setSelected((prev) => (prev === item ? null : item));
};
return {
search,
setSearch,
queryText,
queryLower,
selected,
setSelected,
toggleSelected,
filteredUsers,
usersLoading,
loadMoreUsers,
filteredProjects,
projectsLoading,
loadMoreProjects,
};
};
export default useSearch;