@react-navigation/native
Version:
React Native integration for React Navigation
241 lines (195 loc) • 9.68 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useLinking;
var React = _interopRequireWildcard(require("react"));
var _core = require("@react-navigation/core");
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
const getStateLength = state => {
let length = 0;
if (state.history) {
length = state.history.length;
} else {
length = state.index + 1;
}
const focusedState = state.routes[state.index].state;
if (focusedState && !focusedState.stale) {
// If the focused route has history entries, we need to count them as well
length += getStateLength(focusedState) - 1;
}
return length;
};
let isUsingLinking = false;
function useLinking(ref, {
config,
getStateFromPath = _core.getStateFromPath,
getPathFromState = _core.getPathFromState
}) {
React.useEffect(() => {
if (isUsingLinking) {
throw new Error("Looks like you are using 'useLinking' in multiple components. This is likely an error since URL integration should only be handled in one place to avoid conflicts. Also ensure that you set your android activity launchMode to single task in your AndroiManifest.xml file.");
} else {
isUsingLinking = true;
}
return () => {
isUsingLinking = false;
};
}); // We store these options in ref to avoid re-creating getInitialState and re-subscribing listeners
// This lets user avoid wrapping the items in `React.useCallback` or `React.useMemo`
// Not re-creating `getInitialState` is important coz it makes it easier for the user to use in an effect
const configRef = React.useRef(config);
const getStateFromPathRef = React.useRef(getStateFromPath);
const getPathFromStateRef = React.useRef(getPathFromState);
React.useEffect(() => {
configRef.current = config;
getStateFromPathRef.current = getStateFromPath;
getPathFromStateRef.current = getPathFromState;
}, [config, getPathFromState, getStateFromPath]); // Make it an async function to keep consistent with the native impl
const getInitialState = React.useCallback(async () => {
const path = location.pathname + location.search;
if (path) {
return getStateFromPathRef.current(path, configRef.current);
} else {
return undefined;
}
}, []);
const previousStateLengthRef = React.useRef(undefined);
const previousHistoryIndexRef = React.useRef(0);
const pendingIndexChangeRef = React.useRef();
const pendingStateUpdateRef = React.useRef(false);
const pendingStateMultiUpdateRef = React.useRef(false); // If we're navigating ahead >1, we're not restoring whole state,
// but just navigate to the selected route not caring about previous routes
// therefore if we need to go back, we need to pop screen and navigate to the new one
// Possibly, we will need to reuse the same mechanism.
// E.g. if we went ahead+4 (numberOfIndicesAhead = 3), and back-2,
// actually we need to pop the screen we navigated
// and navigate again, setting numberOfIndicesAhead to 1.
const numberOfIndicesAhead = React.useRef(0);
React.useEffect(() => {
window.addEventListener('popstate', () => {
var _history$state$index, _history$state;
const navigation = ref.current;
if (!navigation) {
return;
}
const previousHistoryIndex = previousHistoryIndexRef.current;
const historyIndex = (_history$state$index = (_history$state = history.state) === null || _history$state === void 0 ? void 0 : _history$state.index) !== null && _history$state$index !== void 0 ? _history$state$index : 0;
previousHistoryIndexRef.current = historyIndex;
if (pendingIndexChangeRef.current === historyIndex) {
pendingIndexChangeRef.current = undefined;
return;
}
const state = navigation.getRootState();
const path = getPathFromStateRef.current(state, configRef.current);
let canGoBack = true;
let numberOfBacks = 0;
if (previousHistoryIndex === historyIndex) {
if (location.pathname + location.search !== path) {
pendingStateUpdateRef.current = true;
history.replaceState({
index: historyIndex
}, '', path);
}
} else if (previousHistoryIndex > historyIndex) {
numberOfBacks = previousHistoryIndex - historyIndex - numberOfIndicesAhead.current;
if (numberOfBacks > 0) {
pendingStateMultiUpdateRef.current = true;
if (numberOfBacks > 1) {
pendingStateMultiUpdateRef.current = true;
}
pendingStateUpdateRef.current = true;
for (let i = 0; i < numberOfBacks; i++) {
navigation.goBack();
}
} else {
canGoBack = false;
}
}
if (previousHistoryIndex < historyIndex || !canGoBack) {
if (canGoBack) {
numberOfIndicesAhead.current = historyIndex - previousHistoryIndex - 1;
} else {
navigation.goBack();
numberOfIndicesAhead.current -= previousHistoryIndex - historyIndex;
}
const state = getStateFromPathRef.current(location.pathname + location.search, configRef.current);
pendingStateMultiUpdateRef.current = true;
if (state) {
const action = (0, _core.getActionFromState)(state);
pendingStateUpdateRef.current = true;
if (action !== undefined) {
navigation.dispatch(action);
} else {
navigation.resetRoot(state);
}
}
}
});
}, [ref]);
React.useEffect(() => {
var _ref$current;
if (ref.current && previousStateLengthRef.current === undefined) {
previousStateLengthRef.current = getStateLength(ref.current.getRootState());
}
if (ref.current && location.pathname + location.search === '/') {
var _history$state$index2, _history$state2;
history.replaceState({
index: (_history$state$index2 = (_history$state2 = history.state) === null || _history$state2 === void 0 ? void 0 : _history$state2.index) !== null && _history$state$index2 !== void 0 ? _history$state$index2 : 0
}, '', getPathFromStateRef.current(ref.current.getRootState(), configRef.current));
}
const unsubscribe = (_ref$current = ref.current) === null || _ref$current === void 0 ? void 0 : _ref$current.addListener('state', () => {
var _previousStateLengthR, _history$state$index3, _history$state3;
const navigation = ref.current;
if (!navigation) {
return;
}
const state = navigation.getRootState();
const path = getPathFromStateRef.current(state, configRef.current);
const previousStateLength = (_previousStateLengthR = previousStateLengthRef.current) !== null && _previousStateLengthR !== void 0 ? _previousStateLengthR : 1;
const stateLength = getStateLength(state);
if (pendingStateMultiUpdateRef.current) {
if (location.pathname + location.search === path) {
pendingStateMultiUpdateRef.current = false;
} else {
return;
}
}
previousStateLengthRef.current = stateLength;
if (pendingStateUpdateRef.current && location.pathname + location.search === path) {
pendingStateUpdateRef.current = false;
return;
}
let index = (_history$state$index3 = (_history$state3 = history.state) === null || _history$state3 === void 0 ? void 0 : _history$state3.index) !== null && _history$state$index3 !== void 0 ? _history$state$index3 : 0;
if (previousStateLength === stateLength) {
// If no new enrties were added to history in our navigation state, we want to replaceState
if (location.pathname + location.search !== path) {
history.replaceState({
index
}, '', path);
previousHistoryIndexRef.current = index;
}
} else if (stateLength > previousStateLength) {
// If new enrties were added, pushState until we have same length
// This won't be accurate if multiple enrties were added at once, but that's the best we can do
for (let i = 0, l = stateLength - previousStateLength; i < l; i++) {
index++;
history.pushState({
index
}, '', path);
}
previousHistoryIndexRef.current = index;
} else if (previousStateLength > stateLength) {
const delta = previousStateLength - stateLength; // We need to set this to ignore the `popstate` event
pendingIndexChangeRef.current = index - delta; // If new enrties were removed, go back so that we have same length
history.go(-delta);
}
});
return unsubscribe;
});
return {
getInitialState
};
}
//# sourceMappingURL=useLinking.js.map