Utility hooks
Permissions, generic React helpers, mining-domain transforms, and TanStack Query re-exports
@tetherto/mdk-react-adapter + @tetherto/mdk-react-devkit/foundation
Generic React helpers, mining-domain transforms, permission checks, device/IP utilities, and a curated set of TanStack Query re-exports. Use these alongside State hooks to wire app concerns (permissions, devices, viewport, formatting) without depending on MDK styled components.
At a glance
| Sub-group | Hooks |
|---|---|
| Permissions | useCheckPerm, useHasPerms, useIsFeatureEditingEnabled |
| Generic React | useLocalStorage, useKeyDown, useWindowSize, usePlatform, useDeviceResolution, useBeepSound, usePagination, useSubtractedTime, useTimezoneFormatter |
| Device and IP | useStaticMinerIpAssignment, useMinerDuplicateValidation, usePduViewer |
| Domain transforms | useCostSummary, useHashBalance, useSubsidyFees, useUpdateExistedActions |
| TanStack Query re-exports | useQuery, useMutation, useQueries, useInfiniteQuery, useIsFetching, useIsMutating, useQueryClient |
Prerequisites
- Wrap your app in
<MdkProvider> - Complete the @tetherto/mdk-react-devkit installation and add the dependency
Import
import {
useBeepSound,
useCheckPerm,
useDeviceResolution,
useHasPerms,
useIsFeatureEditingEnabled,
useKeyDown,
useLocalStorage,
useMinerDuplicateValidation,
usePagination,
usePduViewer,
usePlatform,
useStaticMinerIpAssignment,
useSubtractedTime,
useTimezoneFormatter,
useWindowSize,
} from '@tetherto/mdk-react-adapter'
import {
useCostSummary,
useHashBalance,
useSubsidyFees,
useUpdateExistedActions,
} from '@tetherto/mdk-react-devkit/foundation'Permissions
useCheckPerm
@tetherto/mdk-react-adapter
Check if the current user has a specific permission. Reads permissions from the adapter authStore. Prefer this over useHasPerms for single-permission gates.
import { useCheckPerm } from '@tetherto/mdk-react-adapter'Example
function EditDevicesButton() {
const canEdit = useCheckPerm('devices:edit')
return canEdit ? <Button>Edit devices</Button> : null
}useHasPerms
@tetherto/mdk-react-adapter
Returns a callable that accepts a permission request — a string, a string array (first match wins), or a check object. Reads from the adapter authStore.
import { useHasPerms } from '@tetherto/mdk-react-adapter'Example
function ContextMenu({ device }) {
const hasPerms = useHasPerms()
return (
<Menu>
{hasPerms(['devices:edit', 'devices:admin']) && <MenuItem>Edit</MenuItem>}
{hasPerms('devices:delete') && <MenuItem>Delete</MenuItem>}
</Menu>
)
}useIsFeatureEditingEnabled
@tetherto/mdk-react-adapter
Returns whether the current user has the features capability to edit feature flags.
import { useIsFeatureEditingEnabled } from '@tetherto/mdk-react-adapter'Example
function FeatureFlagRow({ flag }) {
const canEdit = useIsFeatureEditingEnabled()
return <Switch disabled={!canEdit} checked={flag.enabled} />
}Generic React
useLocalStorage
@tetherto/mdk-react-adapter
Type-safe localStorage access with cross-tab synchronization. Returns [value, setValue, removeValue] and stays in sync across browser tabs via the storage event.
import { useLocalStorage } from '@tetherto/mdk-react-adapter'Example
function ThemeToggle() {
const [theme, setTheme] = useLocalStorage<'light' | 'dark'>('app:theme', 'light')
return <Button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle</Button>
}useKeyDown
@tetherto/mdk-react-adapter
Track whether a specific keyboard key is currently pressed. Attaches global keydown/keyup listeners. Useful for modifier-key interactions like shift-click multi-select.
import { useKeyDown } from '@tetherto/mdk-react-adapter'Example
function MinerGrid({ miners }) {
const shiftHeld = useKeyDown('Shift')
return miners.map((m) => (
<MinerCard key={m.id} miner={m} onClick={() => select(m, { range: shiftHeld })} />
))
}useWindowSize
@tetherto/mdk-react-adapter
Track window width and height, refreshing on resize. Returns { windowWidth, windowHeight }. Use useDeviceResolution when you only need a device-class branch rather than raw pixels.
import { useWindowSize } from '@tetherto/mdk-react-adapter'Example
function ResponsiveChart() {
const { windowWidth } = useWindowSize()
return <LineChart width={Math.min(windowWidth - 32, 960)} />
}usePlatform
@tetherto/mdk-react-adapter
Detect the host platform (iOS, Android, Mac, Windows, Linux) from the user agent. Returns the value matching the exported OS_TYPES constant; pair with detectPlatform for one-off checks outside React.
import { usePlatform } from '@tetherto/mdk-react-adapter'Example
function PlatformBadge() {
const platform = usePlatform()
return <span>Running on {platform}</span>
}useDeviceResolution
@tetherto/mdk-react-adapter
Map window width to a device class (mobile, tablet, desktop) using the shared BREAKPOINTS constant. Cheaper than re-reading pixels in every render.
import { useDeviceResolution } from '@tetherto/mdk-react-adapter'Example
function Layout({ children }) {
const device = useDeviceResolution()
return device === 'mobile' ? <Stack>{children}</Stack> : <SidebarLayout>{children}</SidebarLayout>
}useBeepSound
@tetherto/mdk-react-adapter
Play a repeating beep when isAllowed is true. Configurable volume and interval (delayMs). The alarm is synthesised via the Web Audio API — no audio asset is bundled or fetched. Designed for audible critical alerts (overheating containers, equipment failure).
import { useBeepSound } from '@tetherto/mdk-react-adapter'Example
function CriticalAlertChime({ active }) {
useBeepSound({ isAllowed: active, volume: 0.6, delayMs: 1500 })
return null
}usePagination
@tetherto/mdk-react-adapter
Manage pagination state and produce { limit, offset } query arguments for API calls. Returns state shaped for the devkit <Pagination> component plus helpers to change page, page size, and total count.
import { usePagination } from '@tetherto/mdk-react-adapter'Options
| Option | Status | Type | Default | Description |
|---|---|---|---|---|
current | Optional | number | 1 | Initial 1-indexed page |
pageSize | Optional | number | 20 | Initial rows per page |
total | Optional | number | none | Initial total row count |
showSizeChanger | Optional | boolean | true | Whether the UI exposes a page-size selector |
Returns
| Member | Type | Description |
|---|---|---|
pagination | PaginationState | { current, pageSize, showSizeChanger, total } — spread onto <Pagination> |
queryArgs | { limit, offset } | Query arguments for API calls |
handleChange | function | (page, pageSize) => void — pass to <Pagination onChange={…}> |
setPagination | function | Imperative pagination state update |
reset | function | Reset to initial state |
setTotal | function | Update total row count |
hideNextPage | function | Hide next page when the current page has fewer rows than pageSize |
Example
function MinerList() {
const { pagination, queryArgs, handleChange } = usePagination({ current: 1, pageSize: 25 })
const { data } = useQuery(['miners', queryArgs], () => fetchMiners(queryArgs))
return (
<>
<Table rows={data?.rows ?? []} />
<Pagination {...pagination} onChange={handleChange} />
</>
)
}useSubtractedTime
@tetherto/mdk-react-adapter
Returns Date.now() - diff, refreshing on a fixed interval (default 5s). Useful for "synced N seconds ago" labels without forcing tree-wide re-renders.
import { useSubtractedTime } from '@tetherto/mdk-react-adapter'Example
function LastSyncedLabel({ lastSyncOffsetMs }) {
const now = useSubtractedTime(lastSyncOffsetMs)
return <small>Synced {formatDistanceToNow(now)} ago</small>
}useTimezoneFormatter
@tetherto/mdk-react-adapter
Read the app timezone from the adapter timezoneStore and format dates for display. Use when foundation components or app code need timestamps in the operator-selected timezone (alerts, pool manager, dashboard widgets).
import { useTimezoneFormatter } from '@tetherto/mdk-react-adapter'Example
function AlertTimestamp({ raisedAt }) {
const { getFormattedDate } = useTimezoneFormatter()
return <time>{getFormattedDate(raisedAt)}</time>
}Device and IP
useStaticMinerIpAssignment
@tetherto/mdk-react-adapter
Derive the static IP address a miner should receive based on its physical position (container number + socket coordinates). Returns an empty string when the feature is disabled or the socket is incomplete.
import { useStaticMinerIpAssignment } from '@tetherto/mdk-react-adapter'Example
function MinerIpField({ containerInfo, socket, pdu }) {
const { minerIp } = useStaticMinerIpAssignment({ containerInfo, socket, pdu })
return <Input value={minerIp} placeholder="Auto-assigned IP" readOnly />
}useMinerDuplicateValidation
@tetherto/mdk-react-adapter
Async validation hook that flags duplicate miners against the device inventory (MAC, serial, IP, human-facing code). The public surface is stable while the backing implementation evolves.
import { useMinerDuplicateValidation } from '@tetherto/mdk-react-adapter'Example
function MinerForm({ socket, onSubmit }) {
const { checkDuplicate, duplicateError, isDuplicateCheckLoading } = useMinerDuplicateValidation()
const handleSubmit = async (draft) => {
const isDuplicate = await checkDuplicate(socket, draft)
if (!isDuplicate) onSubmit(draft)
}
return (
<>
{duplicateError && <p>Duplicate miner detected.</p>}
<MinerEditor loading={isDuplicateCheckLoading} onSubmit={handleSubmit} />
</>
)
}usePduViewer
@tetherto/mdk-react-adapter
Pan/zoom controller for the PDU floor-plan viewer. Wraps react-zoom-pan-pinch with viewport-aware reset logic and a debounced "back to content" indicator that appears when the user pans the layout off-screen.
import { usePduViewer } from '@tetherto/mdk-react-adapter'Example
import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch'
function PduFloorPlan() {
const { onViewerInit, showBackToContent, handleBackToContent } = usePduViewer()
return (
<>
<TransformWrapper onInit={onViewerInit}>
<TransformComponent>{/* …diagram… */}</TransformComponent>
</TransformWrapper>
{showBackToContent && <Button onClick={handleBackToContent}>Back to content</Button>}
</>
)
}Domain transforms
useCostSummary
@tetherto/mdk-react-devkit/foundation
Base hook for the cost-summary reporting page (single-site mode).
import { useCostSummary } from '@tetherto/mdk-react-devkit/foundation'Example
import { useCostSummary } from '@tetherto/mdk-react-devkit/foundation'
// Wire your query result in; consume queryParams to drive the fetch
function CostSummaryPage({ query }) {
const { datePicker, queryParams, isLoading, error } = useCostSummary({ query })
// Pass queryParams to your data-fetching layer whenever the date range changes
// e.g. useGetCostSummaryQuery(queryParams, { skip: !queryParams })
return (
<div>
{datePicker}
{isLoading && <p>Loading…</p>}
{error && <p role="alert">Failed to load cost data</p>}
</div>
)
}useHashBalance
@tetherto/mdk-react-devkit/foundation
Derives hash-balance metrics and chart datasets from finance log entries for the active date range, currency, and timeframe type. Used by hash balance panels.
import { useHashBalance } from '@tetherto/mdk-react-devkit/foundation'Example
import { useHashBalance } from '@tetherto/mdk-react-devkit/foundation'
function HashBalancePanel({ data, log, currency, dateRange }) {
const {
siteHashRevenueChartData,
networkHashpriceChartData,
combinedCostChartData,
} = useHashBalance({ data, log, currency, dateRange })
return (
<div>
{/* Pass chart datasets to your BarChart components */}
<pre>{JSON.stringify(siteHashRevenueChartData?.labels, null, 2)}</pre>
<pre>{JSON.stringify(networkHashpriceChartData?.labels, null, 2)}</pre>
<pre>{JSON.stringify(combinedCostChartData?.labels, null, 2)}</pre>
</div>
)
}useSubsidyFees
@tetherto/mdk-react-devkit/foundation
Aggregates raw subsidy-fee log entries into chart-ready datasets keyed by the active period type (day / week / month / year) and surfaces a summary for the matching reporting widgets. Used by `Subsid…
import { useSubsidyFees } from '@tetherto/mdk-react-devkit/foundation'Example
import { useSubsidyFees } from '@tetherto/mdk-react-devkit/foundation'
function SubsidyFeesPanel({ data, dateRange }) {
const { summary, subsidyFeesChartData, averageFeesChartData, isEmpty } = useSubsidyFees({
data,
dateRange,
})
if (isEmpty) return <p>No subsidy fee data for this period.</p>
return (
<div>
<p>Total fees: {(summary as any)?.total ?? '—'}</p>
{/* Pass chart datasets to your BarChart components */}
<pre>{JSON.stringify(subsidyFeesChartData?.labels, null, 2)}</pre>
<pre>{JSON.stringify(averageFeesChartData?.labels, null, 2)}</pre>
</div>
)
}useUpdateExistedActions
@tetherto/mdk-react-devkit/foundation
Mutation hook that updates only the changed fields of an existing action record.
import { useUpdateExistedActions } from '@tetherto/mdk-react-devkit/foundation'Example
import { useUpdateExistedActions } from '@tetherto/mdk-react-devkit/foundation'
import { Button } from '@tetherto/mdk-react-devkit/core'
function DeviceActionBar({ actionType, pendingSubmissions, selectedDevices }) {
const { updateExistedActions } = useUpdateExistedActions()
const handleApply = () => {
updateExistedActions({ actionType, pendingSubmissions, selectedDevices })
}
return (
<Button onClick={handleApply} disabled={selectedDevices.length === 0}>
Apply to selected ({selectedDevices.length})
</Button>
)
}TanStack Query re-exports
The adapter re-exports a curated set of TanStack Query hooks so you can import data-fetching primitives from a single package alongside MDK helpers. The re-exports are unmodified — refer to the upstream TanStack Query documentation for full API details.
| Hook | TanStack docs |
|---|---|
useQuery | TanStack useQuery |
useMutation | TanStack useMutation |
useQueries | TanStack useQueries |
useInfiniteQuery | TanStack useInfiniteQuery |
useIsFetching | TanStack useIsFetching |
useIsMutating | TanStack useIsMutating |
useQueryClient | TanStack useQueryClient |