Paginate search results.

closes #16
This commit is contained in:
Nicolas Harraudeau 2020-07-02 23:55:56 +02:00
parent f89495c206
commit dcd0974ad2
5 changed files with 116 additions and 11 deletions

View File

@ -1383,6 +1383,18 @@
"@babel/runtime": "^7.4.4"
}
},
"@material-ui/lab": {
"version": "4.0.0-alpha.56",
"resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.56.tgz",
"integrity": "sha512-xPlkK+z/6y/24ka4gVJgwPfoCF4RCh8dXb1BNE7MtF9bXEBLN/lBxNTK8VAa0qm3V2oinA6xtUIdcRh0aeRtVw==",
"requires": {
"@babel/runtime": "^7.4.4",
"@material-ui/utils": "^4.10.2",
"clsx": "^1.0.4",
"prop-types": "^15.7.2",
"react-is": "^16.8.0"
}
},
"@material-ui/styles": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.10.0.tgz",

View File

@ -6,6 +6,7 @@
"dependencies": {
"@material-ui/core": "^4.10.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.56",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",

View File

@ -5,8 +5,13 @@ import Paper from '@material-ui/core/Paper';
import Typography from '@material-ui/core/Typography';
import TextField from '@material-ui/core/TextField';
import Container from '@material-ui/core/Container';
import Pagination from '@material-ui/lab/Pagination';
import { useSearch } from './utils/useSearch';
import {
useLocationSearch,
useLocationSearchState
} from './utils/routing';
import { SearchHit } from './SearchHit';
@ -22,11 +27,15 @@ const classes = makeStyles((theme) => ({
},
}));
export const SearchPage = () => {
const [titleQuery, setTitleQuery] = useState("");
const pageSize = 20;
const [query, setQuery] = useLocationSearchState('query', '');
const [pageNumber, setPageNumber] = useLocationSearchState('page', 1, parseInt);
const [_, setLocationSearch] = useLocationSearch();
const [results, resultsAreLoading] = useSearch(titleQuery);
const [results, numberOfHits, error, resultsAreLoading] = useSearch(query, pageSize, pageNumber);
const totalPages = Math.ceil(numberOfHits/pageSize);
let resultsDisplay="No rule found...";
if (resultsAreLoading) {
@ -36,6 +45,14 @@ export const SearchPage = () => {
resultsDisplay = results.map(result => <SearchHit key={result.id} data={result}/>)
}
function handleQueryUpdate(event) {
if (pageNumber > 1) {
setLocationSearch({query: event.target.value, page: 1});
} else {
setQuery(event.target.value, {push: false});
}
}
return (
<div>
<Paper className={classes.languagesBar}>
@ -52,15 +69,21 @@ export const SearchPage = () => {
shrink: true,
}}
variant="outlined"
value={titleQuery}
onChange={e => setTitleQuery(e.target.value)}
value={query}
onChange={handleQueryUpdate}
error={error}
helperText={error}
/>
</Container>
</Paper>
<h1>Results</h1>
<Typography variant="h5" className={classes.searchBar}>Number of rules found: {numberOfHits}</Typography>
<ul>
{resultsDisplay}
</ul>
<Pagination count={totalPages} page={pageNumber} siblingCount={2}
onChange={(event, value) => setPageNumber(value)}
/>
<Paper/>
</div>
)
}

View File

@ -0,0 +1,53 @@
import React, { useState } from "react";
import { useLocation, useHistory } from "react-router-dom";
export function useLocationSearch() {
const location = useLocation();
const history = useHistory();
function setLocationSearch(searchParams, push=true) {
const search = new URLSearchParams(location.search);
for (const [key, value] of Object.entries(searchParams)) {
search.set(key, value);
}
if (push) {
history.push(`${location.pathname}?${search.toString()}`);
} else {
history.replace(`${location.pathname}?${search.toString()}`);
}
}
return [new URLSearchParams(location.search), setLocationSearch];
}
export function useLocationSearchState(name, defaultValue, convert=value=>value) {
const [state, setState] = useState(defaultValue);
const location = useLocation();
const history = useHistory();
React.useEffect(() => {
const search = new URLSearchParams(location.search);
if (search.has(name) && search.get(name) != state) {
setState(convert(search.get(name)));
} else if (!search.has(name) && state != defaultValue) {
setState(defaultValue);
}
}, [location, history]);
function setSearchParam(value, {push=true, skipURI=false} = {}) {
const search = new URLSearchParams(location.search);
search.set(name, value);
setState(value);
if (push) {
history.push(`${location.pathname}?${search.toString()}`);
} else {
history.replace(`${location.pathname}?${search.toString()}`);
}
}
return [state, setSearchParam];
}

View File

@ -4,7 +4,7 @@ import * as lunr from 'lunr'
import { useFetch } from './useFetch';
export function useSearch(query) {
export function useSearch(query, pageSize, pageNumber) {
let indexDataUrl = `${process.env.PUBLIC_URL}/rules/rule-index.json`;
let storeDataUrl = `${process.env.PUBLIC_URL}/rules/rule-index-store.json`;
@ -12,6 +12,8 @@ export function useSearch(query) {
const [storeData, storeDataError, storeDataIsLoading] = useFetch(storeDataUrl);
const [results, setResults] = useState([]);
const [numberOfHits, setNumberOfHits] = useState(null);
const [error, setError] = useState(null);
const [resultsAreloading, setResultsAreLoading] = useState(true);
React.useEffect(() => {
@ -23,11 +25,25 @@ export function useSearch(query) {
if (query) {
finalQuery = `titles:${query}`
}
const hits = index.search(finalQuery);
setResults(hits.map(({ ref }) => storeData[ref]));
let hits = []
setError(null);
try {
hits = index.search(finalQuery);
} catch (exception) {
if (exception instanceof lunr.QueryParseError) {
setError(exception.message);
} else {
throw exception;
}
}
setNumberOfHits(hits.length)
// const pageResults = hits;
const pageResults = hits.slice(pageSize*(pageNumber - 1), pageSize*(pageNumber));
setResults(pageResults.map(({ ref }) => storeData[ref]));
setResultsAreLoading(false);
}
}, [query, indexData, storeData, indexDataError, storeDataError, indexDataIsLoading, storeDataIsLoading]);
}, [query, pageSize, pageNumber, error, indexData, storeData, indexDataError, storeDataError, indexDataIsLoading, storeDataIsLoading]);
return [results, resultsAreloading];
return [results, numberOfHits, error, resultsAreloading];
}