StateSyncService
Migrating from StateSyncService to Zustand Stores​
The StateSyncService
has been deprecated in favor of more modern and efficient state management using Zustand stores. This migration guide outlines the reasons for the change and provides step-by-step instructions on how to migrate your extension or mode from using StateSyncService
to Zustand.
Why Migrate?​
The StateSyncService
had limitations:
- Limited Reactivity: Updates weren't always reactive, requiring manual re-renders.
- Lack of Granularity: It stored large chunks of state, hindering performance.
- Complexity: Managing and syncing state across components was cumbersome.
Zustand offers several advantages:
- Lightweight and Fast: Zustand is a minimal and performant state management library.
- Granular Control: Create individual stores for specific data, improving reactivity and performance.
- Simplified API: Easy-to-use hooks for subscribing and updating state.
Migration Steps:​
- Identify State to Migrate: Determine which parts of your extension or mode rely on the
StateSyncService
. Typical examples include:- Viewport Presentations: LUT and position information for viewports.
- Layout State: Custom grid layouts and one-up toggling.
- Synchronizers: State for cross-viewport synchronization.
- UI State: UI-specific settings.
- Replace StateSyncService Usage: In your extension or mode:
- Import Zustand Stores: Import the new stores you created.
- Replace
getState()
andstore()
: Use the Zustand hooks (useStore
,set
,get
) to access and update state in your components. - Handle Presentation IDs: Implement logic for generating and managing presentation IDs within your stores or relevant components. This can involve using unique keys based on viewport options, display sets, and unique indices. See the
presentationUtils.ts
file for example implementations. - Rehydrate State: On mode entry, rehydrate your Zustand stores with any relevant persisted state from localStorage or other storage mechanisms.
- Clear State on Mode Exit: Ensure you clear your Zustand stores appropriately on mode exit to prevent memory leaks.
LutPresentationStore
​
Before (StateSyncService):
const stateSyncService = servicesManager.services.stateSyncService;
const lutPresentationStore = stateSyncService.getState().lutPresentationStore;
const lutPresentation = lutPresentationStore[presentationId];
// ...to update
stateSyncService.store({
lutPresentationStore: {
...lutPresentationStore,
[presentationId]: newLutPresentation,
},
});
After (Zustand):
import { useLutPresentationStore } from '../stores/useLutPresentationStore';
const { lutPresentationStore, setLutPresentation } = useLutPresentationStore();
const lutPresentation = lutPresentationStore[presentationId];
// ...to update
setLutPresentation(presentationId, newLutPresentation);
The getPresentationId
for lutPresentationStore
was previously registered in platform/core
. Now, the Zustand store provides this functionality.
// Fetch getPresentationId functions from respective Zustand stores
const { getPresentationId: getLutPresentationId } = useLutPresentationStore.getState();
// Register presentation id providers
viewportGridService.addPresentationIdProvider('lutPresentationId', getLutPresentationId);
PositionPresentationStore
​
Before (StateSyncService):
const stateSyncService = servicesManager.services.stateSyncService;
const positionPresentationStore = stateSyncService.getState().positionPresentationStore;
const positionPresentation = positionPresentationStore[presentationId];
// ...to update
stateSyncService.store({
positionPresentationStore: {
...positionPresentationStore,
[presentationId]: newPositionPresentation,
},
});
After (Zustand):
import { usePositionPresentationStore } from '../stores/usePositionPresentationStore';
const { positionPresentationStore, setPositionPresentation } = usePositionPresentationStore();
const positionPresentation = positionPresentationStore[presentationId];
// ...to update
setPositionPresentation(presentationId, newPositionPresentation);
Similar to lutPresentationId, the PositionPresentationId is also registered from outside
const { getPresentationId: getPositionPresentationId } = usePositionPresentationStore.getState();
// register presentation id providers
viewportGridService.addPresentationIdProvider(
'positionPresentationId',
getPositionPresentationId
);
ViewportGridStore
​
Before (StateSyncService):
const stateSyncService = servicesManager.services.stateSyncService;
const viewportGridStore = stateSyncService.getState().viewportGridStore;
const gridState = viewportGridStore[storeId];
// ...to update
stateSyncService.store({
viewportGridStore: {
...viewportGridStore,
[storeId]: newGridState,
},
});
After (Zustand):
import { useViewportGridStore } from '../stores/useViewportGridStore';
const { viewportGridState, setViewportGridState } = useViewportGridStore();
const gridState = viewportGridState[storeId];
// ...to update
setViewportGridState(storeId, newGridState);
DisplaySetSelectorStore
​
Before (StateSyncService):
const stateSyncService = servicesManager.services.stateSyncService;
const displaySetSelectorMap = stateSyncService.getState().displaySetSelectorMap;
const displaySetUID = displaySetSelectorMap[selectorKey];
// ...to update
stateSyncService.store({
displaySetSelectorMap: {
...displaySetSelectorMap,
[selectorKey]: newDisplaySetUID,
},
});
After (Zustand):
import { useDisplaySetSelectorStore } from '../stores/useDisplaySetSelectorStore';
const { displaySetSelectorMap, setDisplaySetSelector } = useDisplaySetSelectorStore();
const displaySetUID = displaySetSelectorMap[selectorKey];
// ...to update
setDisplaySetSelector(selectorKey, newDisplaySetUID);
HangingProtocolStageIndexStore
​
Before (StateSyncService):
const stateSyncService = servicesManager.services.stateSyncService;
const hangingProtocolStageIndexMap = stateSyncService.getState().hangingProtocolStageIndexMap;
const hpInfo = hangingProtocolStageIndexMap[cacheId];
// ...to update
stateSyncService.store({
hangingProtocolStageIndexMap: {
...hangingProtocolStageIndexMap,
[cacheId]: newHpInfo,
},
});
After (Zustand):
import { useHangingProtocolStageIndexStore } from '../stores/useHangingProtocolStageIndexStore';
const { hangingProtocolStageIndexMap, setHangingProtocolStageIndex } = useHangingProtocolStageIndexStore();
const hpInfo = hangingProtocolStageIndexMap[cacheId];
// ...to update
setHangingProtocolStageIndex(cacheId, newHpInfo);
ToggleHangingProtocolStore
​
Before (StateSyncService):
const stateSyncService = servicesManager.services.stateSyncService;
const toggleHangingProtocol = stateSyncService.getState().toggleHangingProtocol;
const previousHpInfo = toggleHangingProtocol[storedHanging];
// ...to update
stateSyncService.store({
toggleHangingProtocol: {
...toggleHangingProtocol,
[storedHanging]: newHpInfo,
},
});
After (Zustand):
import { useToggleHangingProtocolStore } from '../stores/useToggleHangingProtocolStore';
const { toggleHangingProtocol, setToggleHangingProtocol } = useToggleHangingProtocolStore();
const previousHpInfo = toggleHangingProtocol[storedHanging];
// ...to update
setToggleHangingProtocol(storedHanging, newHpInfo);
ToggleOneUpViewportGridStore
​
Before (StateSyncService):
const stateSyncService = servicesManager.services.stateSyncService;
const toggleOneUpViewportGridStore = stateSyncService.getState().toggleOneUpViewportGridStore;
const previousGridState = toggleOneUpViewportGridStore.layout; // Assuming layout was a property
// ...to update
stateSyncService.store({
toggleOneUpViewportGridStore: newGridState,
});
After (Zustand):
import { useToggleOneUpViewportGridStore } from '../stores/useToggleOneUpViewportGridStore';
const { toggleOneUpViewportGridStore, setToggleOneUpViewportGridStore } = useToggleOneUpViewportGridStore();
const previousGridState = toggleOneUpViewportGridStore; // No nested layout property
// ...to update
setToggleOneUpViewportGridStore(newGridState);
UIStateStore
​
Before (StateSyncService):
const stateSyncService = servicesManager.services.stateSyncService;
const uiState = stateSyncService.getState().uiStateStore[someUIKey];
// ...to update
stateSyncService.store({
uiStateStore: {
...stateSyncService.getState().uiStateStore,
[someUIKey]: newUIState,
},
});
After (Zustand):
import { useUIStateStore } from '../stores/useUIStateStore';
const { uiState, setUIState } = useUIStateStore();
const currentUIState = uiState[someUIKey];
// ...to update
setUIState(someUIKey, newUIState);
ViewportsByPositionStore
​
Before (StateSyncService):
const stateSyncService = servicesManager.services.stateSyncService;
const viewportsByPosition = stateSyncService.getState().viewportsByPosition;
const cachedViewport = viewportsByPosition[positionId];
// ...to update
stateSyncService.store({
viewportsByPosition: {
...viewportsByPosition,
[positionId]: newViewport,
},
});
After (Zustand):
import { useViewportsByPositionStore } from '../stores/useViewportsByPositionStore';
const { viewportsByPosition, setViewportsByPosition } = useViewportsByPositionStore();
const cachedViewport = viewportsByPosition[positionId];
// ...to update
setViewportsByPosition(positionId, newViewport);
SegmentationPresentationStore
​
After (Zustand):
import { useSegmentationPresentationStore } from '../stores/useSegmentationPresentationStore';
const { segmentationPresentationStore, setSegmentationPresentation } =
useSegmentationPresentationStore();
// ...to update
setSegmentationPresentation(presentationId, newSegmentationPresentation);
// You likely have functions within the store like:
// addSegmentationPresentation
// setSegmentationVisibility
// etc.