Skip to main content

Overview

Crocante provides a collection of custom React hooks for common functionality. These hooks encapsulate complex logic and make it reusable across components.

useModal

Manage modal open/close state with lifecycle callbacks.

Signature

interface UseModalOptions {
  onOpen?: () => void;
  onClose?: () => void;
  onClosePrevious?: () => void;
  onOpenPrevious?: () => void;
}

function useModal(
  initial?: boolean, 
  opts?: UseModalOptions
): {
  isOpen: boolean;
  open: () => void;
  close: () => void;
  toggle: (next?: boolean) => void;
  setIsOpen: (value: boolean) => void;
}

Usage

import { useModal } from "@/hooks/use-modal";
import { Modal, Button } from "@/components";

function TransferModal() {
  const { isOpen, open, close } = useModal();

  return (
    <>
      <Button onClick={open}>Open Transfer</Button>
      <Modal title="Transfer" open={isOpen} onClose={close}>
        <p>Transfer content here</p>
      </Modal>
    </>
  );
}

Features

  • Lifecycle Callbacks: Execute code on open/close
  • Toggle Function: Toggle state programmatically
  • Initial State: Set initial open state
  • Previous Callbacks: Execute before state change

useSessionExpiry

Handle session expiration warnings and renewal.

Signature

function useSessionExpiry(isSignedIn: boolean): {
  isExpiryModalOpen: boolean;
  expiresAtForModal: number | null;
  onRenew: () => Promise<void>;
  isRenewing: boolean;
}

Usage

import { useSessionExpiry } from "@/hooks/use-session-expiry";
import { useSession } from "@/context/session-provider";

function SessionExpiryModal() {
  const { isSignedIn } = useSession();
  const { 
    isExpiryModalOpen, 
    expiresAtForModal, 
    onRenew, 
    isRenewing 
  } = useSessionExpiry(isSignedIn);

  if (!isExpiryModalOpen) return null;

  const timeRemaining = expiresAtForModal 
    ? Math.max(0, expiresAtForModal - Date.now()) 
    : 0;

  return (
    <Modal 
      title="Session Expiring" 
      open={isExpiryModalOpen}
      blockClose={true}
    >
      <p>Your session will expire in {timeRemaining}ms</p>
      <Button 
        onClick={onRenew} 
        isLoading={isRenewing}
      >
        Renew Session
      </Button>
    </Modal>
  );
}

Features

  • Automatic Warning: Shows modal before session expires
  • Countdown: Tracks time until expiration
  • Renewal: Extends session via API call
  • Auto-trigger Auth: Opens login modal on expiration

useTokenConversion

Convert token amounts to/from USD with real-time rates.

Signature

function useTokenConversion(
  userId: string, 
  token: string
): {
  convertToUSD: (value: string) => string;
  convertFromUSD: (usdValue: string) => string;
  conversionRate: number;
  isLoading: boolean;
}

Usage

import { useTokenConversion } from "@/hooks/use-token-conversion";
import { useSession } from "@/context/session-provider";

function TokenBalance({ token, amount }: { token: string; amount: string }) {
  const { user } = useSession();
  const { convertToUSD, conversionRate, isLoading } = useTokenConversion(
    user?.id || "",
    token
  );

  const usdValue = convertToUSD(amount);

  return (
    <div>
      <p>{amount} {token}</p>
      <p className="text-muted">
        {isLoading ? "Loading..." : `$${usdValue}`}
      </p>
      <p className="text-xs">Rate: ${conversionRate}</p>
    </div>
  );
}

Features

  • Real-time Rates: Polls conversion rates at intervals
  • Bidirectional: Convert to and from USD
  • Formatting: Automatic decimal precision
  • Error Handling: Returns “0” for invalid inputs

useTokenSwap

Calculate token swap conversions with fees.

Signature

function useTokenSwap(
  userId: string,
  tokenFrom: string,
  tokenTo: string,
  isLoading: boolean
): {
  convertTo: (valueFrom: string) => string;
  convertFrom: (valueTo: string) => string;
  conversionRateFrom: number;
  commissionRate: number;
  minAmount: string;
  isLoading: boolean;
}

Usage

import { useTokenSwap } from "@/hooks/use-token-swap";

function SwapForm() {
  const { user } = useSession();
  const [fromAmount, setFromAmount] = useState("");
  const [fromToken, setFromToken] = useState("BTC");
  const [toToken, setToToken] = useState("ETH");
  
  const { 
    convertTo, 
    conversionRateFrom, 
    commissionRate,
    minAmount 
  } = useTokenSwap(
    user?.id || "",
    fromToken,
    toToken,
    false
  );

  const toAmount = convertTo(fromAmount);

  return (
    <div>
      <Input 
        label="From"
        value={fromAmount}
        onChange={(e) => setFromAmount(e.target.value)}
      />
      <p>You will receive: {toAmount} {toToken}</p>
      <p>Rate: {conversionRateFrom}</p>
      <p>Fee: {commissionRate}%</p>
      <p>Min: {minAmount} {toToken}</p>
    </div>
  );
}

Features

  • Live Quotes: Real-time swap rates
  • Commission Info: Fee percentages
  • Minimum Amount: Enforced swap minimums
  • Bidirectional: Calculate both directions

useSelector

Manage select dropdown state with change callbacks.

Signature

interface SelectorOptions<T> {
  [key: string]: T;
}

function useSelector<T>(
  options: SelectorOptions<T>,
  defaultIndex?: number,
  opts?: {
    onReset?: () => void;
    onChange?: () => void;
    onChangeReactive?: (selectedRow: T) => void;
  }
): {
  selectedRow: T;
  selectedIndex: number;
  getNextRow: () => T;
  change: (key: string | React.ChangeEvent<HTMLSelectElement>) => void;
  reset: () => void;
}

Usage

import { useSelector } from "@/hooks/use-selector";
import { Select } from "@/components";

function TokenSelect({ tokens }: { tokens: Token[] }) {
  const tokenOptions = tokens.reduce((acc, token) => {
    acc[token.id] = token;
    return acc;
  }, {} as Record<string, Token>);

  const { selectedRow, selectedIndex, change } = useSelector(
    tokenOptions,
    0,
    {
      onChange: () => console.log("Token changed"),
      onChangeReactive: (token) => {
        console.log("Selected:", token.symbol);
      }
    }
  );

  const selectOptions = Object.keys(tokenOptions).map(key => ({
    id: key,
    label: tokenOptions[key].symbol,
    icon: <TokenIcon token={tokenOptions[key]} />
  }));

  return (
    <div>
      <Select
        label="Select Token"
        properties={{
          selectedIndex,
          options: selectOptions,
          onChange: change
        }}
      />
      <p>Selected: {selectedRow.symbol}</p>
    </div>
  );
}

Features

  • Auto-reset: Resets when options change
  • Change Callbacks: React to selection changes
  • Get Next: Cycle through options
  • Type-safe: Fully generic TypeScript support

useIsMobile

Detect mobile viewport with customizable breakpoint.

Signature

function useIsMobile(breakpoint?: number): boolean

Usage

import { useIsMobile } from "@/hooks/use-is-mobile";

function ResponsiveComponent() {
  const isMobile = useIsMobile(); // default: 1024px

  return (
    <div>
      {isMobile ? (
        <MobileNavigation />
      ) : (
        <DesktopNavigation />
      )}
    </div>
  );
}

Features

  • SSR-safe: Prevents hydration mismatches
  • Custom Breakpoints: Configure viewport threshold
  • Resize Listener: Updates on window resize
  • Performance: Efficient event listener cleanup

useMounted

Detect when component has mounted on client.

Signature

function useMounted(): boolean

Usage

import useMounted from "@/hooks/use-mounted";

function ClientOnlyComponent() {
  const mounted = useMounted();

  if (!mounted) return null;

  return (
    <div>
      {/* Client-only content */}
      <BrowserAPIComponent />
    </div>
  );
}

Features

  • SSR Compatible: Returns false on server
  • Hydration Safe: Prevents mismatches
  • Simple API: Boolean return value

useValueVerifier

Validate input values against constraints.

Location

hooks/use-value-verifier.ts

Usage Context

Used internally for validating numeric inputs, checking minimum/maximum values, and ensuring input constraints are met.

useSessionMode

Manage session operation modes.

Location

hooks/use-session-mode.ts

Usage Context

Handles different session states and modes for user authentication flows.

useTableFilters

Manages table filtering state, including filter modal, active filters display, and pagination reset.

Signature

function useTableFilters(props: UseTableFiltersProps): UseTableFiltersReturn

interface UseTableFiltersProps {
  page: number;
  selectors: SelectorProps[];
  resetSelectors: () => void;
  setFilters: (filters: string[][]) => void;
  handleChangeFilters: { op: SelectOption; index: number } | undefined;
}

interface UseTableFiltersReturn {
  page: number;
  filtersClassName: string;
  activeFilters: string;
  openFiltersModal: () => void;
  filtersModalOpen: boolean;
  closeFiltersModal: () => void;
}

Parameters

  • page: Current page number
  • selectors: Array of selector configurations for filter dropdowns
  • resetSelectors: Function to reset all selectors to default state
  • setFilters: Callback to update the applied filters
  • handleChangeFilters: Trigger object for filter changes

Return Value

  • page: Synchronized page number (resets to 1 when filters change)
  • filtersClassName: CSS class for filter button styling ("secondary" or "outline")
  • activeFilters: Formatted string of active filter labels
  • openFiltersModal: Function to open the filters modal
  • filtersModalOpen: Boolean indicating if filters modal is open
  • closeFiltersModal: Function to close the filters modal

Example

import { useTableFilters } from '@/hooks/use-table-filters';
import { useSelector } from '@/hooks/use-selector';

function ActivityTable() {
  const [page, setPage] = useState(1);
  const [filters, setFilters] = useState<string[][]>([]);
  
  // Status filter selector
  const statusSelector = useSelector([
    { label: 'All Statuses', value: '' },
    { label: 'Active', value: 'ACTIVE' },
    { label: 'Completed', value: 'COMPLETED' },
  ]);
  
  // Type filter selector
  const typeSelector = useSelector([
    { label: 'All Types', value: '' },
    { label: 'Deposit', value: 'DEPOSIT' },
    { label: 'Withdrawal', value: 'WITHDRAWAL' },
  ]);
  
  const resetSelectors = () => {
    statusSelector.reset();
    typeSelector.reset();
  };
  
  const {
    page: currentPage,
    filtersClassName,
    activeFilters,
    openFiltersModal,
    filtersModalOpen,
    closeFiltersModal,
  } = useTableFilters({
    page,
    selectors: [statusSelector, typeSelector],
    resetSelectors,
    setFilters,
    handleChangeFilters: undefined,
  });
  
  return (
    <div>
      <Button
        variant={filtersClassName}
        onClick={openFiltersModal}
      >
        Filters {activeFilters && `(${activeFilters})`}
      </Button>
      
      <Modal isOpen={filtersModalOpen} onClose={closeFiltersModal}>
        <Select {...statusSelector} label="Status" />
        <Select {...typeSelector} label="Type" />
      </Modal>
      
      <Table
        data={filteredData}
        page={currentPage}
        onPageChange={setPage}
      />
    </div>
  );
}

Behavior

  1. Filter Application: When selectors change, filters are automatically applied
  2. Page Reset: Page resets to 1 whenever filters change
  3. Visual Feedback: Filter button changes from "secondary" to "outline" when filters are active
  4. Active Filter Display: Shows comma-separated list of active filter labels
  5. Modal Management: Opens/closes filter modal with automatic reset on open

Use Cases

  • Activity transaction filtering by status and type
  • Loan history filtering by loan type and date range
  • Staking position filtering by currency and status
  • Any table that needs multiple filter criteria

Next Steps

Context Providers

Learn about context providers

UI Components

Explore UI components

Build docs developers (and LLMs) love