December 21, 2016
December 21, 2016
Anyone who has used an app which requires registration knows how frustrating it is for the user to enter their login credentials more than once. While it might sometimes be necessary for security purposes, we should strive to keep this nuisance to a safe minimum. Preferably, we would like the user to log into our app just once, and then quickly forget about this utter waste of time by allowing them to freely enjoy the app’s cool features. But what if the user closes the application, or simply reboots their phone? We definitely don’t want them to go through the horrors of login again!
This is where a persistent navigation state comes in handy. In this article, we will show a simple way to store a React Native app’s navigation state and bring back the user to the last page they visited, as well as present potential problems with such a setup.
Imagine we have a React Native application composed of several screens (let’s call the initial one a LoginScreen). How can we save the information about which screen was visited last? A natural solution for many React users would be to store the navigation stack in a state container such as Redux.
I will not go into much detail on this one, because of multiple online tutorials which have been dedicated to coupling Redux with either Navigator or NavigationExperimental modules from React Native.
If you follow any of these tutorials, you’ll probably wind up with a “navigation reducer”, whose initial state will be similar to this one:
export const INITIAL_STATE = {index: 0,key: 'root',routes: [{ key: 'LoginScreen' }],}
When transitioning to other parts of the app, the new screen routes will either be pushed on top of the stack, or replace the current route.
With a neat tool such as redux-persist, you can have the whole app state (or just selected reducers if you prefer) automatically saved into the AsyncStorage of the device on each update, by adding but a few lines of code.
import { persistStore, autoRehydrate } from 'redux-persist'const store = createStore(reducer, undefined, autoRehydrate())persistStore(store)
By using persistStore(store)
, we make sure that the Redux representation of the navigation state of our app will be saved after each transition. The autoRehydrate()
enhancer forces the app to “rehydrate” (a.k.a. reload) the persisted app state on startup. Combining these two results in showing the user the very same screen where they left the app, even if it was closed in the meantime.
Persisting the application state provides a great user experience… but only as long as everything is working correctly. However, remember that in release mode the app is killed as soon as an error is encountered. This, in turn, means that once the app crashes on some screen, it will be “rehydrated” on the next launch with an erroneous state, and the error will occur again. This way, the user will be trapped in a crash loop forever, unless they make a complete reinstall of our application!
This is one of the rare cases when it makes more sense to clean up everything and bring the user back to the login screen. Fortunately, React Native provides an easy way to trap errors globally. We just need to take the setGlobalHandler
function from the ErrorUtils
object and call it in your app’s main component.
A very simple error handler can look like this:
export default class Root extends React.Component {componentWillMount() {//Intercept react-native error handlingthis.defaultHandler = ErrorUtils.getGlobalHandler()ErrorUtils.setGlobalHandler(this.wrapGlobalHandler.bind(this))}async wrapGlobalHandler(error, isFatal) {// If the error kills our app in Release mode, make sure we don't rehydrate// with an invalid Redux state and cleanly go back to login page insteadif (isFatal && !__DEV__) AsyncStorage.clear()//Once finished, make sure react-native also gets the errorif (this.defaultHandler) this.defaultHandler(error, isFatal)}// (...)}
And voilà! If the app crashes in production mode, the whole app state will now be reset to the initial values on the next launch, bringing the user back to a (hopefully) safe place, such as the login screen.
Some things to keep in mind while designing your own error handler:
wrapGlobalHandler
in the example above).If you liked this post, why don't you subscribe for more content? If you're as old-school as we are, you can just grab the RSS feed of this blog. Or enroll to the course described below!
Alternatively, if audio's more your thing why don't you subscribe to our podcast! We're still figuring out what it's going to be, but already quite a few episodes are waiting for you to check them out.