Opt-out of cross-tab syncing?
See original GitHub issueThis library is fantastic, thank you!
I’ve come across a use-case where I need to opt-out of the cross-tab syncing, would it be possible to get a config option for this?
Here’s the part I’m talking about:
My use-case
I’m using use-local-storage-state
to track recent URL paths visited.
For example, I’ll push each path onto an array such as: ['/about', '/blog', '/contact']
, etc.
I then want to update the “Recent Path” on page load, so I do it in a useEffect
:
import { useEffect } from 'react'
import { createLocalStorageStateHook } from 'use-local-storage-state'
const useLocalStorage = createLocalStorageStateHook('recent-paths', [])
// `path` is derived from the current URL
export const useRecentPathsTracker = (path) => {
const [recentPaths, setRecentPaths] = useLocalStorage()
useEffect(() => {
// Make sure `path` is de-duplicated and prefixed to the start of the array
setRecentPaths([
path,
...(recentPaths || []).filter(recentPath => recentPath !== path),
])
}, [path, recentPaths, setRecentPaths])
}
This works fine in a single tab, but when I open 2 tabs that point to different paths (for example; example.com/blog
& example.com/about
), the following happens:
- Open
example.com/blog
in Tab 1 - The
useEffect
callssetRecentPaths(['/blog'])
- Open
example.com/about
in Tab 2 - The
useEffect
setssetRecentPaths(['/about', '/blog'])
- Calling
setRecentPaths
in Tab 2 updates the window’s storage item, which triggers an event in Tab 1 - Tab 1’s
useEffect
sees a new value forrecentPaths
, so runs again and callssetRecentPaths(['/blog', '/about'])
- Note the blog & about have swapped
- Calling
setRecentPaths
in Tab 1 updates the window’s storage item, which triggers an event in Tab 2 - Tab 2’s
useEffect
sees a new value forrecentPaths
, so runs again and callssetRecentPaths(['/about', '/blog'])
- Note the blog & about have swapped back again
- And so on…
I don’t actually need the cross-tab syncing for this usage, so would be happy with something like a { sync: false }
option:
const useLocalStorage = createLocalStorageStateHook('recent-paths', [], { sync: false })
The workaround for now is to use the functional form of the setRecentPaths
call:
export const useRecentPathsTracker = (path) => {
const [, setRecentPaths] = useLocalStorage()
useEffect(() => {
// NOTE: We use the functional version of state setting here to avoid an
// infinite loop when two tabs are open with different values for
// `path`. This is a problem because `use-local-storage-state` will sync the
// value across tabs by subscribing to the `session` window event. If we
// were to add `recentPaths` to the dependency list of `useEffect`, it would
// trigger a re-evaluation of the effect when either tab altered value and
// so the tabs would fight about setting their own path to be the first in
// the list.
// But by using the functional style, it's not a dependency, and our effect
// is only executed when this page's `path` changes, which is what we expect
setRecentPaths(recentPaths => {
return [
path,
...(recentPaths || []).filter(recentPath => recentPath !== path),
]
})
}, [path, setRecentPaths]) // Only run once per page load
}
The end result is the setRecentPaths
is only called once per mount, which is what I expected initially.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:1
- Comments:10 (6 by maintainers)
Top GitHub Comments
Yep, you are right. Sorry. I might have asked too many irrelevant questions. Your use case makes sense. I will think about it more. Thanks!
Isn’t it true that if you use
useRecentPathsTracker()
at two places in your code the issue will appear without needing two tabs? This makes me think that the problem is not the syncing but that theuseEffect()
updates the value every time it changes. UsingsetRecentPaths(recentPaths => {})
seems like a logical solution. Another way of fixing this problem is by making theuseEffect()
run only on initial load by providing[]
as a second parameter.What I want to say is that it doesn’t seem to be a problem with the syncing in particular but with the way you update the values.
Also, correct me if I am wrong but the first version of your code will create an infinite loop. The reason why it isn’t blocking is that it uses
useEffect()
. If you change it touseLayoutEffect()
it will hang your app.