How to subscribe / unsubscribe to specific parts of the state? #2458
-
I have a Zustand store as follows: import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
const initialRating = {
id: 'rating1',
stars: 5,
score: 3,
size: '24px',
title: 'Food'
};
export const useRatingStore = create()(
immer((set) => ({
ratings: {
[initialRating.id]: initialRating
},
removeAllRatings: () =>
set((state) => {
state.ratings = {};
}),
newRating: (payload) =>
set((state) => {
if (state.ratings[payload.id]) {
console.error('Rating already exists', payload);
} else {
state.ratings[payload.id] = payload;
if (payload.score > payload.stars) {
state.ratings[payload.id].score = payload.stars;
}
}
}),
updateScore: (payload) =>
set((state) => {
const toUpdate = payload;
if (Array.isArray(toUpdate)) {
toUpdate.forEach((rating) => {
if (state.ratings[rating.id]) {
const proxyState = state.ratings[rating.id];
proxyState.score = Math.min(rating.score, proxyState.stars);
} else {
console.error('updateScore not found', rating.id);
}
});
} else if (state.ratings[toUpdate?.id]) {
const proxyState = state.ratings[toUpdate.id];
proxyState.score = Math.min(toUpdate.score, proxyState.stars);
} else {
console.error('Invalid updateScore payload', payload);
}
}),
updateStars: (payload) =>
set((state) => {
const toUpdate = payload;
if (Array.isArray(toUpdate)) {
toUpdate.forEach((rating) => {
if (state.ratings[rating.id]) {
const proxyState = state.ratings[rating.id];
proxyState.stars = rating.stars;
} else {
console.error('updateStars not found', rating.id);
}
});
} else if (state.ratings[toUpdate?.id]) {
const proxyState = state.ratings[toUpdate.id];
proxyState.stars = toUpdate.stars;
} else {
console.error('Invalid updateStars payload', payload);
}
}),
updateSize: (payload) =>
set((state) => {
const toUpdate = payload;
if (Array.isArray(toUpdate)) {
toUpdate.forEach((rating) => {
if (state.ratings[rating.id]) {
state.ratings[rating.id].size = rating.size;
} else {
console.error('updateSize not found', rating.id);
}
});
} else if (state.ratings[toUpdate?.id]) {
state.ratings[toUpdate.id].size = toUpdate.size;
} else {
console.error('Invalid updateSize payload', payload);
}
})
}))
); It keeps track of a variable number of ratings. Works like a charm with reactJS. Now I need to react to changes in individual components in non-ui parts of the app. The useRatingStore.subscribe((state, oldState) => console.log('state', state, 'oldState', oldState)); and get all ratings. However I'd like to subscribe to specific ratings only. How do I do this? Also: how to unsubscribe? |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 2 replies
-
@Stwissel here you go -> https://github.com/pmndrs/zustand?tab=readme-ov-file#using-subscribe-with-selector |
Beta Was this translation helpful? Give feedback.
-
Based on the documentation I changed my store to export const useRatingStore = create()(
subscribeWithSelector(
immer((set) => ({
ratings: {
[initialRating.id]: initialRating
},
removeAllRatings: () =>
set((state) => {
state.ratings = {};
}),
newRating: (payload) =>
set((state) => {
if (state.ratings[payload.id]) {
console.error('Rating already exists', payload);
} else {
state.ratings[payload.id] = payload;
if (payload.score > payload.stars) {
state.ratings[payload.id].score = payload.stars;
}
}
}),
updateScore: (payload) =>
set((state) => {
const toUpdate = payload;
if (Array.isArray(toUpdate)) {
toUpdate.forEach((rating) => {
if (state.ratings[rating.id]) {
const proxyState = state.ratings[rating.id];
proxyState.score = Math.min(rating.score, proxyState.stars);
} else {
console.error('updateScore not found', rating.id);
}
});
} else if (state.ratings[toUpdate?.id]) {
const proxyState = state.ratings[toUpdate.id];
proxyState.score = Math.min(toUpdate.score, proxyState.stars);
} else {
console.error('Invalid updateScore payload', payload);
}
}),
updateStars: (payload) =>
set((state) => {
const toUpdate = payload;
if (Array.isArray(toUpdate)) {
toUpdate.forEach((rating) => {
if (state.ratings[rating.id]) {
const proxyState = state.ratings[rating.id];
proxyState.stars = rating.stars;
} else {
console.error('updateStars not found', rating.id);
}
});
} else if (state.ratings[toUpdate?.id]) {
const proxyState = state.ratings[toUpdate.id];
proxyState.stars = toUpdate.stars;
} else {
console.error('Invalid updateStars payload', payload);
}
}),
updateSize: (payload) =>
set((state) => {
const toUpdate = payload;
if (Array.isArray(toUpdate)) {
toUpdate.forEach((rating) => {
if (state.ratings[rating.id]) {
state.ratings[rating.id].size = rating.size;
} else {
console.error('updateSize not found', rating.id);
}
});
} else if (state.ratings[toUpdate?.id]) {
state.ratings[toUpdate.id].size = toUpdate.size;
} else {
console.error('Invalid updateSize payload', payload);
}
})
}))
)
); Now I can: const key = 'rating1';
const sub = useRatingStore.subscribe(
(state) => state.ratings[key],
(newR, oldR) => changeSignal(newR, oldR)); and process it: const changeSignal = (newR, oldR) => {
if (newR.score !== oldR.score || newR.stars !== oldR.stars) {
console.log(
`Rating ${newR.id} changed from ${oldR.score}/${oldR.stars} to ${newR.score}/${newR.stars}`
);
}
}; I also tried What do I miss? btw: love your work! |
Beta Was this translation helpful? Give feedback.
@Stwissel here you go -> https://github.com/pmndrs/zustand?tab=readme-ov-file#using-subscribe-with-selector