useSpace (useViewform) Hook
The useSpace hook provides access to the core boundary explore functionality and state within your React components. It allows you to interact with and control various aspects of the 3D tour viewer.
Accessing the Store
There are two main ways to access the store in your application:
1. Using SpaceProvider (formerly ViewformProvider)
You can wrap your parent component with the SpaceProvider to provide store access throughout your application:
import { SpaceProvider } from './store';
function App() {
return (
<SpaceProvider>
<YourComponents />
</SpaceProvider>
);
}This makes it really easy to access the useSpace hook in a parent component. A child Space component will automatically pick up the parent context.
2. Using the Space Component Structure
The preferred method is to use the Space component’s built-in structure, which automatically provides store context to its children through designated containers:
import { Space, Overlay, Scene } from '@your-library/components';
function App() {
return (
<Space tourId="example-tour">
<Overlay>
{/* UI Components with store access */}
<Loading />
<FloorplanModal />
<TagModal />
<Toolbar />
</Overlay>
<Scene>
{/* Three.js/Fiber Components with store access */}
<Tags />
<Hotspots />
<Measurements />
{/* Add any other fiber content here that needs store access */}
</Scene>
</Space>
);
}Component Structure Overview
Space (provides store context)
├── Overlay (for UI components)
│ ├── Loading
│ ├── FloorplanModal
│ ├── TagModal
│ └── Toolbar
└── Scene (for 3D/Fiber components)
├── Tags
├── Hotspots
└── MeasurementsAccessing Store in Components
Components placed within either Overlay or Scene automatically have access to the store:
// Component in Overlay
function Toolbar() {
const currentCamera = useSpace(state => state.currentCamera);
return <div>{/* UI implementation */}</div>;
}
// Component in Scene
function Hotspots() {
const hotspots = useSpace(state => state.hotspots);
return <>{/* Three.js/Fiber implementation */}</>;
}Best Practices
-
Component Placement
- Place UI components in
<Overlay> - Place Three.js/Fiber components in
<Scene> - Both containers provide full store access
- Place UI components in
-
Store Access Pattern
// In any child component of Overlay or Scene function YourComponent() { const { someState, someAction } = useSpace( state => ({ someState: state.someState, someAction: state.someAction }), shallow ); return <>{/* Your implementation */}</>; } -
Context Usage
- All components within Space have access to the store context
- No need for additional context providers
- Store is automatically initialized with the provided tourId
Example: Complete Implementation
import { Space, Overlay, Scene } from '@your-library/components';
import { useSpace } from './store';
function App() {
return (
<Space tourId="example-tour">
<Overlay>
<CustomUI />
</Overlay>
<Scene>
<Custom3DContent />
</Scene>
</Space>
);
}
// UI Component Example
function CustomUI() {
const { loading, currentCamera } = useSpace(
state => ({
loading: state.loading,
currentCamera: state.currentCamera
}),
shallow
);
return (
<div>
{loading ? 'Loading...' : `Current Camera: ${currentCamera?.id}`}
</div>
);
}
// Scene Component Example
function Custom3DContent() {
const cameras = useSpace(state => state.cameras);
return (
<group>
{cameras.map((camera) => (
<Sphere
key={camera.id}
position={[camera.position.x, camera.position.y, camera.position.z]}
args={[0.1, 16, 16]} />
))}
</group>
);
}This structure ensures proper separation of concerns while maintaining full access to the store’s functionality throughout your application.
Store Slices
TourSlice
Manages the state and operations for virtual tours.
Interface
export interface TourSlice {
id?: string;
instanceId?: string;
loading: boolean;
tour?: IViewformTour;
initializeTour: (tour: IViewformTour, sasInfo: ISasInfo) => void;
updateTour: (tour: IViewformTour, sasInfo: ISasInfo) => void;
refresh: () => void;
destroy: () => void;
}| Property/Method | Type | Description |
|---|---|---|
| id | string | Optional unique identifier for the tour |
| instanceId | string | Optional instance identifier for the tour |
| loading | boolean | Indicates if the tour is currently loading |
| tour | IViewformTour | Current tour data |
| initializeTour | function | Initializes a new tour with given tour data and SAS information |
| updateTour | function | Updates an existing tour with new data |
| refresh | function | Refreshes the current tour state |
| destroy | function | Cleans up and removes the current tour |
CameraSlice
Handles camera management and state for 3D viewing.
Interface
export interface CameraSlice {
cameras: ICameraDetails[];
currentResolution: Resolution;
currentCamera?: ICameraDetails;
currentCameraIndex?: number;
cubeMapLoader?: CubeMapLoader;
setCurrentCamera: (camera: ICameraDetails, index: number) => void;
setCubeMapLoader: (cubeMapLoader: CubeMapLoader) => void;
}| Property/Method | Type | Description |
|---|---|---|
| cameras | ICameraDetails[] | Array of available cameras |
| currentResolution | Resolution | Current resolution settings |
| currentCamera | ICameraDetails | Currently active camera |
| currentCameraIndex | number | Index of current camera in cameras array |
| cubeMapLoader | CubeMapLoader | Loader for cubemap textures |
| setCurrentCamera | function | Sets the active camera and its index |
| setCubeMapLoader | function | Sets the cubemap loader instance |
ModelSlice
Manages 3D model state and operations.
Interface
export interface ModelSlice {
modelCache: any;
currentModels: IModelDetailsRich[];
modelRef: Object3D | undefined;
isSyntheticModel: boolean;
setModelRef: (ref: Object3D, synthetic: boolean) => void;
getModelDownloadUrl: (model: ModelDetails) => string;
downloadModel: (model: ModelDetails) => Promise<GLTF>;
modelViewType?: ModelViewType;
setModelViewType: (m: ModelViewType) => void;
}| Property/Method | Type | Description |
|---|---|---|
| modelCache | any | Cache storage for loaded models |
| currentModels | IModelDetailsRich[] | Currently active models |
| modelRef | Object3D | Reference to the current 3D model |
| isSyntheticModel | boolean | Indicates if current model is synthetic |
| setModelRef | function | Sets the model reference and synthetic flag |
| getModelDownloadUrl | function | Generates download URL for a model |
| downloadModel | function | Downloads a model asynchronously |
| modelViewType | ModelViewType | Current model view type |
| setModelViewType | function | Sets the model view type |
ControlsSlice
Manages user controls and camera navigation.
Interface
export interface ControlsSlice {
timeline: Timeline;
animating: boolean;
jumpMode: JumpMode;
canZoom: boolean;
zoom: number;
canvas?: HTMLCanvasElement;
camera?: PerspectiveCamera;
frame?: HTMLElement;
controlsEnabled: boolean;
canTilt: boolean;
canRotate: boolean;
currentRotation: Euler;
allowJumpYawRotationCorrection?: boolean;
allowJumpPitchRotationCorrection?: boolean;
controls: any;
}| Property/Method | Type | Description |
|---|---|---|
| timeline | Timeline | Controls animation timeline |
| animating | boolean | Current animation state |
| jumpMode | JumpMode | Current jump navigation mode |
| canZoom | boolean | Whether zoom is enabled |
| zoom | number | Current zoom level |
| canvas | HTMLCanvasElement | Reference to canvas element |
| camera | PerspectiveCamera | Reference to camera |
| controlsEnabled | boolean | Whether controls are enabled |
| canTilt | boolean | Whether tilt is enabled |
| canRotate | boolean | Whether rotation is enabled |
| currentRotation | Euler | Current camera rotation |
| controls | any | Reference to controls instance |
Plus numerous navigation methods:
- setJumpMode
- setAnimating
- enableControls
- disableControls
- goToTag
- goToCurrent
- goToVersion
- goToCamera
- goToGroup
- goToNearestCameraWithinFov
- goToNearestCameraWithDirection
- goBack
AnnotationSlice
Manages annotations and tags in the 3D space.
Interface
export interface AnnotationSlice {
tags: IAnnotationDetails[];
currentTag?: IAnnotationDetails;
setCurrentTag: (tag?: IAnnotationDetails) => void;
}| Property/Method | Type | Description |
|---|---|---|
| tags | IAnnotationDetails[] | Array of annotation tags |
| currentTag | IAnnotationDetails | Currently selected tag |
| setCurrentTag | function | Sets the current active tag |
ImageVersionSlice
Manages different versions of images.
Interface
export interface ImageVersionSlice {
imageVersion?: IImageVersion;
imageVersionIndex: number;
getSelectableImageVersions: () => IImageVersion[];
setImageVersion: (index: number) => void;
setImageVersionByName: (name: string) => void;
setImageVersionById: (id: string) => void;
}| Property/Method | Type | Description |
|---|---|---|
| imageVersion | IImageVersion | Current image version |
| imageVersionIndex | number | Index of current version |
| getSelectableImageVersions | function | Returns available image versions |
| setImageVersion | function | Sets version by index |
| setImageVersionByName | function | Sets version by name |
| setImageVersionById | function | Sets version by ID |
MeasurementSlice
Manages measurement functionality in 3D space.
Interface
export interface MeasurementSlice {
canMeasure: boolean;
points: Vector3[];
measurements: Measurement[];
addMeasurement: (point: Vector3) => void;
clearMeasurements: () => void;
enableMeasurement: (enable: boolean) => void;
addMeasurements: (data: Measurement[]) => void;
}| Property/Method | Type | Description |
|---|---|---|
| canMeasure | boolean | Whether measurement mode is enabled |
| points | Vector3[] | Array of measurement points |
| measurements | Measurement[] | Array of completed measurements |
| addMeasurement | function | Adds a new measurement point |
| clearMeasurements | function | Clears all measurements |
| enableMeasurement | function | Toggles measurement mode |
| addMeasurements | function | Adds multiple measurements at once |
HotspotSlice
Manages interactive hotspots in the 3D environment.
Interface
export interface HotspotSlice {
hotspots: IHotspotDetails[];
currentHotspot?: IHotspotDetails;
initializeHotspots: (tour: IViewformTour, shape: HotspotShape, animation: HotspotAnimation, color: string) => void;
setCurrentHotspot: (camera: ICameraDetails) => void;
}| Property/Method | Type | Description |
|---|---|---|
| hotspots | IHotspotDetails[] | Array of available hotspots |
| currentHotspot | IHotspotDetails | Currently active hotspot |
| initializeHotspots | function | Sets up hotspots with specified parameters |
| setCurrentHotspot | function | Sets the active hotspot |
EventSlice
Manages event handling and callbacks.
Interface
export interface EventSlice {
listeners: Listeners;
addListeners: (listeners: Partial<Listeners>) => void;
emitRotation: (e: Euler) => void;
emitRotationStart: (e: Euler) => void;
emitRotationEnd: (e: Euler) => void;
emitJumpStart: (current: ICameraDetails, next: ICameraDetails) => void;
emitJumpEnd: (current: ICameraDetails) => void;
emitKeyframeStart: (current: Keyframe) => void;
emitFov: (v: number) => void;
}| Property/Method | Type | Description |
|---|---|---|
| listeners | Listeners | Collection of event listeners |
| addListeners | function | Registers new event listeners |
| emitRotation | function | Triggers rotation event |
| emitRotationStart | function | Triggers rotation start event |
| emitRotationEnd | function | Triggers rotation end event |
| emitJumpStart | function | Triggers camera jump start event |
| emitJumpEnd | function | Triggers camera jump end event |
| emitKeyframeStart | function | Triggers keyframe start event |
| emitFov | function | Triggers FOV change event |
ResolutionSlice
Manages resolution settings and quality control.
Interface
export interface ResolutionSlice {
loadDurations: ResolutionLoadDuration[];
canvasPerformance: number;
networkQuality: number;
isMobileDevice: boolean;
resolution: Resolution;
minResolution: Resolution;
selectedResolution?: Resolution;
currentLoadedResolution?: Resolution;
setCanvasPerformance: (value: number) => void;
setIsMobileDevice: (value: boolean) => void;
setNetworkQuality: (value: number) => void;
computeResolution: () => void;
recordCameraLoad: (duration: number, resolution: Resolution) => void;
setSelectedResolution: (r?: Resolution) => void;
}| Property/Method | Type | Description |
|---|---|---|
| loadDurations | ResolutionLoadDuration[] | History of load times |
| canvasPerformance | number | Canvas performance metric |
| networkQuality | number | Network quality metric |
| isMobileDevice | boolean | Device type indicator |
| resolution | Resolution | Current resolution |
| minResolution | Resolution | Minimum allowed resolution |
| selectedResolution | Resolution | User-selected resolution |
| setCanvasPerformance | function | Updates canvas performance value |
| computeResolution | function | Calculates optimal resolution |
| recordCameraLoad | function | Records camera load performance |
LoadingSlice
Manages loading states.
Interface
export interface LoadingSlice {
firstCameraLoaded: boolean;
modelLoaded: boolean;
modelLoading: boolean;
setFirstCameraLoaded: (v: boolean) => void;
setModelLoaded: (v: boolean) => void;
setModelLoading: (v: boolean) => void;
}| Property/Method | Type | Description |
|---|---|---|
| firstCameraLoaded | boolean | Initial camera load state |
| modelLoaded | boolean | Model load completion state |
| modelLoading | boolean | Current model loading state |
| setFirstCameraLoaded | function | Updates first camera load state |
| setModelLoaded | function | Updates model load state |
| setModelLoading | function | Updates model loading state |
CursorSlice
Manages cursor position and interaction.
Interface
export interface CursorSlice {
cursorPosition: Vector3;
currsorLookAt: Vector3;
closestCamera?: CameraDetails;
setCursor: (position: Vector3, lookAt: Vector3) => void;
}| Property/Method | Type | Description |
|---|---|---|
| cursorPosition | Vector3 | Current cursor position |
| currsorLookAt | Vector3 | Cursor look-at point |
| closestCamera | CameraDetails | Nearest camera to cursor |
| setCursor | function | Updates cursor position and look-at |
AttachmentSlice
Manages file attachments.
Interface
export interface AttachmentSlice {
getAttachmentDownloadUrl: (attachment: AttachmentDetails) => string;
}| Property/Method | Type | Description |
|---|---|---|
| getAttachmentDownloadUrl | function | Generates download URL for attachments |
FlagSlice
Manages UI flags and states.
Interface
export interface FlagSlice extends FlagSliceProps {
setUIFlag: (p: Partial<FlagSliceProps>) => void;
}| Property/Method | Type | Description |
|---|---|---|
| openTagModal | boolean | Tag modal visibility state |
| openFloorplanModal | boolean | Floorplan modal visibility state |
| selectingResolution | boolean | Resolution selection state |
| debug | boolean | Debug mode state |
| setUIFlag | function | Updates UI flag states |
GroupSlice
Manages groups of elements.
Interface
export interface GroupSlice {
groups: IGroupDetails[];
currentGroups?: GroupDetails[];
setCurrentGroups: (groups?: GroupDetails[]) => void;
}| Property/Method | Type | Description |
|---|---|---|
| groups | IGroupDetails[] | Available groups |
| currentGroups | GroupDetails[] | Currently selected groups |
| setCurrentGroups | function | Updates current group selection |
ConfiguratorSlice
Manages configuration settings and themes.
Interface
export interface ConfiguratorSlice {
configurators: ConfiguratorDetails[];
currentConfigurator?: ConfiguratorDetails;
currentThemes: ThemeDetails[];
initilizeConfigurators: (configurators: ConfiguratorDetails[], themes: ThemeDetails[]) => void;
setCurrentTheme: (theme: ThemeDetails) => string[];
setCurrentThemesByVersion: (version: ImageVersion) => void;
setCurrentConfigurator: (c?: ConfiguratorDetails) => void;
}| Property/Method | Type | Description |
|---|---|---|
| configurators | ConfiguratorDetails[] | Available configurators |
| currentConfigurator | ConfiguratorDetails | Active configurator |
| currentThemes | ThemeDetails[] | Current theme settings |
| initilizeConfigurators | function | Sets up configurators and themes |
| setCurrentTheme | function | Updates current theme |
| setCurrentThemesByVersion | function | Sets themes based on version |
| setCurrentConfigurator | function | Updates current configurator |
Performance Optimization
Using Shallow Equality
Zustand’s shallow comparison helps prevent unnecessary re-renders when working with objects or arrays. Here’s how to use it effectively:
// BAD: Will re-render on every store update
const { camera, model } = useSpace(state => ({
camera: state.currentCamera,
model: state.currentModels
}));
// GOOD: Only re-renders when these specific values change
const { camera, model } = useSpace(
state => ({
camera: state.currentCamera,
model: state.currentModels
}),
shallow
);Selective State Updates
Only select the specific state you need:
// BAD: Subscribes to entire camera object
const camera = useSpace(state => state.currentCamera);
// GOOD: Subscribe only to needed properties
const cameraId = useSpace(state => state.currentCamera?.id);
const cameraPosition = useSpace(state => state.currentCamera?.position);Memoizing Selectors
For complex selections, memoize your selectors:
// BAD: Creates new selector on every render
function Component() {
const activeCameras = useSpace(state =>
state.cameras.filter(camera => camera.active)
);
}
// GOOD: Memoized selector
const selectActiveCameras = (state) =>
state.cameras.filter(camera => camera.active);
function Component() {
const activeCameras = useSpace(selectActiveCameras);
}Atomic State Updates
Keep your state updates focused and atomic:
// BAD: Updating multiple slices at once
const updateEverything = useSpace(state => ({
setCamera: state.setCurrentCamera,
setModel: state.setModelRef,
setControls: state.setControls
}));
// GOOD: Separate concerns
const setCamera = useSpace(state => state.setCurrentCamera);
const setModel = useSpace(state => state.setModelRef);
const setControls = useSpace(state => state.setControls);