I have a need to prevent the default behaviour of StackNavigator that creates multiple copies of a screen and keeps them in memory (and has random unmounting behaviour). I only want a single instance of each of my screens.
I have overriden the router as per the code below. What it does is look for an existing screen in the stack when navigating and if it exists move it to the current index shuffling everything else around. It's potentially more convoluted than required but it seems to me that you shouldn't mess around with the current index.
const AppNavigator = StackNavigator(Routes, {});
//move this into routes.js if it works
const prevGetStateForAction = AppNavigator.router.getStateForAction;
AppNavigator.router = {
...AppNavigator.router,
getStateForAction(action, state) {
//check if route is already on stack and go to it instead of adding a new item on stack
//NOTE: This will break things like being able to have push notification messages on the same route but different messages and being able to go back through them
//if that becomes a problem just opt out for certain route names eg if(action.routeName == "Messages") return prevGetStateForAction(aciton,state)
if(state && action.type == 'Navigation/NAVIGATE' && state.routes !== undefined ) {
//console.log("getStateForAction state",state,"action,",action);
var i = -1;
//try find the route in the stack
for(var c =0; c < state.routes.length; c++) {
if(state.routes[c].routeName == action.routeName) {
i = c;
break;
}
}
//found it but we're already there so do nothing as we're trying to navigate to ourselves
if(i == state.index)
return null;
//didn't find it - add screen to stack - ie call default action
if(i == -1)
return prevGetStateForAction(action,state);
//found it - move it to just after index and increment index - i think
console.log("stacknavigator is trying to duplicate route - moving back to previous route");
var route = state.routes[i];
var routes = state.routes.splice(i,1);
//you've just moved the item that index was pointing at if it was greater than where our item was
var newIndex = 0;
if(state.index > i)
newIndex = state.index -1;
else
newIndex = state.index;
//index was at the end so we just take the array and add our item at the end - no slices needed
if(state.index == state.routes.length -1)
var routes = [ ...routes, route];
else
var routes = [
...state.routes.slice(0,newIndex+1),
route,
...state.routes.slice(state.index+1)
];
return {
...state,
routes: routes,
index: newIndex
}
}
return prevGetStateForAction(action,state);
}
It works for quite some time (as in you can navigate around happily without creating duplicates) but eventually fails and I can't work out why - you can see my console.log call when a dupe is hit but that is never hit - the code is running but . It could be a mistake in the code but I feel it's more likely a behaviour of React Navigation I'm missing. Possibly the Navigation/back and back button behaviour I'm not taking into account.
Can someone correct my code or provide an alternate method of preventing duplicate screens in the stack?
edit - as I can't fit the code in the comments
I had another think about it and it seemed to me like I probably shouldn't be trying to keep the index the same since my screen will be at the end of the stack now.
Anyway it seems to have solved the problem as I haven't been able to reproduce a duplicated screen.
const AppNavigator = StackNavigator(Routes, {});
//move this into routes.js if it works
const prevGetStateForAction = AppNavigator.router.getStateForAction;
AppNavigator.router = {
...AppNavigator.router,
getStateForAction(action, state) {
//check if route is already on stack and go to it instead of adding a new item on stack
//NOTE: This will break things like being able to have push notification messages on the same route but different messages and being able to go back through them
//if that becomes a problem just opt out for certain route names eg if(action.routeName == "Messages") return prevGetStateForAction(aciton,state)
if(state && action.type == 'Navigation/NAVIGATE' && state.routes !== undefined ) {
//console.log("getStateForAction state",state,"action,",action);
var i = -1;
//try find the route in the stack
for(var c =0; c < state.routes.length; c++) {
if(state.routes[c].routeName == action.routeName) {
i = c;
break;
}
}
//found it but we're already there so do nothing as we're trying to navigate to ourselves
if(i == state.index) {
console.log("getstateforaction() - you're trying to navigate to yourself!");
return null;
}
//didn't find it - add screen to stack - ie call default action
if(i == -1) {
console.log("getstateforaction() - no duplicate screen found");
return prevGetStateForAction(action,state);
}
//found it - move it to just after index and increment index - i think
console.log("stacknavigator is trying to duplicate route - moving back to previous route");
var route = state.routes[i];
var routes = state.routes;
routes.splice(i,1);
routes = [
...routes,
route
];
newIndex = routes.length-1;
return {
...state,
routes: routes,
index: newIndex
}
}
return prevGetStateForAction(action,state);
}