<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Giovanni</title>
    <description>The latest articles on DEV Community by Giovanni (@giovannibenussi).</description>
    <link>https://dev.to/giovannibenussi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F50537%2F88e7dc2d-83fb-4892-807f-7085f7732834.jpg</url>
      <title>DEV Community: Giovanni</title>
      <link>https://dev.to/giovannibenussi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/giovannibenussi"/>
    <language>en</language>
    <item>
      <title>Persisting State on React Apps</title>
      <dc:creator>Giovanni</dc:creator>
      <pubDate>Fri, 25 Sep 2020 02:13:12 +0000</pubDate>
      <link>https://dev.to/giovannibenussi/persisting-state-on-react-apps-331c</link>
      <guid>https://dev.to/giovannibenussi/persisting-state-on-react-apps-331c</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;a href="https://medium.com/@gbenussi/persisting-state-on-react-apps-726c310f35ed?source=friends_link&amp;amp;sk=8702c5d71ff926bf12900a88bf1e415a"&gt;Published Originally on Medium&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Persist React's &lt;code&gt;useState&lt;/code&gt; to &lt;code&gt;localStorage&lt;/code&gt; is a common requirement. You'd want to persist user's preferences or data to have them at hand on next sessions. However, there are some bugs that are hard to track when doing this. This article will present them and explain how to solve them effectively.&lt;/p&gt;

&lt;h1&gt;
  
  
  Our Example
&lt;/h1&gt;

&lt;p&gt;Let's suppose that we add a new settings to allow users to enable dark mode in our website. Something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eimR3Ugt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jof314os1rosv9aulep9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eimR3Ugt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jof314os1rosv9aulep9.png" alt="Our new dark mode setting."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Internally, we'd keep an internal state using React's &lt;code&gt;useState&lt;/code&gt; to store the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;title: label to display in the UI&lt;/li&gt;
&lt;li&gt;name: to reference in the input field and to be able to retrieve our persisted state even if we update its title.&lt;/li&gt;
&lt;li&gt;enabled: specifies if the checkbox is checked or not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To store this state we'll use React's &lt;code&gt;useState&lt;/code&gt; hook for now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--urs1JFUG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3iyhe97vjnp6wju7m8fo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--urs1JFUG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3iyhe97vjnp6wju7m8fo.png" alt="Persist options using React's useState."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'll omit the layout details and logic used to enable/disable every option since is beyond the idea of this article.&lt;br&gt;
So here's our UI and it's associated state:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KKlXXiak--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qjv9rc729hklnrod45mp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KKlXXiak--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qjv9rc729hklnrod45mp.png" alt="Our UI and its associated state when dark mode is enabled"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is how it looks when dark mode is disabled:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--60IwAGqB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1xdhp3g8fncraypm3p4a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--60IwAGqB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1xdhp3g8fncraypm3p4a.png" alt="Our UI and its associated state when dark mode is disabled"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we have our data driven UI ready to be persisted, so we'll do that now.&lt;/p&gt;

&lt;h1&gt;
  
  
  Persisting State
&lt;/h1&gt;

&lt;p&gt;To persist our state, we'll use the &lt;a href="https://usehooks.com/useLocalStorage/"&gt;useLocalStorage&lt;/a&gt; hook:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_jz6UgT6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dfpc0ocl4av3pi34n8da.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_jz6UgT6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dfpc0ocl4av3pi34n8da.png" alt="Persist options using React's useLocalStorage."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that we need to specify &lt;code&gt;options&lt;/code&gt; as a first parameter. This is because &lt;a href="https://overreacted.io/why-do-hooks-rely-on-call-order/"&gt;React's hooks rely on call order&lt;/a&gt;, so there isn't a reliable way to persist state without a name. That's why we use &lt;code&gt;options&lt;/code&gt; as a name to reference our state. We need to be careful to not use this name in multiple places (unless we want to reuse the same state across our app, in which case a custom hook will be a better option to keep the state's shape in sync).&lt;/p&gt;

&lt;p&gt;The way &lt;code&gt;useLocalStorage&lt;/code&gt; works is as follows:&lt;br&gt;
If there isn't data on &lt;code&gt;localStorage&lt;/code&gt;, set state to initial state.&lt;br&gt;
If there is data on &lt;code&gt;localStorage&lt;/code&gt;, set state to stored state.&lt;/p&gt;

&lt;p&gt;Here's a visualization of our UI and its associated state and localStorage content:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uknDuvmB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1e0pcj8jnw3t8uawrvp3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uknDuvmB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1e0pcj8jnw3t8uawrvp3.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we have our data driven, persisted UI. We'll see what issues happen when we try to add new options to it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Stale State
&lt;/h1&gt;

&lt;p&gt;Let's add a new configuration to enable data savings mode:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--v9Um1rNd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/syj26qrvku2mupu2tfck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--v9Um1rNd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/syj26qrvku2mupu2tfck.png" alt="Our new data saving option."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Easy, we add just a new option to our new state:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_5f6K71w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gml93wgt7cjcqdtgqnxo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_5f6K71w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gml93wgt7cjcqdtgqnxo.png" alt="Adding a data saving option to our options state."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We save our changes but we see this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MRYayQ9t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/anofun2uyhbiwuin6658.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MRYayQ9t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/anofun2uyhbiwuin6658.png" alt="How our settings looks after our changes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We refresh the browser and restart the app but the UI doesn't get updated. However, if you open our app in a new incognito window, you'll see the new UI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vqJJQf0I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5ypxei99lqrei5p2rev5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vqJJQf0I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5ypxei99lqrei5p2rev5.png" alt="How our settings looks after our changes on an incognito tab"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What happened?&lt;br&gt;
The issue lies on the data that we have saved on localStorage:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aKEWBKKH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8rztgadbd901a07iab1v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aKEWBKKH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8rztgadbd901a07iab1v.png" alt="localStorage data persisted for our state."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As described before, the &lt;code&gt;useLocalStorage&lt;/code&gt; hook will load data from &lt;code&gt;localStorage&lt;/code&gt; if it's present, so it loads this data as our state:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uy7bpOLp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5ovikozxpolsq2r3g0ay.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uy7bpOLp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5ovikozxpolsq2r3g0ay.png" alt="App state when localStorage data was present before load the page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, on an incognito tab (or after delete &lt;code&gt;localStorage&lt;/code&gt; data), there's no data in &lt;code&gt;localStorage&lt;/code&gt; so the &lt;code&gt;options&lt;/code&gt; state will be the provided initial state:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PtKcMTQX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7ux14q7hjp6o32eiulgl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PtKcMTQX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7ux14q7hjp6o32eiulgl.png" alt="App state when localStorage data wasn't present before load the page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The easiest solution would be to just delete &lt;code&gt;localStorage&lt;/code&gt; data and continue. However, &lt;strong&gt;what happens with users that already have seen the settings page on production?&lt;/strong&gt; They'll have stale data and thus won't be able to see our new data saving setting.&lt;/p&gt;

&lt;h1&gt;
  
  
  Versioning
&lt;/h1&gt;

&lt;p&gt;One easy solution can be to update the name on localStorage for our state. For example, add some sort of versioning like option-v1&amp;nbsp;. When there's a change in the initial value, you can increment the version to option-v2&amp;nbsp;, option-v3&amp;nbsp;, and so on. The drawback is that we'll end up using unnecessary space for our users:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--e8kJBR5h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/wh1ivkznmzmgrybv07fl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--e8kJBR5h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/wh1ivkznmzmgrybv07fl.png" alt="localStorage after adding a few versions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Automatic Updates
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/giovannibenussi/use-persisted-state-hook"&gt;usePersistedState&lt;/a&gt; solves the versioning issue by keeping a unique identifier for the provided initial value:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Gmqx7kbf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5damwkqbdmuarb1pxtmx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Gmqx7kbf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5damwkqbdmuarb1pxtmx.png" alt="usePersistedState stores a unique hash to keep track of initial value changes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When we change our initial value the initial state is automatically loaded and previous data on &lt;code&gt;localStorage&lt;/code&gt; gets updated automatically ✨:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gC6nilzt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6go5f5365v1yj3iweyyn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gC6nilzt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6go5f5365v1yj3iweyyn.png" alt="usePersistedState's automatically updates previous data"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The way it works is as follows. If there isn't persisted data, then load state from initial state. However, if there's data, a unique hash is calculated for the initial state and is compared against the stored one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JRhpDAaf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6jqliucxq5qc2fegi2u9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JRhpDAaf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6jqliucxq5qc2fegi2u9.png" alt="usePersistedState's automatic hash comparison"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the hashes match, state will be loaded from &lt;code&gt;localStorage&lt;/code&gt;. If they don't match, it will not be considered and will be overridden by the new default state.&lt;/p&gt;

&lt;h1&gt;
  
  
  Server Side&amp;nbsp;Support
&lt;/h1&gt;

&lt;p&gt;If you need server side support when persisting state, keep in mind that data from &lt;code&gt;localStorage&lt;/code&gt; cannot be read from the server, so you need to delay the data loading until the component is mount on the client (running useEffect works for this). &lt;a href="https://github.com/giovannibenussi/use-persisted-state-hook"&gt;usePersistedState&lt;/a&gt; handles this automatically for you so you don't need to worry about it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Performance
&lt;/h1&gt;

&lt;p&gt;If you're worries about the performance of calculate a hash for the initial state, I did a small test and run the hash function 1,000 times and it took less than 230ms to run. That equals to 0.23ms for each run so it's not a big deal.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;In this article I introduce you about common issues when persisting state to &lt;code&gt;localStorage&lt;/code&gt;. We saw a simple way to automatically adapt to changes and avoid hard to find bugs at the same time.&lt;br&gt;
If you haven't done it yet, I encourage you to use &lt;a href="https://github.com/giovannibenussi/use-persisted-state-hook"&gt;usePersistedState&lt;/a&gt; for this purpose. I build it with ❤️ and hard work so you don't have to.&lt;br&gt;
You can find me on &lt;a href="https://twitter.com/giovannibenussi"&gt;Twitter&lt;/a&gt; if you have any questions.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
