-
Notifications
You must be signed in to change notification settings - Fork 0
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
chore: creates persisted store for Active Project Id using Zustand #67
Conversation
function useActions() { | ||
const context = useCurrentContext() | ||
return context.actions | ||
} |
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.
im having trouble with this typing. Im not able to get the actions back, any chance you see the problem?
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'd need more time to try to figure it out. My gut feeling is that attempting to create a helper function for creating a store and provider is a "premature abstraction", by which I mean an abstraction made before being clear about how something is going to be re-used, and being clear about the goal of the abstraction. I assume the goal is to avoid repeating code for each store, but the costs are:
- More complicated code and typings
- The need to change things in many places when you want to add additional functionality to a store, like configuring migrations
- More code to test and maintain
I'm not sure it really saves that much code or makes it easier to understand what is happening vs. just following the pattern with some repeated code each time. Doing it without the helper will certainly make the types easier. I imagine the issue is related to the wrapping of actions and generics, but it would take me an hour or so to figure it out.
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.
My concern is that there is a lot of boiler plate code and we want all our stores to look the same. Also I am anticipating that we are going to have a few of these stores as In mobile we have quite a few persisted store. I think there will be slightly less for desktop (we don't need a persisted observation store for example). But I think there are still going to be a few stores.
We are expecting all of them to return:
- a provider, that takes the value as props
- a createStore function that returns a persisted or non persisted store
- a useStore hook (with atomic selectors)
- a useStoreAction hook. (we also do not want the actions to be in the zustand store).
We have a lot of very specific requirements for these stores. So it feels like there is alot of room for error by not abstracting this?
Perhaps it would be best to abstract some of these things, but not all of them? Or do you think abstraction is not necessary at all?
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.
It's hard to know exactly what makes sense to abstract until you have started writing some actual stores. I think it's generally fine to start with repeated boilerplate. The challenge with writing the abstractions first is that you can end up trapping yourself in a corner with abstractions that don't actually make sense or make the code harder to write, or you find that every change requires changing lots of pieces of the abstraction. This is very similar to the guidelines we have talked about with React components - it's best to abstract small re-usable parts of the code, rather than creating a large abstraction (component) that tries to manage lots of different things, and ends up adding to the maintenance burden.
An example here might be a future need to have a Zustand store that is not / should not be persisted, and then having to adjust the abstractions so that they can handle that case.
Looking at this code, I think the most "boiler-platey" part which would be repeated and is unlikely to change is the hooks, so you could create an abstraction like:
function createHooks<TStoreState, TStoreActions>(
context: React.Context<{
store: StoreApi<TStoreState>;
actions: TStoreActions;
}>,
) {
function useContextValue() {
const value = useContext(context);
if (!value) {
throw new Error('Must set up the provider first');
}
return value;
}
function useStoreState(): TStoreState;
function useStoreState<S>(selector: (state: TStoreState) => S): S;
function useStoreState<S>(selector?: (state: TStoreState) => S) {
const {store} = useContextValue();
return useStore(store, selector!);
}
function useStoreActions() {
const {actions} = useContextValue();
return actions;
}
return {useStoreState, useStoreActions};
}
That feels like something that is unlikely to change and is fairly simple to understand and test (if tests are needed), and is limited in scope about what it is trying to do (takes a single argument and turns it into two hooks)
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.
src/renderer/src/App.tsx
Outdated
<CssBaseline /> | ||
<IntlProvider> | ||
<QueryClientProvider client={queryClient}> | ||
<PersistedActiveProjectIdProvider store={PersistedProjectIdStore}> |
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.
nitpick: this is really <ActiveProjectIdProvider>
- the provider is the same whether it is persisted or not.
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.
} | ||
} | ||
|
||
const PersistedProjectIdStore = createActiveProjectIdStore({ |
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.
nitpick: we haven't discussed this, but generally we've followed the pattern of naming instances with lowercase, and factory functions / classes with capitalized names, I think it's good to continue that pattern.
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.
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.
great, looks good.
src/renderer/src/routes/index.tsx
Outdated
|
||
console.log({ activeProjectId }) |
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.
console.log({ activeProjectId }) |
src/renderer/src/App.tsx
Outdated
} | ||
} | ||
|
||
const PersistedProjectIdStore = CreateActiveProjectIdStore({ |
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.
const PersistedProjectIdStore = CreateActiveProjectIdStore({ | |
const persistedProjectIdStore = createActiveProjectIdStore({ |
I think my comment before was maybe not very clear, I did not mean for you to change the case of CreateACtiveProjectIdStore
activeProjectIdStore
.