-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
[Feedback Request] Does Zustand v5-rc work for you? #2741
Comments
Yes, this is the last resort I can think of to get some feedback. related tweet
|
@dai-shi I have updated the comment above with how I use zustand. It's a new and simple app. Do I have to provide a Fallback for all selectors? That would be so unnecessary, wouldn't it be? |
This looks bad. Even with v4, this can cause extra re-renders. So, in that sense, v5 catches such bugs.
In this case, that wouldn't solve it anyway. |
So |
True. |
-const [searchValue, setSearchValue] = useStore((state) => [state.searchValue, state.setSearchValue]);
+const searchValue = useStore((state) => state.searchValue);
+const setSearchValue = useStore((state) => state.setSearchValue); Is this the only way to make the selector references stable? Is it possible to group them together to reduce boilerplate and function calls? |
@pavitra-infocusp sure const [searchValue, setSearchValue] = useStore(useShallow((state) => [state.searchValue, state.setSearchValue])); |
Thanks @dai-shi and @dbritto-dev! Zustand v5 is now working fine. |
I was having troubles as well on 5.0.0 rc2 where v4.5.5 was working fine (or at least, not telling me there was issues but was running fine). I spent a few hours trying to find the culprit because the error logs where completely useless.
I had to use useShallow, that's it. Now I need to go see what this is all about to understand why this suddenly happened. from:
to:
|
Thanks for reporting! This is a bit unexpected to me, as my assumption was it can cause the infinite loop even in v4. I think we need to clarify this in the v5 migration guide. 👉 #2749 |
typescript error when using with immer middleware and slice pattern.
|
Everything works with the RC. I tried previous versions, and the only additional step I needed to take was to avoid having a fallback to empty arrays. With the React compiler, there was an issue with react.Ref when I created the Store context. However, that was fixed by the React team some weeks ago. |
@wuarmin would you mind creating a minimal repro on stackblitz? I'd be happy to help |
https://react.dev/errors/185 It seems like unstable selector result. I wonder why it doesn't happen in development. |
Buh, I'm pretty busy at the moment. I will try, but I probably won't be able to do this repro this week.
Yes, it was pretty sobering when I deployed and then nothing worked anymore. I searched for a long time until I found out that zustand had something to do with it. This is the zustand store that triggers the error in the prod area, among other things: import { create } from "zustand";
import { useQuery, keepPreviousData } from "@tanstack/react-query";
import { gql } from "graphql-request";
import { apiClient } from "@/features/query";
import { Order } from "@/graphql/fragments";
const query = gql`
query FilteredOrders($after: String, $first: Int, $query: OrdersQueryInput!) {
filteredOrders(after: $after, first: $first, query: $query) {
totalCount
nodes {
...Order
}
}
}
${Order}
`;
export const statusCategories = [
{ id: "all", name: "Alle" },
{ id: "action_required", name: "Aktion erforderlich" },
];
const afterForPage = (page, itemsPerPage) => String((page - 1) * itemsPerPage);
const pageParams = (page, itemsPerPage) => ({
after: afterForPage(page, itemsPerPage),
first: itemsPerPage,
});
const paginationFilterState = (set, get) => ({
filterItems: { statusCategory: "action_required" },
filters: [],
sort: [{ id: "id", desc: true }],
rowSelection: {},
page: 1,
actions: {
applyFilters: (filters) => set({ filters, page: 1 }),
applyFilterItem: (id, value) => {
const newFilterItems = { ...get().filterItems, [id]: value };
if (!value) {
delete newFilterItems[id];
}
set({ filterItems: newFilterItems, page: 1 });
},
goToPage: (page) => set({ page, rowSelection: {} }),
sort: (sort) => set({ sort }),
applyRowSelection: (rowSelection) => set({ rowSelection }),
},
});
const useQueryStore = create(paginationFilterState);
const useFilter = () =>
useQueryStore(({ filters, filterItems }) => {
return {
...filters.reduce((acc, { id, value }) => ({ ...acc, [id]: value }), {}),
...filterItems,
};
});
export const useFilterItems = () =>
useQueryStore(({ filterItems }) => filterItems);
export const useFilters = () => useQueryStore(({ filters }) => filters);
export const usePage = () => useQueryStore(({ page }) => page);
export const useRowSelection = () =>
useQueryStore(({ rowSelection }) => rowSelection);
export const useSort = () => useQueryStore(({ sort }) => sort);
export const useQueryActions = () => useQueryStore((state) => state.actions);
export function useOrders({ itemsPerPage = 100 } = {}) {
const filter = useFilter();
const page = usePage();
const sort = useSort();
return useQuery({
queryKey: ["orders", "list", filter, sort, page, itemsPerPage],
placeholderData: keepPreviousData,
queryFn: async () => {
return apiClient
.rq(query, {
...pageParams(page, itemsPerPage),
query: { filter, sort },
})
.then((data) => ({
totalPages: Math.ceil(data.filteredOrders.totalCount / itemsPerPage),
records: data.filteredOrders.nodes,
}));
},
});
} @dai-shi Do you see a problem. Thanks |
useQueryStore(({ filters, filterItems }) => {
return {
// ...
};
}); is creating a new object every time which causes infinite loops. |
@dai-shi I think the docs have to be updated as well to not encourage this kind of usages |
Wow, I didn't notice that. @dbritto-dev Can you fix? |
@dai-shi sure. BTW, we talked about that in |
What's the best way to rewrite this: const useFilter = () =>
useQueryStore(({ filters, filterItems }) => {
return {
...filters.reduce((acc, { id, value }) => ({ ...acc, [id]: value }), {}),
...filterItems,
};
}); |
It's tricky. proxy-memoize may help, but I think you should at least separate to const useFilters = () => {
const filters = useQueryStore((state) => state.filters);
return filters.reduce((acc, { id, value }) => ({ ...acc, [id]: value }), {});
};
const useFilterItems = () => useQueryStore((state) => state.filterItems); |
Minimal example - works in v4, breaks in v5 import { create, StateCreator } from "zustand";
import { immer } from "zustand/middleware/immer";
interface Slice1 {
foo: string;
}
interface Slice2 {
bar: string;
}
const createSlice1: StateCreator<Slice1, [], [], Slice1> = (set) => ({
foo: "test",
});
export const useBoundStore = create<Slice1 & Slice2>()(
immer((...a) => ({
...createSlice1(...a),
}))
); Fix in v5: const createSlice1: StateCreator<Slice1 & Slice2, [], [], Slice1> = (set) => ({ The first parameter of StateCreator now requires the entire correct type of the store. I think this is a bug? Is the StateCreator's first parameter supposed to contain the entire type of the store? Counterpoint: the example for slices pattern in the docs does not include the entire store type (is missing the SharedSlice type) which works in v4 but does not work in v5 if you are using immer. I unfortunately don't have the bandwidth in the next two weeks to investigate why. Help is appreciated! |
Yes, I believe so. (And, yes, I noticed in some cases, it can be a Partial of it.) |
zustand/docs/guides/typescript.md Line 416 in 6b29015
Should that be |
Why do you think that following works? const useFilters = () => {
const filters = useQueryStore((state) => state.filters);
return filters.reduce((acc, { id, value }) => ({ ...acc, [id]: value }), {});
}; It returns everytime a new reference, right? Is this expected to work in v5? |
It should work in v5 and v4 too (v4 doesn't loop infinitely, but still causes extra rerenders without this fix.) |
Oh, I understand. So following should work too. Right? const useFilter = () => {
const filters = useQueryStore(({ filters }) => filters);
const filterItems = useQueryStore(({ filterItems }) => filterItems);
return {
...filters.reduce((acc, { id, value }) => ({ ...acc, [id]: value }), {}),
...filterItems,
};
}; Thanks |
@wuarmin yeah, that's the way to solve it. I'll add to |
Hi just installed zustand here for the first time and got version |
@cpatti97100 sure, we added new docs for APIs and Hooks recently |
Thanks everyone for giving feedback! |
How to Install
Migration Guide
https://github.com/pmndrs/zustand/blob/main/docs/migrations/migrating-to-v5.md
How to respond
Add comments to this issue or add reactions to this issue comment.
👍 : I've tested v5-rc and it works fine for me.
👎 : I've tested v5-rc and found an issue. < Comment below!
👀 : I'll try it later.
The text was updated successfully, but these errors were encountered: