3

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);
            }
1
  • I'll leave the question up just in case there is a better answer for those in the future to reference and mark as self answered in a couple of days if there isn't one forthcoming. Commented Jan 13, 2018 at 5:11

1 Answer 1

1

The code provided in the edited response solved the problem and has been working for a week now with no issues.

I hope it helps someone else facing the same issue.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.