
Persisting React State in LocalStorage - joshwcomeau
https://www.joshwcomeau.com/react/persisting-react-state-in-localstorage
======
geocar
I'd recommend just putting this crap in the URL. That way (a) the server could
see it too, and (b) users could share their state by sharing the URL (which
makes supporting them easier!). If you've got so much state this doesn't make
sense, then ¯\\_(ツ)_/¯ I suppose caching it in localStorage is fine, but I
haven't yet run into this problem.

Having a setSticky((k,v) => that modifies the url with the history API is
pretty easy, and if you get the results using the Context API, you can have
your root monitor the URL (again using the history API) and publish the result
using a context so you don't have a storm of updates.

~~~
k__
User specific state like "dark mode enabled" would probably be better inside
localStorage than inside a shareable URL.

~~~
geocar
Don't browsers have media queries for things like that?

I think some old browsers didn't, so I guess that's why there are all those
tacky switches on blogs these days, but I don't usually worry about obsolete
things like "made for netscape 4" banners...

I also think for an application, I'd much prefer my settings on the server so
I can get them again when I'm using another device.

~~~
ultrarunner
We have an application that's used outdoors day and night in some cases. The
good news is it makes a ton of difference in usability; the bad news is I
honestly still have to search for instructions on toggling dark mode on
Ubuntu. The switch is a lot simpler.

~~~
geocar
Hey go nuts. Whatever works for you.

But if you wanted to use other applications that didn't have such a switch,
you might consider looking into a hotkey or an extension. I found this[1]
after a quick ddg. Maybe it'll help?

[1]: [https://extensions.gnome.org/extension/2314/dark-mode-
switch...](https://extensions.gnome.org/extension/2314/dark-mode-switcher/)

------
dudus
I remember people were abandoning localstorage a while ago because of some
fundamental problem I can't quite remember right now. Not sure if it was
difficulty in keeping in sync which the author mentions or the fact that it
can't be scoped in a way to share among subdomains. I'm sure someone here will
have a better memory than me.

Edit: after some basic research among other complaints are: bad performance,
synchronous, bad security model, only strings, limited to 5mb, no access to
web workers.

Localstorage has its uses but shouldn't be abused to store everything in it.

~~~
Drdrdrq
Re web workers: is anyone (apart from Google) using this in practice? I assume
Photopea and similar apps could benefit from it in some specific cases, but
haven't had the need for them myself. Am I missing something?

~~~
stekern
I've experimented with using it together with TensorFlow.js to avoid blocking
the UI thread while performing computationally expensive operations.
Operations do take a bit longer to complete in a web worker compared to the
main thread, but in return you get a smooth UI and a better user experience

------
the_gipsy
LocalStorage is tied to a device. The kind of settings described in the
article should be tied to an account.

Component state should only contain volatile data that is not important to
save, the opposite of LocalStorage.

~~~
PNWChris
Edit:

I misread the parent comment, apologies for that! After re-reading, I still
agree with them, but with some nuance.

This article describes "somewhat" volatile state, and I think there are
interesting use-cases for temporary sticky state that stays between visits
(but doesn't need to be stored permanently). I see this being cool for search
filter states and stuff.

I'll leave my original comment as-is because I still thought that was worth
sharing, too.

\--

I've put a lot of thought into easy, no DB persistence for a project of mine,
and I have to agree with your assessment.

I ended up persisting to the private fragment identifier (hash) portion of the
URL. It makes bookmarking and copy-paste state sharing easier. Most major
browsers have a bookmark sync feature these days, so state sync is free (a
nice benefit!).

While researching persistence options, I came across storage.sync[0]. It's
like local storage, but synced across browsers. I've never used it, but the
promised functionality would be very cool.

I think storage sync is a really good idea. I just wish there was a "web
browser state file system" of sorts for users to browse, copy, back-up, etc.
In absence of that, I feel no user can be confident that local storage is
working, and as a consequence I won't persist anything of consequence there.

[0]: [https://developer.mozilla.org/en-US/docs/Mozilla/Add-
ons/Web...](https://developer.mozilla.org/en-US/docs/Mozilla/Add-
ons/WebExtensions/API/storage/sync)

~~~
pault
Chrome has the FileSystem API, but they couldn't get the other vendors on
board and it's basically dead now.

[https://stackoverflow.com/questions/23560090/is-the-
fileapi-...](https://stackoverflow.com/questions/23560090/is-the-fileapi-in-
html5-dead)

------
ristic
It's a lot more setup but I've had a good experience with redux, redux-persist
and localForage. localForage picks the best available browser storage API. One
can select which subset of data to persist with redux-persist. It does assume
a redux buy in...

------
kmf
this is great - i implemented something similar in a recent project, where we
have some state being persisted "at the edge" (in cloudflare workers' key-
value store product, workers kv) and cached in-browser using local storage. we
wrote a bit about it here[1], though our approach[2] has changed a bit even
since this post went out.

instead of directly interfacing with window.localStorage, we opted to use
lscache[3], which allows us to expire keys: `lscache.set(key, value, 5)`
(expires in 5 minutes)

[1]: [https://blog.cloudflare.com/jamstack-at-the-edge-how-we-
buil...](https://blog.cloudflare.com/jamstack-at-the-edge-how-we-built-built-
with-workers-on-workers/) [2]:
[https://github.com/cloudflare/workers.cloudflare.com/blob/ma...](https://github.com/cloudflare/workers.cloudflare.com/blob/master/src/components/bookmark_state.js)
[3]:
[https://github.com/pamelafox/lscache](https://github.com/pamelafox/lscache)

------
ketzo
Damn, as someone currently trying to move from React classes to React Hooks,
this does a pretty great job of showing the use case for Hooks. I have 10+
components across 5+ projects for which I'd use this hook.

------
benatkin
I don't think key should be in the last part of here, because key should never
change inside the closure:

    
    
        React.useEffect(() => {
          window.localStorage.setItem(key, JSON.stringify(value));
        }, [key, value]);
    

It's pretty neat because it has a simple and familiar API. I think it's more
like sticky props than sticky state, though...

Edit: here's another hook with a more accurate, less catchy name, plus error
handling:
[https://usehooks.com/useLocalStorage/](https://usehooks.com/useLocalStorage/)

~~~
ricardobeat
The ESLint rules for hooks enforce adding _any_ values used inside the
function to the dependency array.

------
emilecantin
The author briefly touches on a pet peeve of mine:

> Every single time, I have to swap the default currency from USD to CAD. Why
> can't it remember that I'm Canadian??

US-based devs and English-speaking devs often forget about the rest of the
world. Either it's bad or inexistent i18n, overly broad assumptions (e.g
name=`${firstName} ${lastName}`) or just straight-up bad default values that
can't be changed (like the example).

When you're developing a SaaS, more often than not you're doing it for a
global audience. Don't forget that.

~~~
jsgo
> overly broad assumptions (e.g name=`${firstName} ${lastName}`)

So an internal application I work on has communication between teams where
first + last isn't a given. So I'm constantly paranoid if I'm going to get it
right (usually just don't communicate name at all, just make statements).

Fast forward to a month ago when I started working on a rewrite and while
constructing the users (and actually making them users within a company
instead of effectively companies themselves), I added an enum for NameOrder
and separated the two fields.

Not particularly important to the application in the grand scheme of things,
but geez, it feels mildly freeing that it won't be so ambiguous in the future.

~~~
lucasmullens
Names are pretty complicated, not sure that fully covers it. Many people have
multiple last names, and how do you handle middle names?

I've generally heard it recommend to just have a "Full Name" text box, but
that wouldn't solve your use-case, so now I'm not sure.

------
ipsum2
Heads up to the author, the code example is wrong.

    
    
      function useStickyState(defaultValue, key)
    

should be

    
    
      function useStickyState(key, defaultValue)

~~~
XCSme
I was also very confused by this. Does anyone know why this article has so
many votes? This is a trivial solution and there are many other articles
explaining this exact thing.

~~~
dinkleberg
Because it brings attention to an interesting concept in a simple way. I'm
starting to use React again after a year away using Vue and I've only just
started to play with hooks. So it's cool to see other use cases.

And even if it is a trivial solution, it brings up an interesting idea. Why
this article and not the others? Because it was posted at the right time. I'm
sure if another one of those articles was posted here (and was written in an
equally approachable way) it would do just as well.

~~~
XCSme
I think my opinion is biased because I know React pretty well and have a lot
of JS experience so the article feels just like explaining syntax and does not
cover important edge cases (eg. what if you have localStorage disabled? your
hook will crash!).

LE: I actually tested the site with Cookies disabled and the entire site
crashed due to the localStorage calls.

------
longnguyen
For SSR supports, use cookie instead of localStorage. But I would put a damn
user preference flag in backend rather than solving it in the front-end.

------
ficklepickle
I'm astonished at the lack of feature detection. I though that was common
knowledge.

Using feature detection solves the SSR issue and as a bonus that code won't
error if localStorage doesn't otherwise exist (old browsers, tests).

The best method of feat. detection of localStorage, IMO, is writing and
reading back a test value in a try catch block. That will catch safari
incognito which lies about support.

------
ronyfadel
Wouldn't it make more sense to stick this view persisted state in your Redux
store? (and write it to LocalStorage with a throttle). Dan Abramov does it
that way in [0]

It would make handling your data much more sane (it all lives in one place,
your Redux store), making it easier to sync (just sync your store's state),
solve the key collision issues (with the author's implementation, separate
components can overwrite each other's data).

[0] [https://egghead.io/lessons/javascript-redux-persisting-
the-s...](https://egghead.io/lessons/javascript-redux-persisting-the-state-to-
the-local-storage)

~~~
jakelazaroff
Not all React applications use Redux.

------
Lord_Zero
I just did this with Vuex and vuex-persistedstate. It was a drop in plugin in
nuxt. Anytime a mutation happens state is written.

I'm new to react but hooks look interesting.

------
EGreg
React is a view layer tech

You should be persisting state in the Data layer on the client

The Data layer should be syncing with the servers or peer to peer once in a
while

~~~
ramenmeal
I don't think react claims to be "view" only anymore.

~~~
capableweb
AFAIK, React does claim to solve "the view problem" and not more than that.
You still need an architecture around your application if you're building
something larger than a demo.

Quote from the React landing page:

> Design simple views for each state in your application

Quite literally, one of the selling points of React is that it's focused on
the view based on data, not on where that data comes from.

------
legym
The Apollo React client does this for us

~~~
code_biologist
Care to elaborate?

