Skip to content

Commit 809b889

Browse files
feat(settings): persist organization filter to localStorage
- Add organizationFilter field to settingsSlice with action and selector - Update AddRepositoryCombobox to use Redux state instead of local useState - Add withHydration wrapper to store for proper localStorage restoration The withHydration wrapper is required because Redux middleware cannot directly modify state - it can only dispatch actions. The wrapper intercepts HYDRATE_COMPLETE action and replaces state with localStorage data. Fixes: Organization filter resetting to 'all' on page navigation
1 parent 968a095 commit 809b889

File tree

3 files changed

+35
-13
lines changed

3 files changed

+35
-13
lines changed

components/Board/AddRepositoryCombobox.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ import {
1919
type GitHubOrganization,
2020
} from '@/lib/actions/github'
2121
import { addRepositoriesToBoard } from '@/lib/actions/repo-cards'
22+
import {
23+
selectOrganizationFilter,
24+
setOrganizationFilter,
25+
} from '@/lib/redux/slices/settingsSlice'
26+
import { useAppDispatch, useAppSelector } from '@/lib/redux/store'
2227

2328
interface AddRepositoryComboboxProps {
2429
boardId: string
@@ -53,8 +58,9 @@ export const AddRepositoryCombobox = memo(function AddRepositoryCombobox({
5358
const [isAdding, setIsAdding] = useState(false)
5459
const [addError, setAddError] = useState<string | null>(null)
5560

56-
// Filters
57-
const [organizationFilter, setOrganizationFilter] = useState<string>('all')
61+
// Filters (organizationFilter persisted to localStorage via Redux)
62+
const dispatch = useAppDispatch()
63+
const organizationFilter = useAppSelector(selectOrganizationFilter)
5864
// TODO: Implement topics filter UI
5965
// const [topicsFilter, setTopicsFilter] = useState<string[]>([])
6066
const [visibilityFilter, setVisibilityFilter] = useState<
@@ -80,15 +86,15 @@ export const AddRepositoryCombobox = memo(function AddRepositoryCombobox({
8086

8187
/**
8288
* Guarded organization filter change handler
83-
* Prevents potential infinite re-render by checking if value actually changed
89+
* Dispatches Redux action to persist organization filter to localStorage
8490
*/
8591
const handleOrganizationFilterChange = useCallback(
8692
(value: string) => {
8793
if (value !== organizationFilter) {
88-
setOrganizationFilter(value)
94+
dispatch(setOrganizationFilter(value))
8995
}
9096
},
91-
[organizationFilter],
97+
[dispatch, organizationFilter],
9298
)
9399

94100
// Refs

lib/redux/slices/settingsSlice.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ interface SettingsState {
2626
typography: TypographySettings
2727
compactMode: boolean
2828
showArchived: boolean
29+
/** Organization filter for AddRepositoryCombobox ('all' or organization login name) */
30+
organizationFilter: string
2931
}
3032

3133
const initialState: SettingsState = {
@@ -37,6 +39,7 @@ const initialState: SettingsState = {
3739
},
3840
compactMode: false,
3941
showArchived: false,
42+
organizationFilter: 'all',
4043
}
4144

4245
export const settingsSlice = createSlice({
@@ -58,6 +61,9 @@ export const settingsSlice = createSlice({
5861
setShowArchived: (state, action: PayloadAction<boolean>) => {
5962
state.showArchived = action.payload
6063
},
64+
setOrganizationFilter: (state, action: PayloadAction<string>) => {
65+
state.organizationFilter = action.payload
66+
},
6167
resetSettings: () => initialState,
6268
},
6369
})
@@ -68,6 +74,7 @@ export const {
6874
setTypography,
6975
setCompactMode,
7076
setShowArchived,
77+
setOrganizationFilter,
7178
resetSettings,
7279
} = settingsSlice.actions
7380

@@ -84,3 +91,5 @@ export const selectCompactMode = (state: { settings: SettingsState }) =>
8491
state.settings.compactMode
8592
export const selectShowArchived = (state: { settings: SettingsState }) =>
8693
state.settings.showArchived
94+
export const selectOrganizationFilter = (state: { settings: SettingsState }) =>
95+
state.settings.organizationFilter

lib/redux/store.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,27 @@
1212
*/
1313

1414
/* eslint-disable import/order -- Workspace package @gitbox/* resolves differently in CI vs local environments */
15-
import { configureStore } from '@reduxjs/toolkit'
15+
import { combineReducers, configureStore } from '@reduxjs/toolkit'
1616
import type { TypedUseSelectorHook } from 'react-redux'
1717
import { useDispatch, useSelector } from 'react-redux'
1818

19-
import { createStorageMiddleware } from '@gitbox/redux-storage-middleware'
19+
import {
20+
createStorageMiddleware,
21+
withHydration,
22+
} from '@gitbox/redux-storage-middleware'
2023
import authReducer from './slices/authSlice'
2124
import boardReducer from './slices/boardSlice'
2225
import draftReducer from './slices/draftSlice'
2326
import settingsReducer from './slices/settingsSlice'
2427

28+
// Combine all reducers
29+
const rootReducer = combineReducers({
30+
auth: authReducer,
31+
board: boardReducer,
32+
draft: draftReducer,
33+
settings: settingsReducer,
34+
})
35+
2536
// Storage middleware configuration
2637
const { middleware: storageMiddleware } = createStorageMiddleware({
2738
// Synchronize settings, board, and draft slices to LocalStorage
@@ -30,12 +41,8 @@ const { middleware: storageMiddleware } = createStorageMiddleware({
3041
})
3142

3243
export const store = configureStore({
33-
reducer: {
34-
auth: authReducer,
35-
board: boardReducer,
36-
draft: draftReducer,
37-
settings: settingsReducer,
38-
},
44+
// Wrap reducer with withHydration to handle localStorage state restoration
45+
reducer: withHydration(rootReducer),
3946
middleware: (getDefaultMiddleware) =>
4047
getDefaultMiddleware({
4148
serializableCheck: {

0 commit comments

Comments
 (0)