+ {isLoading || isFetching ? (
+
Loading actions...
) : (
diff --git a/web/libs/datamanager/src/hooks/README.md b/web/libs/datamanager/src/hooks/README.md
new file mode 100644
index 000000000000..5c2a559b608c
--- /dev/null
+++ b/web/libs/datamanager/src/hooks/README.md
@@ -0,0 +1,147 @@
+# DataManager Hooks
+
+This directory contains React hooks for the DataManager library, following modern React patterns with TanStack Query for data fetching.
+
+## Available Hooks
+
+### `useActions`
+
+A hook for fetching available actions from the DataManager API using TanStack Query.
+
+#### Features
+
+- **Automatic caching**: Actions are cached for 5 minutes by default
+- **Lazy loading**: Only fetches when enabled (e.g., when dropdown is opened)
+- **Error handling**: Built-in error states
+- **Loading states**: Provides both initial loading and refetching states
+- **Type safety**: Full TypeScript support
+
+#### Usage
+
+```tsx
+import { useActions } from "../hooks/useActions";
+
+function ActionsDropdown({ projectId }) {
+ const {
+ actions,
+ isLoading,
+ isError,
+ error,
+ refetch,
+ isFetching,
+ } = useActions({
+ projectId, // Optional: Used for cache scoping per project
+ enabled: isOpen, // Only fetch when dropdown is opened
+ staleTime: 5 * 60 * 1000, // Optional: 5 minutes
+ cacheTime: 10 * 60 * 1000, // Optional: 10 minutes
+ });
+
+ if (isLoading) return Loading...
;
+ if (isError) return Error: {error.message}
;
+
+ return (
+
+ {actions.map((action) => (
+
+ ))}
+
+ );
+}
+```
+
+#### Parameters
+
+- `options.projectId` (string, optional): Project ID for scoping the query cache. When provided, actions are cached per project, preventing cache conflicts in multi-project scenarios
+- `options.enabled` (boolean, default: `true`): Whether to enable the query
+- `options.staleTime` (number, default: `5 * 60 * 1000`): Time in ms before data is considered stale
+- `options.cacheTime` (number, default: `10 * 60 * 1000`): Time in ms before unused data is garbage collected
+
+#### Return Value
+
+- `actions` (Action[]): Array of available actions
+- `isLoading` (boolean): True on first load
+- `isFetching` (boolean): True whenever data is being fetched
+- `isError` (boolean): True if the query failed
+- `error` (Error): The error object if query failed
+- `refetch` (function): Function to manually refetch the data
+
+### `useDataManagerUsers`
+
+A hook for fetching users from the DataManager API with infinite pagination support.
+
+See `useUsers.ts` for documentation.
+
+### Other Hooks
+
+- `useFirstMountState`: Utility hook to detect first mount
+- `useUpdateEffect`: Effect hook that skips the first render
+
+## Migration from MobX to TanStack Query
+
+The DataManager is gradually migrating from MobX State Tree flows to TanStack Query hooks for better performance, caching, and developer experience.
+
+### Why TanStack Query?
+
+1. **Automatic caching**: Reduces unnecessary API calls
+2. **Better loading states**: Built-in loading, error, and refetching states
+3. **Background refetching**: Keeps data fresh automatically
+4. **Query invalidation**: Easy cache management
+5. **TypeScript support**: Full type safety out of the box
+6. **React best practices**: Follows modern React patterns recommended in project rules
+
+### Coexistence with MobX
+
+The hooks replace the need for MobX flows for data fetching:
+
+- **Old code**: `store.fetchActions()` is now deprecated but kept for backward compatibility
+- **New code**: Should always use `useActions()` hook
+- **Migration complete**: The actions endpoint is now only called via the `useActions` hook, preventing duplicate API calls
+
+### Example Migration
+
+**Before (MobX):**
+```javascript
+useEffect(() => {
+ if (isOpen && actions.length === 0) {
+ setIsLoading(true);
+ store.fetchActions().finally(() => {
+ setIsLoading(false);
+ });
+ }
+}, [isOpen, actions.length, store]);
+```
+
+**After (TanStack Query):**
+```typescript
+const { actions, isLoading, isFetching } = useActions({
+ projectId, // Optional: for cache scoping
+ enabled: isOpen,
+});
+```
+
+## Best Practices
+
+1. **Use lazy loading**: Set `enabled: false` for data that's not immediately needed
+2. **Scope by projectId**: Always pass `projectId` when working with project-specific data to prevent cache conflicts
+3. **Configure cache times**: Adjust `staleTime` and `cacheTime` based on data freshness requirements
+4. **Handle loading states**: Always provide loading UI for better UX
+5. **Handle errors**: Display user-friendly error messages
+6. **Type everything**: Use TypeScript interfaces for type safety
+
+## QueryClient Setup
+
+The QueryClient is configured at the app level in `App.tsx`:
+
+```typescript
+import { QueryClientProvider } from "@tanstack/react-query";
+import { queryClient } from "@humansignal/core/lib/utils/query-client";
+
+
+
+ {/* App content */}
+
+
+```
+
+This ensures all hooks have access to the shared query cache.
+
diff --git a/web/libs/datamanager/src/hooks/useActions.ts b/web/libs/datamanager/src/hooks/useActions.ts
new file mode 100644
index 000000000000..69323d6ba8ca
--- /dev/null
+++ b/web/libs/datamanager/src/hooks/useActions.ts
@@ -0,0 +1,91 @@
+import { useQuery } from "@tanstack/react-query";
+
+// Extend Window interface to include DataManager properties
+declare global {
+ interface Window {
+ DM?: {
+ store?: {
+ apiCall: (method: string, params?: any) => Promise;
+ };
+ apiCall?: (method: string, params?: any) => Promise;
+ };
+ }
+}
+
+interface Action {
+ id: string;
+ title: string;
+ order: number;
+ hidden?: boolean;
+ dialog?: {
+ type?: string;
+ text?: string;
+ form?: any;
+ title?: string;
+ };
+ children?: Action[];
+ disabled?: boolean;
+ disabledReason?: string;
+ isSeparator?: boolean;
+ isTitle?: boolean;
+ callback?: (selection: any, action: Action) => void;
+}
+
+interface UseActionsOptions {
+ projectId?: string;
+ enabled?: boolean;
+ staleTime?: number;
+ cacheTime?: number;
+}
+
+/**
+ * Hook to fetch available actions from the DataManager API
+ * Uses TanStack Query for data fetching and caching
+ *
+ * @param options - Configuration options for the query
+ * @returns Object containing actions data, loading state, error state, and refetch function
+ */
+export const useActions = (options: UseActionsOptions = {}) => {
+ const {
+ enabled = true,
+ staleTime = 5 * 60 * 1000, // 5 minutes
+ cacheTime = 10 * 60 * 1000, // 10 minutes
+ projectId,
+ } = options;
+
+ const queryKey = ["actions", projectId];
+
+ const { data, isLoading, isError, error, refetch, isFetching } = useQuery({
+ queryKey,
+ queryFn: async () => {
+ // Use the correct DataManager API pattern - window.DM is the AppStore
+ const store = window?.DM?.store || window?.DM;
+
+ if (!store) {
+ throw new Error("DataManager store not available");
+ }
+
+ const response = await store.apiCall?.("actions");
+
+ if (!response) {
+ throw new Error("No actions found in response or response is invalid");
+ }
+
+ return response as Action[];
+ },
+ enabled,
+ staleTime,
+ cacheTime,
+ });
+
+ const actions = data ?? [];
+
+ return {
+ actions,
+ isLoading,
+ isError,
+ error,
+ refetch,
+ isFetching,
+ };
+};
diff --git a/web/libs/datamanager/src/stores/AppStore.js b/web/libs/datamanager/src/stores/AppStore.js
index a49ed1a60bd6..6ca6a10fe258 100644
--- a/web/libs/datamanager/src/stores/AppStore.js
+++ b/web/libs/datamanager/src/stores/AppStore.js
@@ -542,14 +542,22 @@ export const AppStore = types
return true;
}),
+ /**
+ * @deprecated Use the useActions hook instead for better caching and performance
+ * This method is kept for backward compatibility but is no longer actively used
+ */
fetchActions: flow(function* () {
- const serverActions = yield self.apiCall("actions");
+ try {
+ const serverActions = yield self.apiCall("actions");
- const actions = (serverActions ?? []).map((action) => {
- return [action, undefined];
- });
+ const actions = (serverActions ?? []).map((action) => {
+ return [action, undefined];
+ });
- self.SDK.updateActions(actions);
+ self.SDK.updateActions(actions);
+ } catch (error) {
+ console.error("Error fetching actions:", error);
+ }
}),
fetchActionForm: flow(function* (actionId) {