- 
          
 - 
                Notifications
    
You must be signed in to change notification settings  - Fork 261
 
LB-1761 : Save Fresh Releases filters in browser storage #3281
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,119 @@ | ||||||||||
| import { useEffect } from "react"; | ||||||||||
| import localforage from "localforage"; | ||||||||||
| 
     | 
||||||||||
| const release_filters_cache = localforage.createInstance({ | ||||||||||
| name: "listenbrainz", | ||||||||||
| driver: [localforage.INDEXEDDB, localforage.LOCALSTORAGE], | ||||||||||
| storeName: "fresh-releases", | ||||||||||
| }); | ||||||||||
| const RELEASE_FILTERS_STORAGE_KEY = "release-filters"; | ||||||||||
| 
     | 
||||||||||
| interface StoredFilters { | ||||||||||
| checkedList: Array<string | undefined>; | ||||||||||
| releaseTagsCheckList: Array<string | undefined>; | ||||||||||
| releaseTagsExcludeCheckList: Array<string | undefined>; | ||||||||||
| includeVariousArtists: boolean; | ||||||||||
| coverartOnly: boolean; | ||||||||||
| } | ||||||||||
| 
     | 
||||||||||
| interface UseFilterPersistenceParams { | ||||||||||
| checkedList: Array<string | undefined>; | ||||||||||
| releaseTagsCheckList: Array<string | undefined>; | ||||||||||
| releaseTagsExcludeCheckList: Array<string | undefined>; | ||||||||||
| includeVariousArtists: boolean; | ||||||||||
| coverartOnly: boolean; | ||||||||||
| 
     | 
||||||||||
| setCheckedList: (list: Array<string | undefined>) => void; | ||||||||||
| 
         There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like a cumbersome way to go about saving the filters state, and makes it harder to extend in the future. I think the hook should instead expose a  What this means is moving the state management of these filter options inside this hook component instead of in the ReleaseFilters component instead of passing the values and their respective setters to the hook. In ReleaseFilters you can use the values exposed by the hook, something like : The other approach is to create a hook that only persists one value, and use that hook for each filter value instead of useState. And then in ReleaseFilters: Which might be easier to use, but does mean as many localForage calls as we have options, when we load the component. I would say test it out and see if there is an impact on performance at all?  | 
||||||||||
| setReleaseTagsCheckList: (list: Array<string | undefined>) => void; | ||||||||||
| setReleaseTagsExcludeCheckList: (list: Array<string | undefined>) => void; | ||||||||||
| setIncludeVariousArtists: (value: boolean) => void; | ||||||||||
| setCoverartOnly: (value: boolean) => void; | ||||||||||
| } | ||||||||||
| 
     | 
||||||||||
| export default function useFilterPersistence( | ||||||||||
| 
         There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The name here is a bit too generic, you can imagine there are other places in the codebase where we might have filters.  | 
||||||||||
| params: UseFilterPersistenceParams | ||||||||||
| ) { | ||||||||||
| const { | ||||||||||
| checkedList, | ||||||||||
| releaseTagsCheckList, | ||||||||||
| releaseTagsExcludeCheckList, | ||||||||||
| includeVariousArtists, | ||||||||||
| coverartOnly, | ||||||||||
| setCheckedList, | ||||||||||
| setReleaseTagsCheckList, | ||||||||||
| setReleaseTagsExcludeCheckList, | ||||||||||
| setIncludeVariousArtists, | ||||||||||
| setCoverartOnly, | ||||||||||
| } = params; | ||||||||||
| 
     | 
||||||||||
| // Load filters on component mount | ||||||||||
| useEffect(() => { | ||||||||||
| const loadFilters = async () => { | ||||||||||
| try { | ||||||||||
| const savedFilters = await release_filters_cache.getItem<StoredFilters>( | ||||||||||
| RELEASE_FILTERS_STORAGE_KEY | ||||||||||
| ); | ||||||||||
| if (savedFilters) { | ||||||||||
| setCheckedList(savedFilters.checkedList ?? []); | ||||||||||
| setReleaseTagsCheckList(savedFilters.releaseTagsCheckList ?? []); | ||||||||||
| setReleaseTagsExcludeCheckList( | ||||||||||
| savedFilters.releaseTagsExcludeCheckList ?? [] | ||||||||||
| ); | ||||||||||
| setIncludeVariousArtists(savedFilters.includeVariousArtists ?? false); | ||||||||||
| setCoverartOnly(savedFilters.coverartOnly ?? false); | ||||||||||
| } | ||||||||||
| } catch (error) { | ||||||||||
| console.error("Failed to load filters:", error); | ||||||||||
| } | ||||||||||
| }; | ||||||||||
| loadFilters(); | ||||||||||
| }, []); | ||||||||||
| 
     | 
||||||||||
| // Save filters when they change | ||||||||||
| useEffect(() => { | ||||||||||
| const saveFilters = async () => { | ||||||||||
| try { | ||||||||||
| const filtersToSave: StoredFilters = { | ||||||||||
| checkedList: checkedList.filter( | ||||||||||
| (item): item is string => item !== undefined | ||||||||||
| ), | ||||||||||
| 
         
      Comment on lines
    
      +77
     to 
      +79
    
   
  There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This syntax can be made a lot shorter like so to filter out falsy values (will also filter out empty strings but that's good in this case): 
        Suggested change
       
    
  | 
||||||||||
| releaseTagsCheckList: releaseTagsCheckList.filter( | ||||||||||
| (item): item is string => item !== undefined | ||||||||||
| ), | ||||||||||
| releaseTagsExcludeCheckList: releaseTagsExcludeCheckList.filter( | ||||||||||
| (item): item is string => item !== undefined | ||||||||||
| ), | ||||||||||
| includeVariousArtists, | ||||||||||
| coverartOnly, | ||||||||||
| }; | ||||||||||
| await release_filters_cache.setItem( | ||||||||||
| RELEASE_FILTERS_STORAGE_KEY, | ||||||||||
| filtersToSave | ||||||||||
| ); | ||||||||||
| } catch (error) { | ||||||||||
| console.error("Failed to save filters:", error); | ||||||||||
| } | ||||||||||
| }; | ||||||||||
| saveFilters(); | ||||||||||
| }, [ | ||||||||||
| checkedList, | ||||||||||
| releaseTagsCheckList, | ||||||||||
| releaseTagsExcludeCheckList, | ||||||||||
| includeVariousArtists, | ||||||||||
| coverartOnly, | ||||||||||
| ]); | ||||||||||
| 
     | 
||||||||||
| const clearSavedFilters = async () => { | ||||||||||
| try { | ||||||||||
| await release_filters_cache.removeItem(RELEASE_FILTERS_STORAGE_KEY); | ||||||||||
| setCheckedList([]); | ||||||||||
| setReleaseTagsCheckList([]); | ||||||||||
| setReleaseTagsExcludeCheckList([]); | ||||||||||
| setIncludeVariousArtists(false); | ||||||||||
| setCoverartOnly(false); | ||||||||||
| } catch (error) { | ||||||||||
| console.error("Failed to clear filters:", error); | ||||||||||
| } | ||||||||||
| }; | ||||||||||
| return { clearSavedFilters }; | ||||||||||
| } | ||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is the core of your issue with the filters resetting when you change between "for you" and general releases.
I think the ideal solution is to save the settings separately (more flexibility for users), using this key as a prefix, and adding the
pageTypeas a suffix, something like:Which will result in the keys "release-filters-user" and "release-filters-sitewide" to store the filters separately.