@ewb/reach-react
Version:
React Resource and Fetch stuff
163 lines (162 loc) • 8.26 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.useSearch = void 0;
const reach_1 = require("@ewb/reach");
const react_1 = require("react");
const ReachContext_1 = require("./ReachContext");
const SKIP = 0;
const LIMIT = 10;
const COUNT = 0;
const LIMIT_KEY = 'limit';
const SKIP_KEY = 'skip';
const COUNT_HEADER = 'X-Total-Count';
const toInitialState = (props) => ({
busy: !props.disableInit,
limit: props.limit || LIMIT,
skip: props.skip || props.defaultSkip || SKIP,
page: 0,
count: props.count || COUNT,
items: props.defaultItems || [],
searchQuery: props.query || {},
hasFetched: false,
});
const toInitialQuery = (state, skipKey, limitKey = LIMIT_KEY) => (Object.assign(Object.assign({}, state.searchQuery), { [limitKey]: state.limit, [skipKey]: state.skip }));
function useSearch(path, props) {
const { skipKey = SKIP_KEY, countHeader = COUNT_HEADER, responseToData, reachOptions, skipPages, paginationMode = 'client', searchNextKey, } = props;
const init = react_1.useRef(false);
const searchNext = react_1.useRef('');
const service = react_1.useContext(ReachContext_1.ReachContext);
const initialState = react_1.useMemo(() => toInitialState(props), [props]);
const [state, setState] = react_1.useState(initialState);
const _page = react_1.useRef(state.skip);
const initialQuery = react_1.useRef(toInitialQuery(state, skipKey, props.limitKey));
const searchQuery = react_1.useRef(initialQuery.current);
const reach = react_1.useMemo(() => new reach_1.Reach(service), [service]);
const search = react_1.useCallback((page, reachQuery) => __awaiter(this, void 0, void 0, function* () {
try {
const paginate = page !== 0;
_page.current = page;
if (page === 0) {
searchQuery.current = Object.assign(Object.assign({}, initialQuery.current), (reachQuery || {}));
}
const querySkip = skipPages ? page : page * state.limit;
const query = reachQuery
? Object.assign(Object.assign(Object.assign({}, initialQuery.current), reachQuery), { [skipKey]: querySkip }) : Object.assign(Object.assign({}, initialQuery.current), { [skipKey]: querySkip });
const response = yield reach.api(path, Object.assign(Object.assign({}, reachOptions), { query, noJson: true }));
const json = (yield response.json());
const newState = getNewStateFromResponse(response, json, querySkip, query, countHeader);
const toNewItems = (s, items) => items ? (paginate && paginationMode === 'client' ? [...s.items, ...items] : items) : s.items;
if (searchNextKey && searchNextKey in json) {
searchNext.current = String(json[searchNextKey]);
}
if (typeof responseToData === 'function') {
let retItems = [];
setState((s) => {
const _a = responseToData(json, Object.assign(Object.assign({}, s), newState), response, paginate), { items } = _a, responseState = __rest(_a, ["items"]);
if (items) {
retItems = items;
}
return Object.assign(Object.assign(Object.assign(Object.assign({}, s), newState), responseState), { items: toNewItems(s, items) });
});
return retItems;
}
if (!Array.isArray(json)) {
throw new Error('useSearch error. json response is not typeof array. Use responseToData to parse response');
}
setState((s) => (Object.assign(Object.assign(Object.assign({}, s), newState), { items: toNewItems(s, json) })));
return json;
}
catch (error) {
setState((s) => (Object.assign(Object.assign({}, s), { busy: false, error })));
return [];
}
}), [path, responseToData, reachOptions, skipKey, countHeader, state.limit, skipPages, paginationMode, searchNextKey]);
const next = react_1.useCallback((searchQuery, page = _page.current + 1) => __awaiter(this, void 0, void 0, function* () {
if (state.items.length < state.count) {
setState((s) => (Object.assign(Object.assign({}, s), { busy: true })));
if (searchNext.current) {
return search(page, Object.assign(Object.assign({}, searchQuery), { [String(searchNextKey)]: searchNext.current }));
}
return search(page, searchQuery);
}
return null;
}), [state.items.length, state.count, search, searchNextKey]);
const info = react_1.useMemo(() => ({
limit: state.limit,
skip: state.skip,
page: _page.current,
count: state.count,
hasFetched: state.hasFetched,
json: state.json,
}), [state.limit, state.skip, state.count, state.hasFetched, state.json]);
const actions = react_1.useMemo(() => ({
unshift: (...items) => {
setState((s) => (Object.assign(Object.assign({}, s), { items: [...items, ...s.items], count: s.count + items.length })));
},
splice: (start, deleteCount = 1, ...items) => {
setState((s) => {
const newItems = [...s.items];
newItems.splice(start, deleteCount, ...items);
return Object.assign(Object.assign({}, s), { items: newItems, count: s.count - deleteCount + items.length });
});
},
push: (...items) => {
setState((s) => {
return Object.assign(Object.assign({}, s), { items: [...s.items, ...items], count: s.count + items.length });
});
},
search: (query) => {
_page.current = 0;
setState((s) => (Object.assign(Object.assign({}, s), { busy: true })));
return search(props.defaultSkip || 0, query);
},
set: (items) => setState((s) => (Object.assign(Object.assign({}, s), { items }))),
map: (fn) => setState((s) => (Object.assign(Object.assign({}, s), { items: s.items.map(fn) }))),
filter: (fn) => setState((s) => (Object.assign(Object.assign({}, s), { items: s.items.filter(fn) }))),
}), [search, props.defaultSkip]);
react_1.useEffect(() => {
if (!init.current && !props.disableInit) {
search(0).then();
}
init.current = true;
}, [search, props.disableInit]);
return react_1.useMemo(() => [state.busy, state.items, state.error, next, info, actions], [state.busy, state.items, state.error, next, info, actions]);
}
exports.useSearch = useSearch;
function getNewStateFromResponse(response, json, skip, searchQuery, countHeader) {
const newState = {
skip,
searchQuery,
busy: false,
hasFetched: true,
error: null,
json,
};
if (countHeader) {
const count = Number(response.headers.get(countHeader));
if (count && !isNaN(count)) {
newState.count = count;
}
}
return newState;
}