Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] Can stack router have replace like History API? #802

Closed
joonhocho opened this issue Mar 24, 2017 · 33 comments
Closed

[Feature Request] Can stack router have replace like History API? #802

joonhocho opened this issue Mar 24, 2017 · 33 comments

Comments

@joonhocho
Copy link

@joonhocho joonhocho commented Mar 24, 2017

I think replace is very common routing pattern that html history api also has it.

You don't always want to push to stack, but sometimes want to replace it with or without animation instead of going through pop and push (two animation phases).

According to doc, calling reset will empty your stack, but that's not how replace should work.

It should work like so:
state: a -> b -> c
action: c calls replace(d)
result: a -> b -> d

state: a -> b -> c
action: b calls replace(d)
result: a -> d

@joonhocho
Copy link
Author

@joonhocho joonhocho commented Mar 24, 2017

Related #295 #774

@joonhocho
Copy link
Author

@joonhocho joonhocho commented Mar 24, 2017

Issues with using reset to replace:
#395 #691
#199
#335

@dioxide
Copy link

@dioxide dioxide commented Mar 24, 2017

+1 , agree with you, the REPLACE pattern is very useful!

@justbettereveryday
Copy link

@justbettereveryday justbettereveryday commented Mar 25, 2017

I just think that : it will empty the stack,but all objects you have registered before are also here,so you can use them as usual.

@joonhocho
Copy link
Author

@joonhocho joonhocho commented Mar 25, 2017

@justbettereveryday Could you explain more about how to get all objects you have registered?

@maximovicd
Copy link

@maximovicd maximovicd commented Mar 25, 2017

I also use replace in my app and I think it's a good idea to have an action like that.

Until that gets done, I have used a workaround of passing my own flag into a navigate action, and when I detect it, I manually kick the last route from state before doing getStateForAction. It seems to work, with a little problem of missing the animation.

@justbettereveryday
Copy link

@justbettereveryday justbettereveryday commented Mar 29, 2017

@joonhocho
Just like this:

A.js

  const DefaultLaunch = StackNavigator({

    Main: { screen: Main },

    Login: { screen: LoginComponent },

    MySetting: { screen: MySetting }},
   {
        headerMode: 'screen'
    })

Main.js

const MainScreen = TabNavigator({
    Task: { screen: Task },
    Shop: { screen: Shop }
})

then you can reset your current vc anytime!

@aranda-adapptor aranda-adapptor mentioned this issue Apr 6, 2017
3 of 9 tasks complete
@ericvicenti
Copy link
Contributor

@ericvicenti ericvicenti commented Apr 13, 2017

I think it is a good idea to have replace functionality.

What if we added a key to the navigate action, so that you could specify the current key to replace the current screen?

Lets track this issue as a part of #135, where we plan on adding a key to the navigate action

@spencercarli
Copy link
Member

@spencercarli spencercarli commented Apr 24, 2017

For anyone else that comes along this issue I'm currently using the following solution. I specify a new action type inside the getStateForAction handler and then use dispatch rather than navigate in my component.

const prevGetStateForActionHomeStack = HomeStack.router.getStateForAction;
HomeStack.router = {
  ...HomeStack.router,
  getStateForAction(action, state) {
    if (state && action.type === 'ReplaceCurrentScreen') {
      const routes = state.routes.slice(0, state.routes.length - 1);
      routes.push(action);
      return {
        ...state,
        routes,
        index: routes.length - 1,
      };
    }
    return prevGetStateForActionHomeStack(action, state);
  },
};
replaceScreen = () => {
  const { locations, position } = this.props.navigation.state.params;
  this.props.navigation.dispatch({
    key: 'NearMeMap',
    type: 'ReplaceCurrentScreen',
    routeName: 'NearMeMap',
    params: { locations, position },
  });
};

I also put together a short video walking through this code, if that would help anyone.

@joonhocho
Copy link
Author

@joonhocho joonhocho commented Apr 24, 2017

@spencercarli Awesome! Do you know how to animate replace action by any chance?

@spencercarli
Copy link
Member

@spencercarli spencercarli commented Apr 25, 2017

@joonhocho I don't - do you have an example of what kind of animation you're thinking of?

@wilomgfx
Copy link

@wilomgfx wilomgfx commented Apr 26, 2017

@spencercarli probably the base animation it has when navigating to a screen :p

@timscott
Copy link

@timscott timscott commented Jun 3, 2017

@spencercarli - I tried your solution, and it's not woking for me.

I check MyNavigator.router.getStateForAction just before I render it (<MyNavigator />), and it points to my custom function. However, when I reach the point to replace a route and call this.props.navigation.dispatch I can see that it's calling the original getStateForAction.

Thoughts?

@cyberdelphos
Copy link

@cyberdelphos cyberdelphos commented Jun 13, 2017

@timscott I had to change the code a bit and it's working correctly.
Instead of:

HomeStack.router = {
  ...HomeStack.router,
  getStateForAction(action, state) {
     ...

Modified it directly:

HomeStack.router.getStateForAction = (action, state) => {
  ... (everything else remains the same)

You can see this direct modification mentioned in the docs for routers: https://reactnavigation.org/docs/routers/

@bleonard
Copy link

@bleonard bleonard commented Jun 25, 2017

In case it's helpful, I tried a different approach to a new action name to try and not mess around as much as possible with the normal implementation of getStateForAction.

When I navigated, I did this:

navigation.navigate("ScreenName", { replaceRoute: true });

And then looked for that in the getStateForAction. By messing with the routes the normal one returned, I felt better about being more resistant to various interesting things the built-in stack router is doing.

/* @flow */

import type {
  NavigationAction,
  NavigationState
} from "react-navigation/lib/TypeDefinition";

import NavigationActions from "react-navigation/lib/NavigationActions";

const updateStackRouter = function(StackNavigator: any) {
  // generally defer to the "real" one
  const parentGetStateForAction = StackNavigator.router.getStateForAction;
  StackNavigator.router.getStateForAction = function(
    action: NavigationAction,
    inputState?: ?NavigationState
  ): ?NavigationState {
    const state: ?NavigationState = parentGetStateForAction(action, inputState);

    // fix it up if applicable
    if (state && action.type === NavigationActions.NAVIGATE) {
      if (action.params && action.params.replaceRoute) {
        delete action.params.replaceRoute;
        if (state.routes.length > 1 && state.index > 0) {
          const oldIndex = state.index - 1;
          // remove one that we are replacing
          state.routes.splice(oldIndex, 1);
          // index now one less
          state.index = oldIndex;
        }
      }
    }

    return state;
  };
};

export default updateStackRouter;
@digitalmaster
Copy link

@digitalmaster digitalmaster commented Jun 30, 2017

Any movement here?.. just trying to do the same thing i did in ex-navigation like so: this.props.navigator.replace(Router.getRoute('routeName'))

@joshuapinter
Copy link

@joshuapinter joshuapinter commented Jul 31, 2017

@cyberdelphos' corrected method works great with the latest (beta.11) version of the library.

Thanks for posting that update here @cyberdelphos! 🙏 You saved me hours trying to figure out what's wrong with @spencercarli's original method.

@leesolway
Copy link

@leesolway leesolway commented Sep 1, 2017

I really liked @bleonard approach to the problem. I would love to see more helpers using this method, or having this level of simplicity such as reset.

@ahanriat
Copy link

@ahanriat ahanriat commented Sep 6, 2017

Not sure if that's gonna work with nested navigators though

@bleonard
Copy link

@bleonard bleonard commented Sep 10, 2017

I haven't tried with nested, but it's only modifying the one stack that it's dealing with, so I would think it was ok.

@DanielRamosAcosta
Copy link

@DanielRamosAcosta DanielRamosAcosta commented Sep 13, 2017

Both @spencercarli and @bleonard solutions loses the transition animation, is there any way to preserve it? Thanks

@tommeier
Copy link

@tommeier tommeier commented Oct 20, 2017

Doesn't work with nested routes unfortunately

@jinxac
Copy link

@jinxac jinxac commented Nov 3, 2017

@spencercarli thanks for sharing the code. Can you please help to understand how this would work with nested navigators? I am getting #1127 when I try this with nested routes

@jinxac
Copy link

@jinxac jinxac commented Nov 4, 2017

I was exploring this out a bit more and here are few things which one should be aware before implementing:-

  1. This does not work with nested routes.
  2. This does not work with tab routes.
  3. If you are trying to replace more than once screen using the code shared above you are liking to get an unidentified error message for key being missing.

To avoid point 3 I had changed my code to following:-

const defaultGetStateForAction = MainNavigation.router.getStateForAction;

MainNavigation.router.getStateForAction = (action, state) => {
  if (state && action.type === "replace") {
    const routes = state.routes.slice(0, state.routes.length - 1);
    action.key =  `id-${Date.now()}-${routes.length - 1}`;
    routes.push(action);
    return {
      ...state,
      routes,
      index: routes.length - 1
    };
  }
  return defaultGetStateForAction(action, state);
};
@gianpieroc
Copy link

@gianpieroc gianpieroc commented Dec 21, 2017

@jinxac what about nested routes? Any solution?

@jinxac
Copy link

@jinxac jinxac commented Dec 22, 2017

@gianpieroc i had used nested-routes when i started the porting of code to react-navigation but after seeing the open issues related to nesting i completing avoided it and created a single stack navigator itself. So currently no solution for nested routes either.

@parlir
Copy link

@parlir parlir commented Mar 8, 2018

For those who have nested do the following (note pullat is a lodash function but basically you just need to remove second to last element)
@gianpieroc
@jinxac

const prevGetStateForActionMainStack = MainStack.router.getStateForAction;
MainStack.router.getStateForAction = (action, state) => {
  if (state && action.type === "ReplaceCurrentScreen") {
    action.type = "Navigation/NAVIGATE"; // change it to navigate to get the next state
    let newState = prevGetStateForActionMainStack(action, state); // Let default run because nesting hard to build
    pullAt(newState.routes, [newState.routes.length - 2]); // pull out second to last element
    newState.index = newState.index - 1;
    return newState;
  }
  return prevGetStateForActionMainStack(action, state);
};
@pacozaa
Copy link

@pacozaa pacozaa commented Apr 2, 2018

@brentvatne still no example in there :(

@ericvicenti
Copy link
Contributor

@ericvicenti ericvicenti commented Apr 2, 2018

@pacozaa, feel free to add a link to the navigation playground code in this repository which demonstrates the replace functionality, and send a pull request for that 🙂

@wv1124
Copy link

@wv1124 wv1124 commented Apr 3, 2018

ExceptionsManager.js:63 navigation.state.routes[2].key "scene_OrderList" conflicts with another route!

@b-asaf
Copy link

@b-asaf b-asaf commented Apr 11, 2018

Can replace() method be used to replace stacks, instead of using reset()?
currently, to replace stacks I am doing something like this:
import { NavigationActions } from 'react-navigation';

export default(navigation, stackName, screenName, params = {}) => {
    const resetAction = NavigationActions.reset({
        index: 0,
        key: null,
        actions: [
            NavigationActions.navigate({ routeName: stackName }),
        ],
    });
    navigation.dispatch(resetAction);
    if (screenName) {
        navigation.navigate(screenName, params);
    }
};
@martin-braun
Copy link

@martin-braun martin-braun commented Sep 6, 2019

Has this been implemented into react-navigation, eventually, so I don't have to hack around the API to get this to work?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet