Open rogchap opened 7 years ago
can you share your setup/code?
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;
};
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>
)
}
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.
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
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.
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 TabNavigation
s) 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.
Hi, I managed to get ex-navigation use a persisted navigation history, but it's a rather brutal method.
First of all, I waited for the persistStore
function to complete before creating and using a navigationContext
.
export default class AppComponent extends Component{
navigationContext = null;
constructor(props) {
super(props);
this.state={restored: false};
// The navigation context has to be created only after the state has been restored.
persistStore(store, {storage: AsyncStorage}, () => {
navigationContext = new NavigationContext({
router: Router,
store: store,
});
this.setState({restored: true});
})
}
render() {
if(!this.state.restored) {
return (<View>
<Text>Restoring state...</Text>
</View>);
}
return (
<Provider store={store}>
<NavigationProvider context={navigationContext}>
<StackNavigation initialRoute={Router.getRoute('login')} />
</NavigationProvider>
</Provider>
);
}
}
Second, I made an "interceptor" middleware that captures the persist/REHYDRATE
action to get a copy of the past routing history and the EX_NAVIGATION.SET_CURRENT_NAVIGATOR
to restore it manually by editing the EX_NAVIGATION.SET_CURRENT_NAVIGATOR
action itself:
/**
* Actions interceptor middleware looks for persist and exNavigator configuration actions to restore
* routes cleared by exnavigation initialization action.
*/
var savedRoutes = [];
const actionsInterceptor = store => next => action => {
if (action.type === "persist/REHYDRATE") {
if(!action.payload.navigation || !action.payload.navigation.navigators[action.payload.navigation.currentNavigatorUID]) return next(action);
savedRoutes = action.payload.navigation.navigators[action.payload.navigation.currentNavigatorUID].routes;
}
if (action.type === "EX_NAVIGATION.SET_CURRENT_NAVIGATOR" && savedRoutes.length > 0) {
action.routes = savedRoutes.map((value, index, array) => {
return Router.getRoute(value.routeName,value.params);
});
action.index = action.routes.length - 1;
}
return next(action);
}
Just remember to add actionsInterceptor
to the middleware list used by your applyMiddleware
function.
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.
@matteogiuliano Wow.. crafty! 👍
really hope this is addressed (or at least easier) in Ex Nav 2
@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:
@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.
Are you able to rehydrate without issue using react-navigation?
Still not tried. Referring to the docs, it seems to have a better integration with redux state, so i'm very confident about it.
@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).
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.
Using
redux-persist
to save state to AysncStorage, which re-hydrates correctly and then the navigation state gets "wiped".