MDK Logo

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-groupHooks
PermissionsuseCheckPerm, useHasPerms, useIsFeatureEditingEnabled
Generic ReactuseLocalStorage, useKeyDown, useWindowSize, usePlatform, useDeviceResolution, useBeepSound, usePagination, useSubtractedTime, useTimezoneFormatter
Device and IPuseStaticMinerIpAssignment, useMinerDuplicateValidation, usePduViewer
Domain transformsuseCostSummary, useHashBalance, useSubsidyFees, useUpdateExistedActions
TanStack Query re-exportsuseQuery, useMutation, useQueries, useInfiniteQuery, useIsFetching, useIsMutating, useQueryClient

Prerequisites

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

OptionStatusTypeDefaultDescription
currentOptionalnumber1Initial 1-indexed page
pageSizeOptionalnumber20Initial rows per page
totalOptionalnumbernoneInitial total row count
showSizeChangerOptionalbooleantrueWhether the UI exposes a page-size selector

Returns

MemberTypeDescription
paginationPaginationState{ current, pageSize, showSizeChanger, total } — spread onto <Pagination>
queryArgs{ limit, offset }Query arguments for API calls
handleChangefunction(page, pageSize) => void — pass to <Pagination onChange={…}>
setPaginationfunctionImperative pagination state update
resetfunctionReset to initial state
setTotalfunctionUpdate total row count
hideNextPagefunctionHide 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.

HookTanStack docs
useQueryTanStack useQuery
useMutationTanStack useMutation
useQueriesTanStack useQueries
useInfiniteQueryTanStack useInfiniteQuery
useIsFetchingTanStack useIsFetching
useIsMutatingTanStack useIsMutating
useQueryClientTanStack useQueryClient

On this page