Просмотр исходного кода

Add listen parameter to usePersistedState hook (#5134)

GitOrigin-RevId: d40f942164403a31ffbb10336227eef59a57febf
Alf Eaton 5 лет назад
Родитель
Сommit
e76b7696da

+ 27 - 2
services/web/frontend/js/shared/hooks/use-persisted-state.js

@@ -1,7 +1,14 @@
-import { useState, useCallback } from 'react'
+import { useState, useCallback, useEffect } from 'react'
 import localStorage from '../../infrastructure/local-storage'
 import localStorage from '../../infrastructure/local-storage'
 
 
-function usePersistedState(key, defaultValue) {
+/**
+ * @param {string} key
+ * @param {any} [defaultValue]
+ * @param {boolean} [listen]
+ *
+ * @returns {[any, function]}
+ */
+function usePersistedState(key, defaultValue, listen = false) {
   const [value, setValue] = useState(() => {
   const [value, setValue] = useState(() => {
     return localStorage.getItem(key) ?? defaultValue
     return localStorage.getItem(key) ?? defaultValue
   })
   })
@@ -24,6 +31,24 @@ function usePersistedState(key, defaultValue) {
     [key, defaultValue]
     [key, defaultValue]
   )
   )
 
 
+  useEffect(() => {
+    if (listen) {
+      const listener = event => {
+        if (event.key === key) {
+          // note: this value is read via getItem rather than from event.newValue
+          // because getItem handles deserializing the JSON that's stored in localStorage.
+          setValue(localStorage.getItem(key))
+        }
+      }
+
+      window.addEventListener('storage', listener)
+
+      return () => {
+        window.removeEventListener('storage', listener)
+      }
+    }
+  }, [key, listen])
+
   return [value, updateFunction]
   return [value, updateFunction]
 }
 }
 
 

+ 42 - 0
services/web/test/frontend/shared/hooks/use-persisted-state.test.js

@@ -138,4 +138,46 @@ describe('usePersistedState', function () {
 
 
     expect(localStorage.getItem(key)).to.equal('foobar')
     expect(localStorage.getItem(key)).to.equal('foobar')
   })
   })
+
+  it('handles syncing values via storage event', async function () {
+    const key = 'test:sync'
+    localStorage.setItem(key, 'foo')
+    expect(window.Storage.prototype.setItem).to.have.callCount(1)
+
+    // listen for storage events
+    const storageEventListener = sinon.stub()
+    window.addEventListener('storage', storageEventListener)
+
+    const Test = () => {
+      const [value, setValue] = usePersistedState(key, 'bar', true)
+
+      useEffect(() => {
+        setValue('baz')
+      }, [setValue])
+
+      return <div>{value}</div>
+    }
+
+    render(<Test />)
+
+    screen.getByText('baz')
+
+    expect(window.Storage.prototype.getItem).to.have.callCount(1)
+    expect(window.Storage.prototype.removeItem).to.have.callCount(0)
+    expect(window.Storage.prototype.setItem).to.have.callCount(2)
+
+    expect(localStorage.getItem(key)).to.equal('baz')
+
+    expect(storageEventListener).to.have.callCount(0)
+
+    // set the new value in localStorage
+    localStorage.setItem(key, 'cat')
+
+    // dispatch a "storage" event and check that it's picked up by the hook
+    window.dispatchEvent(new StorageEvent('storage', { key }))
+
+    await screen.findByText('cat')
+
+    expect(storageEventListener).to.have.callCount(1)
+  })
 })
 })