expo / ex-navigation

Route-centric navigation for React Native
997 stars 208 forks source link

Navigation state is cleared on initialize #152

Open rogchap opened 7 years ago

rogchap commented 7 years ago

This is related to closed issue #27

When Ex-Navigation initializes it ignores any previous state that may have been persisted and then renders default routes.

image

Using redux-persist to save state to AysncStorage, which re-hydrates correctly and then the navigation state gets "wiped".

chirag04 commented 7 years ago

can you share your setup/code?

rogchap commented 7 years ago

Sure... a bit complex, but here you go:

// store/configure.js

import { applyMiddleware, createStore } from 'redux';
import { createNavigationEnabledStore } from '@exponent/ex-navigation';
import { persistStore, autoRehydrate } from 'redux-persist';

const createStoreWithNavigation = createNavigationEnabledStore({
  createStore,
  navigationStateKey: 'navigation',
});

const createMyStore = applyMiddleware(thunk, logger)(createStoreWithNavigation);

export default (onComplete: ?() => void) => {
  let store;
  if (isDebuggingInChrome) {
    store = autoRehydrate()(createMyStore)(reducers, devTools());
    if (module.hot) {
      // Enable hot module replacement for reducers
      module.hot.accept(() => {
        const nextRootReducer = require('../reducers').default;
        store.replaceReducer(nextRootReducer);
      });
    }
  } else {
    store = autoRehydrate()(createMyStore)(reducers);
  }

  persistStore(store, { storage: AsyncStorage }, onComplete);
  if (isDebuggingInChrome) {
    window.store = store;
  }
  return store;
};
rogchap commented 7 years ago

Ohhh... and this is the other bit:

// setup.js

constructor() {
  super();
  this.state = {
    isLoading: true,
    store: configureStore(() => this.setState({ isLoading: false })),
  };
}

render() {
  const { isLoading, store } = this.state;
  if (isLoading) {
    // TODO: return Loading App Screen
    return null;
  }

  const navigationContext = new NavigationContext({
    router,
    store,
  });

  return (
    <ReduxProvider store={store}>
      <NavigationProvider context={navigationContext}>
        <App />
      </NavigationProvider>
    </ReduxProvider>
  )
}
rogchap commented 7 years ago

Another interesting issue is that redux-persist gives this warning:

 redux-persist: cannot process cyclical state.
 Consider changing your state structure to have no cycles.
 Alternatively blacklist the corresponding reducer key.
 Cycle encounted at key "subscriber" with value "[object Object]".

Which makes sense.

deldreth commented 7 years ago

I encountered a similar problem. I too am using redux-persist. I've put together a test project that shows the behavior. Immediately post persist/REHYDRATE the initially rendered screen will go blank. That project is here: https://github.com/deldreth/ex-navigation-issue-152

This project uses a rehydration method similar to one of my production apps that I was testing ex-navigation against (specifically that the store is versioned). I've simplified that here to only use the version 1. If you bump the version number up you can see that when the store is purged the issue does not present itself.

Also, it may be worth noting that on the production app I was testing ex-navigation against I get the following warning coming from ExNavigationComponents:250

Object {type: "EX_NAVIGATION.INITIALIZE"}
Object {type: "EX_NAVIGATION.SET_CURRENT_NAVIGATOR", navigatorUID: "8e3fc402-8d0a-42d5-b7e4-70a62d4eb925", parentNavigatorUID: undefined, navigatorType: "stack", defaultRouteConfig: Object…}
Object {type: "persist/REHYDRATE", payload: Object}
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the FocusableComponent component.
reactConsoleError       @   ExceptionsManager.js:78
console.error           @   YellowBox.js:61
printWarning            @   warning.js:36
warning                 @   warning.js:60
getInternalInstanceReadyForUpdate   @   ReactUpdateQueue.js:49
enqueueSetState         @   ReactUpdateQueue.js:201
ReactComponent.setState @   ReactComponent.js:64
(anonymous function)    @   ExNavigationComponents.js:250
dispatch                @   createStore.js:186
(anonymous function)    @   Store.js:39
(anonymous function)    @   middleware.js:52
(anonymous function)    @   ExNavigationMiddleware.js:71
(anonymous function)    @   ExNavigationMiddleware.js:71
(anonymous function)    @   persistStore.js:44
complete                @   getStoredState.js:81
(anonymous function)    @   getStoredState.js:60
(anonymous function)    @   AsyncStorage.js:87
__invokeCallback        @   MessageQueue.js:268
(anonymous function)    @   MessageQueue.js:130
guard                   @   MessageQueue.js:42
invokeCallbackAndReturnFlushedQueue @   MessageQueue.js:129
onmessage               @   debuggerWorker.js:39

Object {type: "APP_LOADED"}type: "APP_LOADED"__proto__: Object
chirag04 commented 7 years ago

cc @skevy

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the FocusableComponent component.

Also, the warning reported above. This is basically FocusableComponent setting the state while unmounting. This is exactly what we discussed in the past.

gfontenot commented 7 years ago

Any movement on this? The most obvious culprit here is the call to INITIALIZE, which completely blows away the existing state (side note: this seems unnecessary. You should be able to get the same result by passing INITIAL_STATE to reduce instead of null, right?). Unfortunately, it also seems like something with the initialRoute (and initialTab for TabNavigations) aren't playing nicely with the existing state. I also have a hunch that something with SET_CURRENT_NAVIGATOR is also causing some existing state to be lost, although I haven't looked into it too much.

matteogiuliano commented 7 years ago

Hi, I managed to get ex-navigation use a persisted navigation history, but it's a rather brutal method.

As I said before this isn't probably the best method to restore the navigation history, but it works. Please let me know if this approach can introduce some problems related to ex-navigation.

digitalmaster commented 7 years ago

@matteogiuliano Wow.. crafty! 👍

really hope this is addressed (or at least easier) in Ex Nav 2

joeferraro commented 7 years ago

@matteogiuliano cheers for the sample code. This works fine for me when using a single StackNavigator, but once I introduce nested StackNavigators in TabNavigation, it blows up:

glass_and_iphone_7_ _ios_10_2__14c89_
matteogiuliano commented 7 years ago

@joeferraro I didn't test that code with multiple navigators, as in my application I use just one stack navigator. I think the problem is that you have no clue on which navigator is being initialized. You should find a field in the navigator initialization action that lets you understand which navigator that initialization is for. To do this I suggest you to start logging the actions captured by actionsInterceptor. The error you are having lets me also suppose that the line of code return Router.getRoute(value.routeName,value.params); returned null and you assigned it as your tabItem, so I suggest you to log also action.routes. If this is the case, you should avoid to rehidrate your navigation history for one run of the application, so that it returns to be consistent with your current routing configuration.

I'm sorry I can't help you more, but in fact I no longer use that code, as I changed my navigator to ReactNavigation.

joeferraro commented 7 years ago

Are you able to rehydrate without issue using react-navigation?

matteogiuliano commented 7 years ago

Still not tried. Referring to the docs, it seems to have a better integration with redux state, so i'm very confident about it.

jordanmkoncz commented 7 years ago

@rogchap Thanks for posting your ex-navigation / redux-persist set up, I was able to get the two working together nicely (but with my navigation state blacklisted in redux-persist).