-
Notifications
You must be signed in to change notification settings - Fork 53
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
HMS-2861: Use RTK for state management #1477
Conversation
d32af54
to
25524bf
Compare
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #1477 +/- ##
==========================================
- Coverage 77.69% 77.52% -0.17%
==========================================
Files 69 69
Lines 2035 2016 -19
Branches 560 557 -3
==========================================
- Hits 1581 1563 -18
+ Misses 413 412 -1
Partials 41 41 see 5 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
|
25524bf
to
909cade
Compare
Thanks for working on this other way of doing things. I'm still reviewing you PR and will hopefully soon be able to write you my thoughts about it. |
I'm in the middle of the review now and I've noticed a bug. The wizard keeps it's state across multiple spawns. To reproduce the issue, open the wizard, fill up a couple of fields, click on cancel and open it up again. The fields that have been selected in the previous wizard session are still active. The expected behavior is that on every new click on |
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 @lucasgarfield this is a very interesting approach and I'm glad you did the experimentation. The Redux toolkit is quite interesting, and the fact that we can explore the global state at any point in time adds value to this contribution.
In fairness to the other two approaches (the one with the Context
and the one with the states lifted up to the wizard's root) let's not forget that the React dev toolkit already allows us to explore the current wizard state:
For the context
:
For the prop lifting:
One advantage as you mentioned on slack for the Redux toolkit is that it allows you to get back in time, one inconvenient is that you always have to scroll a lot to get to the latest version of the global state and it gets mixed up with the states of the RTK queries.
There still work for this PR to be a drop in replacement but you're getting there 🚀
{release.match('centos-*') && <CentOSAcknowledgement />} | ||
{isFetching && ( |
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.
Where is the fetching spinner and error handling moved to? I can't find them
src/store/wizardSlice.ts
Outdated
name: 'wizard', | ||
initialState, | ||
reducers: { | ||
changeRelease: (state, action) => { |
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.
For each update a new function must be written right ? Can you try to come up with a generic one? Like we could have an extra information in the payload about which states gets to be updated and then to find it in the state on change
time. That would definitely be a better sell to me.
These functions are just setters right?
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.
They're reducers. They take a state and an action, and they return a state. In normal Redux state updates should be done immutably, but RTK uses the Immer library which allows us to write mutable state updates.
https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers#writing-reducers
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 link to the doc. I meant that these reducers are equivalent to setters, each changeXyz
function affects the action.payload
value to a place in the state.
changeEnvironment: (state, action) => { | ||
state.targetStep.targetEnvironment = action.payload; | ||
}, | ||
initializeWizard: () => initialState, |
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.
Initialize could take a payload too. That way we could pass in a previous image request to initialize the state with.
src/store/wizardSlice.ts
Outdated
import { RHEL_9, X86_64 } from '../constants'; | ||
|
||
type wizardState = { | ||
targetStep: { |
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'm understanding that your intention is to cluster data based on which step is supposed to update it. I think this kind of isolation from one step to another is essential for maintainability. We never want to have the XStep changing values of the YStep, that would lead to some buggy nightmares down the road.
My main concern here is that there is no actual software protection for that to happen, if in the future someone decides to call changeEnvironment
within the ReviewStep
they'll have very little friction to do so (just import changeEnvironment
and call it later in the component`. This issue is already arising in the former DDFWizard we are refactoring (see https://github.com/RedHatInsights/image-builder-frontend/blob/3e07472826749bc665157178759741ea2c841de9/src/Components/CreateImageWizard/formComponents/ReviewStep.js#L31-L51 in that code chunk the review step is doing some changes to the wizard step, which is less than ideal for the state machine of the wizazrd. )
Outside of the naming convention that gives us a framework to apply the law in the future, can you think of any way of improving this to provide better security in that regard?
src/store/hooks.ts
Outdated
|
||
import type { RootState, AppDispatch } from './index'; | ||
|
||
// Use throughout your app instead of plain `useDispatch` and `useSelector` |
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 don't understand this comment, can you elaborate please?
// By default the component doesn't show the Centos releases and only the RHEL | ||
// ones. The user has the option to click on a button to make them appear. | ||
const release = useAppSelector((state) => state.wizard.targetStep.release); | ||
const dispatch = 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.
should dispatch
be called appDispatch
to avoid any confusion with the React
dispatch
?
const prefetchSources = provisioningApi.usePrefetch('getSourceList'); | ||
const { isBeta } = useGetEnvironment(); | ||
// TODO: Handle isFetching state (use skeletons) | ||
// TODO: Handle isError state (very unlikely...) |
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'm looking forward to these TODOs to be implemented.
}; | ||
}; | ||
|
||
const initialState: wizardState = { |
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 bug with the react state always being the same after closing and reopening is likely to come from here. See the initialState
is a const, and we're updating that same object everytime we are calling the changeXyz
functions. To avoid that issue you can either not have the initialState
as a global const, or you could deep copy the object on re-initialization.
src/store/wizardSlice.ts
Outdated
|
||
type wizardState = { | ||
targetStep: { | ||
release: 'rhel-93'; |
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 correctly type the release and distributions as they were previously?
src/store/wizardSlice.ts
Outdated
targetStep: { | ||
release: 'rhel-93'; | ||
architecture: 'x86_64'; | ||
targetEnvironment: 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.
The targetEnvironment
type is less safe than the predecessor it intends to replace.
909cade
to
905baf1
Compare
905baf1
to
338c3b3
Compare
Closing this for a fresh start. |
This is currently a WIP, there are a few bugs and some clean-up/refactoring is necessary
So far we have considered (thanks to the work of @lavocatt) two possible options for managing the wizard state: storing state in the highest level component via useState() hooks, and storing state via a useContext() hook.
However, there is a 3rd option - we can use Redux (with RTK).
One big advantage is we can take advantage of the Redux dev tools and use them to inspect the state of the wizard during development: