I would like to create custom history back and forwards buttons using a React hook.
For reference, in case it helps, I'm trying to replicate the behaviour that can be seen in Spotify's web app. Their custom forwards and back buttons integrate seamlessly with the browser history buttons.
I think I have it mostly working, but with one issue. Here is my React hook:
import { useState, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
const useNavigationHistory = () => {
const history = useHistory();
const [length, setLength] = useState(0);
const [direction, setDirection] = useState(null);
const [historyStack, setHistoryStack] = useState([]);
const [futureStack, setFutureStack] = useState([]);
const canGoBack = historyStack.length > 0;
const canGoForward = futureStack.length > 0;
const goBack = () => {
if (canGoBack) {
history.goBack();
}
};
const goForward = () => {
if (canGoForward) {
history.goForward();
}
};
useEffect(() => {
return history.listen((location, action) => {
// if action is PUSH we are going forwards
if (action === 'PUSH') {
setDirection('forwards');
setLength(length + 1);
// add the new location to the historyStack
setHistoryStack([...historyStack, location.pathname]);
// clear the futureStack because it is not possible to go forward from here
setFutureStack([]);
}
// if action is POP we could be going forwards or backwards
else if (action === 'POP') {
// determine if we are going forwards or backwards
if (futureStack.length > 0 && futureStack[futureStack.length - 1] === location.pathname) {
setDirection('forwards');
// if we are going forwards, pop the futureStack and push it onto the historyStack
setHistoryStack([...historyStack, futureStack.pop()]);
setFutureStack(futureStack);
} else {
setDirection('backwards');
// if we are going backwards, pop the historyStack and push it onto the futureStack
setFutureStack([...futureStack, historyStack.pop()]);
setHistoryStack(historyStack);
}
setLength(historyStack.length);
}
});
}, [history, length, historyStack, futureStack]);
return { canGoBack, canGoForward, goBack, goForward };
};
export default useNavigationHistory;
In my testing this all seems to work fine when navigating forwards and back between various different pages.
If I navigate forwards by alternating between the same 2 pages, for example:
/home
/about
/home
/about
/home
/about
...then my logic to determine if we are going forwards or backwards falls apart.
I think it's this line:
if (futureStack.length > 0 && futureStack[futureStack.length - 1] === location.pathname) {
because the forwards pathname and the backwards pathname are identical, so it thinks I'm going forwards even when I'm going backwards.
I've been trying to figure out how I could resolve this, but haven't managed to get something working.
Is anyone able to help?
Maybe my solution is flawed and I need an entirely different method, I'm not sure.
It turns out that the data returned by react-router-dom's useHistory hook already includes a unique key value for every item in the history.
So the solution was simply to swap every instance of location.pathname
for location.key
in the hook, and now it behaves as desired.