phx-react
Version:
PHX REACT
137 lines • 5.5 kB
JavaScript
'use client';
import { useEffect, useRef } from 'react';
import { pageTrackerStore } from './page-tracker-store';
const DEBUG = false;
const useStrictModeDetector = () => {
const counterRef = useRef(0);
const isStrictMode = useRef(false);
const detectorHandler = () => {
counterRef.current++;
if (isStrictMode.current) {
return counterRef.current % 2 === 0;
}
return false;
};
return {
initStrictModeDetector: () => {
counterRef.current++;
if (counterRef.current === 2) {
isStrictMode.current = true;
}
},
strictModeDetector: () => {
return detectorHandler();
},
};
};
const debugLog = (message) => {
if (DEBUG) {
console.debug(`[DEBUG PAGE CHANGE] ${message}`);
}
};
const initHistoryState = () => {
const defaultData = {
__REACT_PAGE_TRACKER_INTERNAL__: {
pageIndex: 0,
referrer: document.referrer,
pageHistory: [location.href.replace(location.origin, '')],
pageHistoryLength: 1,
},
};
if (typeof history.state === 'object' && history.state !== null) {
history.replaceState({
...history.state,
...defaultData,
}, location.href);
}
else {
history.replaceState(defaultData, '');
}
pageTrackerStore.setState(defaultData.__REACT_PAGE_TRACKER_INTERNAL__);
};
export const usePageTrackerHandler = () => {
const { initStrictModeDetector } = useStrictModeDetector();
const pageIndex = useRef(0);
// for isLastPage usage
const visitedTotalLength = useRef(1);
useEffect(() => {
initHistoryState();
initStrictModeDetector();
/** Handle user operate original back/forward button or history.go()/back()/forward() or framework routing. **/
const handlePopState = (event) => {
var _a, _b;
const state = event.state;
const internal = state === null || state === void 0 ? void 0 : state.__REACT_PAGE_TRACKER_INTERNAL__;
if (!internal) {
// If the state isn't ours, ignore to avoid corrupting indices
return;
}
const statePageIndex = (_a = internal.pageIndex) !== null && _a !== void 0 ? _a : 0;
const pageEvent = pageIndex.current > statePageIndex ? 'back' : 'forward';
if (pageEvent === 'forward') {
pageIndex.current = statePageIndex; // moving forward to the state's index
}
else {
pageIndex.current = statePageIndex;
}
const pageHistory = [...(internal.pageHistory || [])];
pageTrackerStore.setState({
pageIndex: pageIndex.current,
isFirstPage: pageIndex.current === 0,
isLastPage: pageHistory.length === visitedTotalLength.current,
referrer: (_b = internal.referrer) !== null && _b !== void 0 ? _b : '',
pageHistory,
pageHistoryLength: visitedTotalLength.current,
pageEvent,
});
};
// save original pushState
const originalPushState = history.pushState.bind(history);
const normalize = (raw) => raw.replace(location.origin, '');
// override popstate using addEventListener
window.addEventListener('popstate', handlePopState);
history.pushState = (state, title, url) => {
const baseHistory = pageTrackerStore.getImmutablePageHistory();
// trim forward if navigating mid-stack
if (pageIndex.current < baseHistory.length - 1) {
baseHistory.splice(pageIndex.current + 1);
}
const nextUrl = url ? normalize(url) : normalize(location.href);
const newPageHistory = [...baseHistory, nextUrl];
const newPageIndex = newPageHistory.length - 1;
pageIndex.current = newPageIndex;
visitedTotalLength.current = newPageHistory.length;
const newState = {
pageIndex: newPageIndex,
referrer: normalize(window.location.href),
pageHistory: newPageHistory,
pageHistoryLength: visitedTotalLength.current,
};
const stateWithPageInfo = {
...state,
__REACT_PAGE_TRACKER_INTERNAL__: {
...newState,
},
};
pageTrackerStore.setState({
...newState,
isFirstPage: newPageIndex === 0,
isLastPage: true,
pageEvent: 'push',
});
debugLog(`pushState: stateWithPageInfo.pageIndex -->${stateWithPageInfo.__REACT_PAGE_TRACKER_INTERNAL__.pageIndex} ,pageIndex.current --> ${pageIndex.current} referrer -->${stateWithPageInfo.__REACT_PAGE_TRACKER_INTERNAL__.referrer}`);
return originalPushState(stateWithPageInfo, title || '', url || '');
};
return () => {
window.removeEventListener('popstate', handlePopState);
history.pushState = originalPushState;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
};
export const PageTracker = () => {
// eslint-disable-next-line no-use-before-define
usePageTrackerHandler();
return null;
};
//# sourceMappingURL=page-tracker.js.map