Skip to content
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

State migrations for hydration and loading files #843

Merged
merged 5 commits into from
Jan 12, 2022

Conversation

noahm
Copy link
Contributor

@noahm noahm commented Jan 11, 2022

What kind of change does this PR introduce? (delete not applicable)

  • Bugfix

This uses the built-in migrations feature of redux-persist to migrate the state shape to the latest whenever it is hydrated. (I believe the initial code from #130 may have never worked.) It also uses a shared function to do the same when loading from a JSON file so older files will continue to function with upgrades to the app.

The migrations feature of redux-persist depends on a versioning scheme which is a bit overkill for where this app is at the moment, but I think it will be useful in the long run. We just need to be vigilant about bumping the version and including a migration step for each change to the state interface.

(Also fixes a typo in the menu.)

Maintainer note:
Fixes an error thrown if localStorage is missing parameters currently expected, due to version updates. Reproducing on master:

let store = JSON.parse(window.localStorage["persist:root"])
let y2020 = JSON.parse(store.Y2020)
y2020.healthSavingsAccounts = undefined
store.Y2020 = JSON.stringify(y2020)
window.localStorage["persist:root"] = JSON.stringify(store)

Will throw an error after switching to Health Savings Account tab.

@codecov
Copy link

codecov bot commented Jan 11, 2022

Codecov Report

Merging #843 (2f6b895) into master (82488b5) will increase coverage by 0.14%.
The diff coverage is 55.55%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #843      +/-   ##
==========================================
+ Coverage   70.52%   70.67%   +0.14%     
==========================================
  Files         201      202       +1     
  Lines        6555     6557       +2     
  Branches     1103     1106       +3     
==========================================
+ Hits         4623     4634      +11     
+ Misses       1918     1909       -9     
  Partials       14       14              
Impacted Files Coverage Δ
src/components/Menu.tsx 93.33% <ø> (ø)
src/redux/migration.ts 33.33% <33.33%> (ø)
src/redux/store.ts 85.71% <60.00%> (+12.38%) ⬆️
src/redux/fs/FSReducer.ts 58.33% <100.00%> (ø)
src/forms/Y2020/irsForms/ScheduleEIC.ts 79.32% <0.00%> (+0.55%) ⬆️
src/forms/Y2021/irsForms/ScheduleEIC.ts 79.32% <0.00%> (+4.46%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 82488b5...2f6b895. Read the comment docs.

* @returns state, mutated
*/
export function migrateEachYear(state: YearsTaxesState) {
for (const year of enumKeys(TaxYears)) {
Copy link
Collaborator

@zakpatterson zakpatterson Jan 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your as unknown as YearsTaxesState dance in the other file is needed because you're returning a YearsTaxesState type from this function, but you're passing in a subtype of YearsTaxesState, (actually a union of YearsTaxesState).

So I think this could be either:

const migrateEachYear = <S>(state: S & PersistedState & YearsTaxesState): S & PersistedState & YearsTaxesState => ...

Or better:

const migrateEachYear = <S extends PersistedState & YearsTaxesState>(state: S): S => ...

Could this be a new state object created here instead of mutating (it's the redux way right?), and written more simply as an expression, instead of a for statement, for example something like:

enumKeys(TaxYear).reduce((acc, year) => ({...acc, [year]: ...}), {} as S)

We only have a couple for statements in this code base and I think they should be rewritten too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see it needing any reference to PersistedState for this function specifically (it doesn't care about that part of state at all). But the rest of it sounds great.

'0': (state) => {
// this type dance is unfortunate, but createMigrate isn't typed generically...
const migrated = migrateEachYear(state as unknown as YearsTaxesState)
return migrated as unknown as typeof state
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I bet you can get rid of this. I agree I don't like the type in redux-persist could probably be better, but it's just requiring that the parameter and return is a subtype of PersistedState. So you can enforce any union type of PersistedState in the migration functions, see comment in migration.ts.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found a much cleaner way by dropping their whole system for a plain async function with just our simple logic.

@@ -21,7 +23,7 @@ const fsReducer = <S, A extends AnyAction>(
case 'fs/recover': {
return {
...newState,
...action.data
...migrateEachYear(action.data as unknown as YearsTaxesState)
Copy link
Collaborator

@zakpatterson zakpatterson Jan 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could probably get rid of this double cast by restricting the type of S to extends YearsTaxesState & PersistedState as above

@noahm
Copy link
Contributor Author

noahm commented Jan 12, 2022

@zakpatterson I think the latest commit addresses all that feedback. Let me know if you'd prefer to have it rebased.

@zakpatterson
Copy link
Collaborator

Nice find on the utilitytype

Copy link
Collaborator

@zakpatterson zakpatterson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work!

@zakpatterson zakpatterson merged commit 908811c into ustaxes:master Jan 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants