-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
[Redux Toolkit Migration] Use new Redux Toolkit configureStore API #4000
Conversation
✅ Deploy Preview for actualbudget ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
546d614
to
c53deaf
Compare
Bundle Stats — desktop-clientHey there, this message comes from a GitHub action that helps you and reviewers to understand how these changes affect the size of this project's bundle. As this PR is updated, I'll keep you updated on how the bundle size is impacted. Total
Changeset
View detailed bundle breakdownAdded No assets were added Removed No assets were removed Bigger
Smaller
Unchanged
|
Bundle Stats — loot-coreHey there, this message comes from a GitHub action that helps you and reviewers to understand how these changes affect the size of this project's bundle. As this PR is updated, I'll keep you updated on how the bundle size is impacted. Total
Changeset No files were changed View detailed bundle breakdownAdded No assets were added Removed No assets were removed Bigger No assets were bigger Smaller No assets were smaller Unchanged
|
/update-vrt |
@@ -11,6 +11,7 @@ const currentUndoState: T.UndoState = { | |||
url: null, | |||
openModal: null, | |||
selectedItems: null, | |||
current: null, |
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.
Moved from redux state.app.lastUndoState
to here
@@ -8,6 +8,7 @@ export type UndoState = { | |||
name: string; | |||
items: Set<string>; | |||
} | null; | |||
current: unknown; |
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.
Moved from redux state.app.lastUndoState
to here
// Reset the state and only keep around things intentionally. This | ||
// blows away everything else | ||
state = { | ||
account: initialAccountState, |
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.
TS is complaining about the missing state properties so I have set them to initial states instead.
@@ -1,7 +1,7 @@ | |||
import type * as constants from '../constants'; | |||
|
|||
export type AccountState = { | |||
failedAccounts: Map<string, { type: string; code: string }>; | |||
failedAccounts: { [key: string]: { type: string; code: string } }; |
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.
Changed to object because redux toolkit complains that Map
is non-serializable
@@ -18,17 +16,6 @@ export function update(state = initialState, action: Action): AppState { | |||
...state, | |||
...action.state, | |||
}; | |||
case constants.SET_LAST_UNDO_STATE: |
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.
Removed from redux state and moved to loot-core/platform/client/undo
since redux toolkit is complaining that it's mutating in the reducer
state.lastUndoState.current = action.undoState; | ||
return state; | ||
|
||
case constants.SET_LAST_SPLIT_STATE: |
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.
Removed from redux state and moved to useSplitExpanded
since redux toolkit is complaining that it's mutating in the reducer
@@ -2,8 +2,8 @@ import * as constants from '../constants'; | |||
import type { Action } from '../state-types'; | |||
import type { AccountState } from '../state-types/account'; | |||
|
|||
const initialState: AccountState = { | |||
failedAccounts: new Map(), |
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.
Changed from Map to object since redux toolkit is complaining that Map is non-serializable
5b062e2
to
dd92cb3
Compare
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.
Thanks for doing this! Left one quick suggestion to start since I think it will make the diff much more manageable to review 😅
import { useDispatch, useSelector } from 'react-redux'; | ||
|
||
import { type AppDispatch, type RootState } from 'loot-core/client/store'; | ||
|
||
export const useAppDispatch = useDispatch.withTypes<AppDispatch>(); | ||
export const useAppSelector = useSelector.withTypes<RootState>(); |
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.
Is it possible to keep the names the same so we don't have to rename everywhere?
import { useDispatch, useSelector } from 'react-redux'; | |
import { type AppDispatch, type RootState } from 'loot-core/client/store'; | |
export const useAppDispatch = useDispatch.withTypes<AppDispatch>(); | |
export const useAppSelector = useSelector.withTypes<RootState>(); | |
import { useDispatch as useReduxDispatch, useSelector as useReduxSelector } from 'react-redux'; | |
import { type AppDispatch, type RootState } from 'loot-core/client/store'; | |
export const useDispatch = useReduxDispatch.withTypes<AppDispatch>(); | |
export const useSelector = useReduxSelector.withTypes<RootState>(); |
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.
I have reverted these renames. These are based on redux toolkit recommendations https://redux.js.org/usage/usage-with-typescript. Will separate them out on another PR instead to make this PR more manageable to review.
b4055e8
to
08cfcf4
Compare
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.
Thanks for the changes! Code looks mostly good now, had some questions around performance and bug bashing
packages/desktop-client/src/components/mobile/accounts/Accounts.tsx
Outdated
Show resolved
Hide resolved
export type AppDispatch = typeof store.dispatch; | ||
export type GetRootState = typeof store.getState; |
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.
Curious if there's a reason to call these AppDispatch
and GetRootState
rather than Dispatch
and GetState
for the same reason as my first comment! Not a huge deal though at this point
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.
These are based on the redux toolkit recommendations https://redux.js.org/usage/usage-with-typescript. Same as the naming of the useAppDispatch
and useAppSelector
. I will just bring back these names on another PR to make this PR easier to review.
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.
Rename PR: #4101
@@ -21,7 +22,7 @@ export type BoundActions = { | |||
|
|||
// https://react-redux.js.org/api/hooks#recipe-useactions | |||
/** | |||
* @deprecated please use actions directly with `useDispatch` | |||
* @deprecated please use actions directly with `useAppDispatch` |
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.
* @deprecated please use actions directly with `useAppDispatch` | |
* @deprecated please use actions directly with `useDispatch` |
? !data.state.ids.has(id) | ||
: data.state.ids.has(id), | ||
isExpanded: (id: string) => { | ||
const idSet = new Set(data.state.ids); |
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.
Since we're now constructing the set on each call to isExpanded
(O(n)) I'm a little worried about performance as this function gets called for each transaction. Is there any way we can still cache the set while keeping the state serializable? Maybe use a plain object with IDs as keys instead?
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.
Or, since we're not even using Redux at all anymore for this state, couldn't we just keep using a Set
? Or is the plan to later move this to Redux?
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.
Nice catch! Reverted it back to a Set
@@ -80,15 +79,16 @@ export function ManagePayeesWithData({ | |||
await refetchRuleCounts(); | |||
} | |||
|
|||
await dispatch(setLastUndoState(null)); | |||
undo.setUndoState('current', null); |
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.
One side-effect of this approach is that other components (and this component) won't rerender when the undo state changes. Not sure if/where this matters—have you had a chance to confirm if any undo behaviour is broken? Wasn't able to repro anything in my testing
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.
The dispatching of setLastUndoState
doesn't actually cause a re-render because in the reducer the state is mutated instead of returning a new state. See old comment on https://github.com/actualbudget/actual/pull/4000/files#diff-6312fd65ee6fa1c84229aa82af3e5def6a17a14c01c4938dd5475cba8d66afbaL21.
@@ -1239,8 +1239,8 @@ export function useTableNavigator<T extends TableItem>( | |||
}, []); | |||
|
|||
useEffect(() => { | |||
modalStackLength.current = store.getState().modals.modalStack.length; | |||
}, []); | |||
modalStackLength.current = modalState.modalStack.length; |
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.
Unfortunately I think this approach breaks the field refocus behaviour due to caching. Steps to repro:
- Mark transactions as reconciled
- Edit the payee on a reconciled transaction
- Hit 'Cancel' on the modal
On edge, this refocuses the payee field. However in this PR the field is not focused
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.
Should be fixed on the latest push
2601c05
to
630170c
Compare
…-serializable values from the state
630170c
to
8420ae4
Compare
Initial migration to redux-toolkit. This PR focuses on retrofitting the existing reducers to the newer redux toolkit APIs. We can work on converting them to redux slices in a future PR.
Aside from converting to the usage of typed
useAppDispatcher
anduseAppSelector
, I had to do some changes to theSET_LAST_UNDO_STATE
andSET_LAST_SPLIT_STATE
to prevent mutating the state in their reducers because redux toolkit now check for that.There are still some redux validation errors due to non-serializable values being on the the state e.g. modal callback functions. But I have disabled those checks temporarily until we can address them in a future PR.
configureStore
APISET_LAST_UNDO_STATE
,SET_LAST_SPLIT_STATE
)createSlice
APIcreateAsyncThunk
API