next-batch-router
Version:
An alternative to next/router that batches push and replace calls. It allows for multiple pushes without overwriting each other.
154 lines (146 loc) • 6.63 kB
JavaScript
import React, { useReducer, useRef, useContext } from 'react';
import { parseUrl } from 'next/dist/shared/lib/router/utils/parse-url';
import Router from 'next/router';
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
function __awaiter(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());
});
}
class BatchRouterCore {
constructor(forceRender) {
this.queue = [];
this.pushHistory = false;
this.shallow = false;
this.scroll = false;
this.locale = undefined;
this.renderTriggered = false;
this.routePromise = undefined;
this.resolveRoutePromise = undefined;
this.forceRender = forceRender;
}
push(url, as, options = {}) {
return __awaiter(this, void 0, void 0, function* () {
return this.change("push", url, as, options);
});
}
replace(url, as, options = {}) {
return __awaiter(this, void 0, void 0, function* () {
return this.change("replace", url, as, options);
});
}
change(history, url, as, options = {}) {
var _a, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
// Add to queue for batching
this.queue.push({
query: (_a = url.query) !== null && _a !== void 0 ? _a : {},
asQuery: (_c = (_b = as === null || as === void 0 ? void 0 : as.query) !== null && _b !== void 0 ? _b : url.query) !== null && _c !== void 0 ? _c : {},
hash: (as === null || as === void 0 ? void 0 : as.hash) !== undefined ? as.hash : url.hash
});
// Merge options instead of queueing them
if (history === "push") this.pushHistory = true;
if (options.scroll !== false) this.scroll = true; // All must be false to be false.
if (options.shallow !== true) this.shallow = false; // All must be true to be true.
if (options.locale !== undefined) this.locale = options.locale;
// Trigger force render so that flush() would be called.
// Needs to queueMicrotask because rerender happened between multiple calls to this method.
// This problem occured when using lodash.debounce.
// Might need to use setTimeout if other timing issues occur.
if (!this.renderTriggered) {
queueMicrotask(() => this.forceRender());
// this.renderTriggered is required for triggering rerender once.
// using queueMicrotask caused multiple unneeded rerenders.
this.renderTriggered = true;
}
return this.routePromise;
});
}
flush() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (!this.queue.length) return;
let newQuery = Object.assign({}, Router.query);
let newAsQuery = parseUrl(Router.asPath).query;
let newHash = window.location.hash;
for (const {
query,
asQuery,
hash
} of this.queue) {
if (isUpdaterFunction(query)) newQuery = turnWriteQueryObjectToNextQueryObject(query(newQuery));else applyWriteQueryObjectToNextQueryObject(query, newQuery);
if (isUpdaterFunction(asQuery)) newAsQuery = turnWriteQueryObjectToNextQueryObject(asQuery(newAsQuery));else applyWriteQueryObjectToNextQueryObject(asQuery, newAsQuery);
if (hash !== undefined) newHash = hash;
}
const routePromise = (this.pushHistory ? Router.push : Router.replace)({
query: newQuery
}, {
query: newAsQuery,
hash: newHash
}, {
scroll: this.scroll,
shallow: this.shallow,
locale: this.locale
});
(_a = this.resolveRoutePromise) === null || _a === void 0 ? void 0 : _a.call(this, routePromise);
this.clear();
return routePromise;
});
}
clear() {
this.pushHistory = false;
this.queue = [];
this.renderTriggered = false;
this.shallow = true;
this.scroll = false;
this.locale = undefined;
this.routePromise = new Promise(resolve => this.resolveRoutePromise = resolve);
}
}
function isUpdaterFunction(input) {
return typeof input === "function";
}
function turnWriteQueryObjectToNextQueryObject(obj) {
const nextQueryObj = {};
for (const [k, v] of Object.entries(obj)) if (v != null) nextQueryObj[k] = Array.isArray(v) ? v.map(String) : String(v);
return nextQueryObj;
}
function applyWriteQueryObjectToNextQueryObject(write, obj) {
// TODO: Should clone?
for (const [k, v] of Object.entries(write)) {
if (v === undefined) continue;else if (v === null) delete obj[k];else obj[k] = Array.isArray(v) ? v.map(String) : String(v);
}
}
const BatchRouterContext = /*#__PURE__*/React.createContext(null);
/** Provider required to use useBatchRouter hook */
function BatchRouterProvider(props) {
const [, forceRender] = useReducer(prev => prev + 1, 0);
const batchRouter = useRef(new BatchRouterCore(forceRender));
batchRouter.current.flush();
return /*#__PURE__*/React.createElement(BatchRouterContext.Provider, {
value: batchRouter.current
}, props.children);
}
/** Get the BatchRouter instance. */
function useBatchRouter() {
const batchRouter = useContext(BatchRouterContext);
if (batchRouter === null) throw Error("Could not find BatchRouter. Please ensure the component is wrapped in a <BatchRouterProvider>");
return batchRouter;
}
export { BatchRouterCore, BatchRouterProvider, useBatchRouter };
//# sourceMappingURL=index.js.map