Managing Ionic App State With Redux

When, when not to, and how to use it.

Lee Nathan
One Tap Software
8 min readJul 31, 2018

--

Why Use Redux?

Redux has gained popularity as a way to manage navigation in React apps. Ionic doesn’t need a navigation tool as it has a pretty good one though. So why would an Ionic app need Redux?

Redux is great at managing navigation because navigation is just state. When you open an app, it’s in an Index page state. When you navigate to the Contact Us page, the app enters the Contact Us state.

Any variable or data that you want to be accessible app-wide, like authentication/authorization data can and should be managed with an app state. This gets perilously close to global variables which are a bad thing. Redux avoids the trap of global variables by using a pub-sub like system for communicating with the state store. All state variables are managed by the state store. And the object that represents state is disposed of and a new one is returned on every state change. The state variables themselves are not directly accessed. State is instead listened to or accessed with a function. This keeps state loosely coupled from the rest of the app.

It gets even better. Not only does Redux keep itself loosely coupled to the app, it can be used to keep the various providers loosely coupled to each other. By storing data that needs to be shared in a central state, you can avoid problems like breaking the app every time you change a provider, and circular dependencies.

Circular dependencies happen when one provider injects another that injects another that injects the first one in the chain. For example, let’s say the CurrentUserProvider accesses the MessagesProvider to see the current user’s new messages. The MessagesProvider needs to know if the current user can even see the messages so it accesses the AuthorizationProvider. The AuthorizationProvider needs to know who the current user is to make sure they have access, so it calls CurrentUserProvider. That’s about the time the app crashes.

All that can be avoided by saving current user and auth data in the app state. If needed, you can even save messages in the app state.

Another benefit of saving app state is that it allows the app to be serialized, or frozen in place. You can take the variables of the current state and save them to local data. This way, if the app is completely unloaded by the OS, the providers can check for app state on startup and load the data needed to put the app back in the same state the user left it in.

Getting Started

Redux functionality goes very very deep. But implementing it and using it is very simple. I’ve been using it for almost a year and still haven’t looked at the “advanced” documentation and “recipes”. The basic use is so powerful I haven’t needed more. That may just mean I’m doing it wrong though. Not sure.

Install in the usual way.

Then generate an app-state provider.

This is a bare minimum app state configuration with a couple practical state variables in place for reference.

I’m going to do my best to break this down, but I won’t do nearly as good a job as the official Redux documentation. That’s partly because Redux is built with the functional programming philosophy and I really don’t speak functional programming. So if you’ll allow me, I’ll now proceed to completely butcher an explanation of this code.

First off, the official documentation uses the old school constant style
const ADD_TODO = ‘ADD_TODO’
I don’t have a CS degree, so I feel it’s unnecessarily verbose and it looks like yelling. I like my providers to talk to each other without all that yelling and I just use lowercase strings like 'add_todo'.

The store is where the state is stored. It can be listened to for changes and queried with getState(). developerMode and appBooting are variables in the state. Every time an action is dispatched to the state store, every function in the rootReducer gets called. Each function then chooses whether or not it wants to change the variable it manages based on the action.

The appBooting function starts with a default state value of false. (State variables can be strings, numbers, objects, or anything.) If appBooting receives an action type of 'platform_ready' it will change its state value to true. The state will hold that value on each subsequent call until the function changes it again. You can see that that’s not possible with either of the functions in this example, but the functions can return whatever they want. Using case is generally the best way to decide what to return, but it’s not necessary.

So how do we actually access and change the app state? I’ll show that in the next section.

Practical Applications

These are some examples of code that I use in my own apps.

App Booting

You saw the appBooting function in the last section. That’s a variable that lets the whole app know when the Ionic platform is ready without having to access Ionic’s platform in each provider. The app.component sets it like so.

As you can see, the appBooting variable isn’t set directly. This means that if there’s no function that listens for 'platform_ready' then things will fail silently without breaking your whole app. That’s the beauty of loosely coupled code. It also means that multiple state variables can be set with a single action. And your providers don’t even need to know which variables are being affected, keeping your app’s logic much simpler.

Now let’s say we want an AuthenticationProvider to check whether a user has signed in as soon as the platform is ready. That would look something like this.

But it wouldn’t look exactly like that. What’s wrong with this code? First off, it will check for authentication each and every time the state changes. So the provider needs to maintain its own state as well. Second, I checked for state with this.state.appBooting. I use a convenience method to make referencing state a little bit simpler and to keep my views cleaner. Here’s the corrected code.

I have a strong feeling that there’s a better way to do things. Maintaining local state like that can create a lot of heavy logic. And the state getter has to be copied into every provider and controller that needs to access state. But I haven’t come up with an alternative and this has worked for me. If anyone knows a better way, I’d love to hear it!

That’s how you update the state; by using dispatch. Every object that’s dispatched to state must have a type key that references the action’s name. And that’s how you access state both through an observable, and directly with getState(). Moving on to other examples

Developer Mode

I use this to bypass Cordova specific code and show developer only views. It goes in app.component as well.

This completes that function from the getting started section.

Loading

Here’s a known working example from an app I’m currently building. I maintain whether the app is loading as part of the state so I can show a loader anywhere at any time while only importing Ionic’s loading functionality in one place — the app.component. This example shows a random message every time the loading box is shown. My app-state function looks like this:

I think the only part that may need breaking down is the loading object. I track the current time with since so the app can snap out of it if it’s been loading too long. The message is the text that’s shown on the pop-up. I have the option of sending a custom message to loader. Any data can be attached to the action! Only the value key is necessary. Here’s what a custom call to loading would look like.

I think the actions themselves should be self explanatory and give you a bit more of an idea about why switch/case works well for state functions. This function turns on loading for 3 different actions and turns it off for 3 other actions. Notice that this function listens to ‘platform_ready’ as well.

In my app.component I have this.

This uses Ionic’s LoadingController. Nothing too surprising here either. But now the whole app can show and hide the loading pop-up with just this:

I also use state for maintaining auth and saving current user data.

Distinguishing Between State and Action

As a final note, I want to illustrate an important point about state. Because it’s used to communicate between app providers and it’s such a powerful tool it’s easy to get carried away and use it for all in-app communication. For example, if the app has a search feature it may be a good idea to save the search string and search results in the app state. If an unrelated provider needs to know that the user hit the search button though, it can be tempting to add a “searching” variable to the state. This can get more and more complicated as you add more logic to the state subscriber. I consider something the user does to be an action, not an aspect of the app’s state. Actions are better communicated between providers and controllers with a pub sub. I’ll explain that in my next post if I remember or ever get around to it.

I think this was the first post I didn’t use a bad word in. Well, shit!

--

--

Lee Nathan
One Tap Software

Freelance Writer for Hire and Personal Development Advocate