@ewb/reach-react
Version:
React Resource and Fetch stuff
181 lines (180 loc) • 7.88 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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.useCrud = void 0;
const react_1 = require("react");
const reach_1 = require("@ewb/reach");
const ReachContext_1 = require("./ReachContext");
function useCrud(path, data, props) {
const service = react_1.useContext(ReachContext_1.ReachContext);
const reach = react_1.useMemo(() => new reach_1.Reach(service), [service]);
const init = react_1.useRef(false);
const initialData = react_1.useMemo(() => JSON.parse(JSON.stringify(data)), [data]);
const defaultState = react_1.useMemo(() => (Object.assign(Object.assign({}, getNewState(path, initialData)), { dirty: !initialData[props.idKey], busy: Boolean(props.initWithGet && initialData[props.idKey]) })), [path, props.idKey, initialData]);
const ref = react_1.useRef(defaultState);
const queue = react_1.useRef([]);
const [state, setState] = react_1.useState(defaultState);
const id = react_1.useMemo(() => state.data[props.idKey], [state.data, props.idKey]);
const opts = react_1.useMemo(() => props.reachOptions || {}, [props.reachOptions]);
const fetch = react_1.useCallback((method) => () => __awaiter(this, void 0, void 0, function* () {
try {
if (!id) {
console.warn('Fetch used when id is undefined');
return;
}
const apiPath = `${path}/${id}`;
const data = yield reach.api(apiPath, Object.assign(Object.assign({}, opts), { method }));
ref.current = getNewState(path, Object.assign(Object.assign({}, ref.current.data), data));
setState(ref.current);
}
catch (error) {
ref.current.busy = false;
ref.current.error = error;
setState((s) => (Object.assign(Object.assign({}, s), { busy: false, error })));
}
}), [reach, opts, path, id]);
const patch = react_1.useCallback((state, key) => __awaiter(this, void 0, void 0, function* () {
try {
const id = state.data[props.idKey];
if (id && !Object.values(state.edited).some(Boolean)) {
return null;
}
if (ref.current.busy) {
queue.current.push(Object.assign({}, state));
return ref.current.data;
}
if (!ref.current.busy) {
ref.current.busy = true;
setState((s) => (Object.assign(Object.assign({}, s), { busy: true })));
}
let data;
let apiPath = `${path}${id ? `/${id}` : ''}`;
if (props.subPath) {
apiPath += `/${props.subPath}`;
}
if (!props.alwaysPost && (id || props.alwaysPatch)) {
const body = getPatchData(state, props.forcePatch, key);
data = yield reach.api(apiPath, Object.assign(Object.assign({}, opts), { method: 'PATCH', body }));
}
else {
data = yield reach.api(apiPath, Object.assign(Object.assign({}, opts), { method: 'POST', body: ref.current.data }));
}
if (queue.current.length > 0) {
const patchState = queue.current[0];
queue.current.splice(0, 1);
ref.current.busy = false;
yield patch(patchState);
}
else if (!props.dontSetStateOnPost) {
let edited = {};
if (key) {
edited = ref.current.edited;
edited[key] = false;
}
ref.current = getNewState(path, data || ref.current.data, edited, ref.current.meta);
setState(ref.current);
}
return data;
}
catch (error) {
setState((s) => (Object.assign(Object.assign({}, s), { busy: false, error })));
ref.current.busy = false;
ref.current.error = error;
return null;
}
}), [
reach,
path,
props.idKey,
props.subPath,
opts,
props.dontSetStateOnPost,
props.alwaysPatch,
props.alwaysPost,
props.forcePatch,
]);
const set = react_1.useCallback((key, disableAutoSave = props.disableAutoSave) => (event, meta) => {
const value = event && typeof event === 'object' && 'target' in event ? event.target.value : event;
setState((s) => {
const edited = Object.assign(Object.assign({}, s.edited), { [key]: !s.initialData[key] || s.initialData[key] !== value });
ref.current = Object.assign(Object.assign({}, s), { edited, dirty: Object.values(edited).some(Boolean), data: Object.assign(Object.assign({}, s.data), { [key]: value }) });
if (meta) {
ref.current.meta[key] = meta;
}
if (!disableAutoSave) {
patch(ref.current, key);
}
return ref.current;
});
}, [props.disableAutoSave, patch]);
const save = react_1.useCallback(() => patch(ref.current), [patch]);
const setData = react_1.useCallback((data, meta = {}, isEdited = true) => {
setState((s) => {
const edited = Object.assign({}, s.edited);
// @ts-ignore
Object.keys(data).forEach((key) => {
if (s.data[key] !== data[key]) {
edited[key] = isEdited;
}
});
ref.current = getNewState(s.path, Object.assign(Object.assign({}, s.data), data), edited, Object.assign(Object.assign({}, s.meta), meta));
return ref.current;
});
}, []);
const actions = react_1.useMemo(() => ({ read: fetch('GET'), delete: fetch('DELETE') }), [fetch]);
react_1.useEffect(() => {
if (!init.current && props.initWithGet && id) {
actions.read();
}
init.current = true;
}, [props.initWithGet, id, actions.read]);
return react_1.useMemo(() => {
const ret = [state, set, save, setData, actions, setState];
ret.state = state;
ret.set = set;
ret.save = save;
ret.setData = setData;
ret.actions = actions;
ret.setState = setState;
return ret;
}, [state, set, save, setData, actions, setState]);
}
exports.useCrud = useCrud;
function getPatchData(state, forcePatch, key) {
if (key) {
return {
[key]: state.data[key],
};
}
const patchData = {};
for (const key in state.edited) {
if (state.edited[key]) {
patchData[key] = state.data[key];
}
}
if (forcePatch) {
for (const key of forcePatch) {
patchData[key] = state.data[key];
}
}
return patchData;
}
function getNewState(path, data, edited = {}, meta = {}) {
return {
path,
busy: false,
dirty: Object.values(edited).some(Boolean),
data,
initialData: data,
edited,
meta,
};
}