Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/app/store/__tests__/selectorHooks.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { act, renderHook } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import {
useMessagingStore,
useMessagingStoreSelector,
} from '../messagingStore';
import {
useNotificationStore,
useNotificationStoreSelector,
} from '../notificationStore';
import {
useVolunteerStore,
useVolunteerStoreSelector,
} from '../volunteerStore';

describe('store selector hooks', () => {
it('does not re-render a messaging selector for unrelated state changes', () => {
let renders = 0;
const { result } = renderHook(() => {
renders += 1;
return useMessagingStoreSelector((state) => state.messages);
});

act(() => useMessagingStore.getState().setTyping(!useMessagingStore.getState().isTyping));

expect(result.current).toEqual([]);
expect(renders).toBe(1);
});

it('does not re-render a notification selector for unrelated state changes', () => {
let renders = 0;
const { result } = renderHook(() => {
renders += 1;
return useNotificationStoreSelector((state) => state.notifications);
});

act(() => useNotificationStore.setState({ clearRead: () => undefined }));

expect(result.current).toEqual([]);
expect(renders).toBe(1);
});

it('does not re-render a volunteer selector for unrelated state changes', () => {
let renders = 0;
const { result } = renderHook(() => {
renders += 1;
return useVolunteerStoreSelector((state) => state.volunteers);
});

act(() => useVolunteerStore.setState({ removeVolunteer: () => undefined }));

expect(result.current).toEqual([]);
expect(renders).toBe(1);
});
});
6 changes: 6 additions & 0 deletions src/app/store/messagingStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ interface MessagingState {
getTotalUnreadCount: () => number;
}

/**
* Subscribe to a focused slice of messaging state instead of the entire store.
*/
export const useMessagingStoreSelector = <T>(selector: (state: MessagingState) => T): T =>
useMessagingStore(selector);

export const useMessagingStore = create<MessagingState>((set, get) => ({
conversations: [],
currentConversation: null,
Expand Down
6 changes: 6 additions & 0 deletions src/app/store/notificationStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ interface NotificationState {
clearRead: () => void;
}

/**
* Subscribe to a focused slice of notification state instead of the entire store.
*/
export const useNotificationStoreSelector = <T>(selector: (state: NotificationState) => T): T =>
useNotificationStore(selector);

export const useNotificationStore = create<NotificationState>((set, get) => ({
notifications: load<AppNotification[]>(STORAGE_KEY, [], dateReviver),
addNotification: (n) => {
Expand Down
6 changes: 6 additions & 0 deletions src/app/store/volunteerStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ interface VolunteerState {
updateSMSPreferences: (id: string, sms: Partial<VolunteerSMSPreferences>) => void;
}

/**
* Subscribe to a focused slice of volunteer state instead of the entire store.
*/
export const useVolunteerStoreSelector = <T>(selector: (state: VolunteerState) => T): T =>
useVolunteerStore(selector);

export const useVolunteerStore = create<VolunteerState>((set, get) => ({
volunteers: load<Volunteer[]>(STORAGE_KEY, []),

Expand Down
Loading