Patterns: Keeping app state in the URL

2018-9-3 16:21:16

Lately I've been experimenting with putting all of the app state in the URL as query paramaters. I want to sketch out my current thinking about the pattern.

A screenshot of the set-counter app with nine counters set up. Matching the URL query parameters.

In my set-counter app, all of the configuration is contained within the URL: https://grantcuster.github.io/set-counter?c[0][to]=10&c[1][sc]=2&c[1][to]=10&c[2][sc]=3&c[2][to]=10&c[3][sc]=4&c[3][to]=10&c[4][sc]=5&c[4][to]=10&c[5][sc]=4&c[5][to]=10&c[6][sc]=3&c[6][to]=10&c[7][sc]=2&c[7][to]=10&c[8][to]=10&sp=s&cv=1&cr=1&sv=0.5&sr=1.5&rl=y

Origins

I started doing this alot with the CFFL Prototypes. A lot of our prototypes involve exploring data and models looking for interesting results. I wanted those results to be easily shareable between people (this also helps with the debugging process). The easiest way make them shareable was to include the relevant state data (location, mode, view, etc.) in the URL.

I was primed to put the state in the URL because I'd been using Redux for state management. Redux guides you to have one source of truth for the state with the UI as a pure function of that state. It also encourages you to have a relatively flat state, which ends up being helpful for stuffing it into URL query parameters. Generally I'd develop an app using Redux then, as part of the end crunch, move some of the state into the URL. I was happy with the end results, but I always wanted to make the process more deliberate.

Experiments

In the past few weeks I've been exploring the pattern with some small example apps. I made a minimal app showing the pattern called USL. It's still very much a proof-of-concept but I tried to clean up the set-up a bit. I standardize parsing and stringifying of the parameters using the qs package. I set a default state for when there are no parameters, and use that state to determine if the provided parameters are valid or not. I also have a localStorage based save feature.

There are some promising results that flow from these choices. As I mentioned, because all state is contained in the URL configurations are easily shareable. This could be especially useful for generative pieces (ones you might generally use dat.gui for). Since I'm using the history pushState (by way of React-Router) you also get a history of your changes for free through the browser (so you can use the native back and forward buttons to navigate through it).

I've built a couple of apps on top of the pattern. One is the Wing Tsun set counter, which counts sets out loud. Breathe is a simple breath-counting app where you can set the in- and out-breath duration using the query params.

Inspiration

Besides Redux, there's a swirl of influences behind this pattern for me. A fun recent one is itty.bitty.site which keeps the entire site in a URL fragment. Possibly I'll follow their compression example at some point in my explorations, though there's something to be said for keeping things readable. Another cool one is Tracery Live by Gregory Avery-Wair and based on GalaxyKate's Tracery text generator. You can use it to create Tracery grammars and share them through the URL. This is pretty exactly the kind of generative project I was thinking this pattern would be useful for.

Another inspiration that I only realized as I was experimenting is the Zeit Now deployment set-up. With Now, each deployment is its own new URL. This means you can check it out and then use the alias command to set the public address to your new deployment.

Issues and plans

Mostly I've been happy with the example apps performance and feel. There is an issue in the set-counter I'd like to figure out how to address, however. I've got a few configurations I regularly return to, things I think of as "Full warm-up" and "Half warm-up". Currently I just have a text file with the links to those exact configurations, which mostly works. I do sometimes make small tweaks to the configurations, however, and I don't always remember to save the updated URL to my text file.

I've been thinking about if this is a case for zomething similar to Zeit Now's alias command. You would have the URLs containing the entire state, but then you could also alias those to certain more memorable URLs. I guess that's effectively a URL shortener. I think it's worth trying but I'd like to make it as lightweight as possible (like avoid a database and user accounts) and I'm not certain if there's a good way to do that without running into name collisions. I could use something like a JSON file served through Github pages. It would be very doable using browser local storage so I should start there. But for things like the set-counter one of the big features for me is being able to use it on my laptop or phone, local storage only works for one device at a time. I could do some sort of mix of local storage and a file you could download and send yourself?

Besides doing some more work on the set-counter my next plan is to use this pattern for my picture hanging arrangement generator. The information about picture size, order and wall config will all be in the URL. As you find arrangements you like they'll be a mechanism to save or fav those, and that will probably save them using local storage (maybe combined with a text file download option). I think that project's a great use case for this pattern and will help me find more issues and hopefully solutions.