blog/working-with-zustand #88
Replies: 47 comments 64 replies
-
Great Article!!! |
Beta Was this translation helpful? Give feedback.
-
Really well explained! |
Beta Was this translation helpful? Give feedback.
-
Great writeup! Looking forward to some more in-depth guides :) I haven't seen the style of putting all actions into a single store object, might try that out. Have you explored defining actions outside of the store? I've found it really effective in some past projects:
The main downside I've found is discoverability (consumers need to find where actions are defined) - still exploring different file patterns to simplify this. Curious what your overall store structure tends to look like! |
Beta Was this translation helpful? Give feedback.
-
Awesome article!! Congrats |
Beta Was this translation helpful? Give feedback.
-
I don't use Zustand but these were anyhow excellent tips, thanks. 👍
Heh, I found this funny because the correct phrase is "bare minimum," not "bear minimum." 😃 But seems intentional since Zustand's GitHub page has other funny puns as well:
|
Beta Was this translation helpful? Give feedback.
-
awesome!! Nice article! |
Beta Was this translation helpful? Give feedback.
-
Love this article! |
Beta Was this translation helpful? Give feedback.
-
Awesome article! Love the simplicity 🔥 |
Beta Was this translation helpful? Give feedback.
-
🐻 Adorable bear, cute article |
Beta Was this translation helpful? Give feedback.
-
I'm curious about what stops us from creating multiple stores using const useBearStore = create((set) => ({
bears: 0,
})
const useFishStore = create((set) => ({
fish: 0,
}) Instead of having all data in the same store and using slices? Assuming they don't need to interact with each other? Is there any drawbacks? In Twitter Daishi mentioned that Zustand is primarily designed for a single store. |
Beta Was this translation helpful? Give feedback.
-
The combine-with-useQuery leaves me wanting for a combination:
😆 |
Beta Was this translation helpful? Give feedback.
-
Great article! Simply explained and with great examples! I'm wondering, what if you had a |
Beta Was this translation helpful? Give feedback.
-
Good article! I think the only recommendation I might make is to separate the actions even more, in that you shouldn't even put them into your state in the first place. export const useFilterStore = create(() => ({
applied: []
})) Then you can either use <Button
onClick={() => {
useFilterStore.setState((state) => ({
applied: [...state.applied, filter]
}))
}}
>
Add Filter
</Button> ...Or if you want to be more ceremonial about it you can create a named function... export const addFilter = (filter) =>
useFilterStore.setState((state) => ({ applied: [...state.applied, filter] })),
}
// Elsewhere
<Button onClick={() => addFilter(filter)}>
Add Filter
</Button> I'd written about this in an Github issue as well: pmndrs/zustand#151 (comment) |
Beta Was this translation helpful? Give feedback.
-
Interesting usage with useQuery there. For some time now the projects I'm working on don't really require external stores like Redux, MobX or Zustand, but maybe I'm not just realizing the benefits of something like Zustand. Will have to try zustand and react-query a bit more in general. Might solve some issues for me that I don't even realize I have 😄 |
Beta Was this translation helpful? Give feedback.
-
One question though export const useBearActions = () => useBearStore((state) => state.actions) Wouldn't equality check fail for this as well? |
Beta Was this translation helpful? Give feedback.
-
Thanks for the insight, bro! |
Beta Was this translation helpful? Give feedback.
-
Zustand allows for a createContext, so you can encapsulate the component in
a zustand store. So each has its own instance. Just an fyi.
…On Tue, Mar 28, 2023, 11:46 PM Attila Veres ***@***.***> wrote:
If I understand the question correctly, I had a similar use case where I
wanted to implement a simple store for each picker on a page:
import { create, StoreApi } from "zustand";
export interface PickerStore {
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
toggleIsOpen: () => void;
}
// when you add a picker, add a unique key for it here
export const PickerStoreKeys = {
AcademicTitlePicker: "AcademicTitlePicker",
} as const;
type PickerStoreKeysType = typeof PickerStoreKeys;
type StoreKeyValues<T> = T[keyof T];
export type PickerStoreKey = StoreKeyValues<PickerStoreKeysType>;
// you set up a map to store all stores
const storeMap = new Map<PickerStoreKey, StoreApi<PickerStore>>();
export const usePickerStore = (key: PickerStoreKey): StoreApi<PickerStore> => {
if (!storeMap.has(key)) {
const store = create<PickerStore>((set) => ({
isOpen: false,
setIsOpen: (isOpen: boolean) => set(() => ({ isOpen })),
toggleIsOpen: () => set(({ isOpen }) => ({ isOpen: !isOpen })),
}));
storeMap.set(key, store);
}
return storeMap.get(key) as StoreApi<PickerStore>;
};
Then in your component:
<PickerWithOptionalSearchFilter
title={title}
searchText={searchText}
setSearchText={setSearchText}
onSelect={setSelectedItem}
selectedItem={selectedItem}
storeKey={PickerStoreKeys.AcademicTitlePicker}
items={items.filter((item) => item.label.toLowerCase().includes(searchText.toLocaleLowerCase())) || []}
/>
And then, finally, in the PickerWithOptionalSearchFilter itself:
const PickerWithOptionalSearchFilter: React.FC<PickerWithOptionalSearchProps> = ({
isRequired,
items,
storeKey,
isFilterOpen,
filterOpenHandler,
disabled = false,
loading = false,
loadMore,
staticDropdownButtonLabel = undefined,
title,
}) => {
const store = usePickerStore(storeKey);
const isFilterOpen = useStore(store, (state) => state.isOpen);
const setIsFilterOpen = useStore(store, (state) => state.setIsOpen);
const toggleFilterOpen = useStore(store, (state) => state.toggleIsOpen);
...
We finally went to just have this picker be controlled by its parent, but
I think I will remember this pattern for the future. I wonder if context is
necessary, or maybe using a Map to store stores is a less complex solution
in most contexts?
What do you guys think?
—
Reply to this email directly, view it on GitHub
<#88 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABTB7SNHMYJ5KDUC4YO7DBDW6PLE3ANCNFSM6AAAAAASF47QGM>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
Hello, Really nice article, really helped me a lot to properly organize my stores. I have a question regarding a zustand usage. Admitting a following store
Does the following subscribe is a good habit or will it have side effects ?
Best regards, |
Beta Was this translation helpful? Give feedback.
-
Hey there, great article! But I’m still a little confused as to good scenarios to use zustand + query v4. For example, I have a page where the user gets to filter some stuff (eg: check-in / checkout dates), right now those filter are being passed into the URL by query params. And there’s some code to push those params into the route, check if the params and the current filters are different in order to update the filters using useState. I was trying to figure out with in this case, I should use zustand to store the filters or leave it as it is… I thought for sure since filters can be considerate as global state, I should keep’em there. Anyway, still bit confused as to what cases should they be used together. |
Beta Was this translation helpful? Give feedback.
-
Amazing, I grew to like Zustand a lot but this article meant a great leap forward in terms of managing state logic in a clean, readable and easy to nderstand way. I was kinda disappointed that the often stated potential simpleness of the library just came down to a few simple hook examples and very few was clever enough to release the potential of it. Nice one! :) |
Beta Was this translation helpful? Give feedback.
-
Awesome article <3 |
Beta Was this translation helpful? Give feedback.
-
Great article! Really got me thinking! You showed an example of combining zustand to filter a query using React Query. What about the inverse - using React Query to fetch, for simplicity, a boolean value to set a value in the store? Any thoughts on what that might look like or if it’s even a good idea? |
Beta Was this translation helpful? Give feedback.
-
How can one adhere to the concept of not exporting the store, yet allowing a component to call I see no way other than exporting the entire store and calling destroy: const { destroy } = useMyStore()
useEffect(() => () => {
destroy()
}), []) |
Beta Was this translation helpful? Give feedback.
-
If I have non-React code that needs to use the get/setState methods (like unit tests), would it still make sense to export just the get/setState methods or is it worth it to just export the store? I've been updating our stores with these and some other suggestions and really like these concepts. However, I'm having troubling applying them to a store we have for a complex date picker/navigation UI. The state data has 6 properties. We export the entire store because multiple properties are used by different components, and we use the I could export custom selectors for groups of these properties I guess, but the real question is how best to export the get/setState methods. I can export the entire store of course, but maybe the effort to avoid that in this case isn't worth it? const initialState: SequenceNavigationState = {
hasFutureRecords: false,
hasPastRecords: false,
isFromCalendar: false,
isMinTimestamp: false,
selectedDate: todayInUtc(),
sequenceDates: []
}
export const useSequenceNavigationStore = createWithEqualityFn<SequenceNavigationState>(() => initialState, shallow)
// Unit tests call, to setup a specific state for use in testing
useSequenceNavigationStore.setState(...)
// React components
const { isFromCalendar, isMinTimestamp, selectedDate } = useSequenceNavigationStore()
// or
const { hasFutureRecords, hasPastRecords, selectedDate, sequenceDates } = useSequenceNavigationStore() |
Beta Was this translation helpful? Give feedback.
-
For actions i would always use this approach. This enables me to just import the import { create } from 'zustand';
export const useSomething = create(() => ({
isMinimized: false
}));
export const minimize = () => useHeaderStore.setState({ isMinimized: true }); |
Beta Was this translation helpful? Give feedback.
-
import { create } from 'zustand'
import { useShallow } from 'zustand/react/shallow'
export const useAuthStore = create(set => ({
user: null,
setUser: props => set({ user: props }),
removeUser: () => set({ user: null })
}))
export const useAuth = () =>
useAuthStore(useShallow(state => ({ user: state.user, setUser: state.setUser, removeUser: state.removeUser })))
// USING
const { setUser, user, removeUser } = useAuth() |
Beta Was this translation helpful? Give feedback.
-
You can check out the structure I use. import { create } from 'zustand'
import { useShallow } from 'zustand/react/shallow'
export const useAuthStore = create(set => ({
user: null,
setUser: props => set({ user: props }),
removeUser: () => set({ user: null })
}))
export const useAuth = () =>
useAuthStore(useShallow(state => ({ user: state.user, setUser: state.setUser, removeUser: state.removeUser })))
// USING
const { setUser, user, removeUser } = useAuth() |
Beta Was this translation helpful? Give feedback.
-
I love putting all the method in actions.FYI, if you use persist middle ware, you would need to partialize. export interface InitialStateType {
}
export const initialState: InitialStateType = {
}
export interface AppStoreType extends InitialStateType {
actions: {
};
}
export const useAppStore = create<AppStoreType>()(
persist(
(set, get) => ({
...initialState,
actions: {
},
}),
{
name: "appStore",
getStorage: () => localStorage,
partialize: (state) =>
Object.fromEntries(
Object.keys(initialState).map((key) => [
key,
state[key as keyof typeof state],
])
),
}
)
); |
Beta Was this translation helpful? Give feedback.
-
Hi, |
Beta Was this translation helpful? Give feedback.
-
Great points! I find the treating all actions as a single atom really great, that's such a simple thing to not know yet never thought of it that way. But I find creating creating serparate hook for individual states a bit of an overkill and too much code for me to be sane haha. So I opted to creating a hook where I can just pass the state name like this: const fooStore = create<FooStore>((set) => ({
bar: 10,
SetBar:....
}))
// Custom Hook
const useFoo = (selector: keyof FooStore) =>
fooStore(s=> s[selector])
// Implementation
const bar = useFoo("bar") This way I can keep my store code relatively small and I also get sweet auto complete and it's easier on the eyes compared to using selectors the default way. |
Beta Was this translation helpful? Give feedback.
-
blog/working-with-zustand
Let's dive into some tips for working with Zustand - one of my favourite client state management libraries in React.
https://tkdodo.eu/blog/working-with-zustand
Beta Was this translation helpful? Give feedback.
All reactions