useWayfinderRequest

Overview

The useWayfinderRequest hook provides direct access to the Wayfinder request function. This is the most commonly used hook for fetching data from ar:// URLs, offering a clean and focused API for data retrieval without exposing the full Wayfinder instance.

Signature

function useWayfinderRequest(): Wayfinder['request']

// The returned function signature:
function request(url: string, options?: RequestOptions): Promise<Response>

Usage

Basic Data Fetching

import { useWayfinderRequest } from '@ar.io/wayfinder-react'
import { useState } from 'react'

function DataFetcher({ txId }) {
  const request = useWayfinderRequest()
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(null)

  const fetchData = async () => {
    setLoading(true)
    setError(null)

    try {
      const response = await request(`ar://${txId}`)
      const text = await response.text()
      setData(text)
    } catch (err) {
      setError(err)
    } finally {
      setLoading(false)
    }
  }

  return (
    <div>
      <button onClick={fetchData} disabled={loading}>
        {loading ? 'Loading...' : 'Fetch Data'}
      </button>

      {error && <div className="error">Error: {error.message}</div>}
      {data && <pre>{data}</pre>}
    </div>
  )
}

JSON Data Fetching

import { useWayfinderRequest } from '@ar.io/wayfinder-react'
import { useState, useEffect } from 'react'

function JsonDataComponent({ txId }) {
  const request = useWayfinderRequest()
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)

  useEffect(() => {
    if (!txId) return

    const fetchJson = async () => {
      setLoading(true)
      setError(null)

      try {
        const response = await request(`ar://${txId}`)
        const text = await response.text()
        const json = JSON.parse(text)
        setData(json)
      } catch (err) {
        setError(err)
      } finally {
        setLoading(false)
      }
    }

    fetchJson()
  }, [txId, request])

  if (loading) return <div>Loading JSON data...</div>
  if (error) return <div>Error loading JSON: {error.message}</div>
  if (!data) return <div>No data</div>

  return (
    <div>
      <h3>JSON Data</h3>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  )
}

Image/Binary Data Fetching

import { useWayfinderRequest } from '@ar.io/wayfinder-react'
import { useState, useEffect } from 'react'

function ImageComponent({ txId }) {
  const request = useWayfinderRequest()
  const [imageUrl, setImageUrl] = useState(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)

  useEffect(() => {
    if (!txId) return

    const fetchImage = async () => {
      setLoading(true)
      setError(null)

      try {
        const response = await request(`ar://${txId}`)
        const blob = await response.blob()
        const url = URL.createObjectURL(blob)
        setImageUrl(url)
      } catch (err) {
        setError(err)
      } finally {
        setLoading(false)
      }
    }

    fetchImage()

    // Cleanup blob URL
    return () => {
      if (imageUrl) {
        URL.revokeObjectURL(imageUrl)
      }
    }
  }, [txId, request])

  if (loading) return <div>Loading image...</div>
  if (error) return <div>Error loading image: {error.message}</div>
  if (!imageUrl) return <div>No image</div>

  return (
    <div>
      <img
        src={imageUrl}
        alt="Arweave content"
        style={{ maxWidth: '100%', height: 'auto' }}
      />
    </div>
  )
}

Request with Custom Options

import { useWayfinderRequest } from '@ar.io/wayfinder-react'
import { useState } from 'react'
import { StaticRoutingStrategy } from '@ar.io/wayfinder-core'

function CustomRequestComponent({ txId }) {
  const request = useWayfinderRequest()
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(null)

  const fetchWithCustomOptions = async () => {
    setLoading(true)
    setError(null)

    try {
      // Request with custom options
      const response = await request(`ar://${txId}`, {
        method: 'GET',
        headers: {
          Accept: 'application/json',
          'User-Agent': 'my-app/1.0.0',
        },
        timeout: 10000, // 10 second timeout
        routingSettings: {
          // Override routing for this request
          strategy: new StaticRoutingStrategy({
            gateway: 'https://arweave.net',
          }),
        },
        verificationSettings: {
          // Enable verification for this request
          enabled: true,
          strict: true,
        },
      })

      const text = await response.text()
      setData(text)
    } catch (err) {
      setError(err)
    } finally {
      setLoading(false)
    }
  }

  return (
    <div>
      <button onClick={fetchWithCustomOptions} disabled={loading}>
        {loading
          ? 'Fetching with custom options...'
          : 'Fetch with Custom Options'}
      </button>

      {error && <div className="error">Error: {error.message}</div>}
      {data && <pre>{data}</pre>}
    </div>
  )
}

Custom Hooks

Reusable JSON Fetching Hook

import { useWayfinderRequest } from '@ar.io/wayfinder-react'
import { useState, useEffect, useCallback } from 'react'

function useArweaveJson(txId) {
  const request = useWayfinderRequest()
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(null)

  const fetchData = useCallback(async () => {
    if (!txId) return

    setLoading(true)
    setError(null)

    try {
      const response = await request(`ar://${txId}`)
      const text = await response.text()
      const json = JSON.parse(text)
      setData(json)
    } catch (err) {
      setError(err)
    } finally {
      setLoading(false)
    }
  }, [txId, request])

  useEffect(() => {
    fetchData()
  }, [fetchData])

  const refetch = useCallback(() => {
    fetchData()
  }, [fetchData])

  const clearData = useCallback(() => {
    setData(null)
    setError(null)
  }, [])

  return {
    data,
    loading,
    error,
    refetch,
    clearData,
  }
}

// Usage
function JsonDisplay({ txId }) {
  const { data, loading, error, refetch } = useArweaveJson(txId)

  if (loading) return <div>Loading JSON...</div>
  if (error)
    return (
      <div>
        Error: {error.message}
        <button onClick={refetch}>Retry</button>
      </div>
    )

  return <pre>{JSON.stringify(data, null, 2)}</pre>
}

Generic Data Fetching Hook

import { useWayfinderRequest } from '@ar.io/wayfinder-react'
import { useState, useCallback, useMemo, useRef } from 'react'

function useArweaveData(parser = (response) => response.text()) {
  const request = useWayfinderRequest()
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(null)

  const fetchData = useCallback(
    async (txId, options = {}) => {
      setLoading(true)
      setError(null)

      try {
        const response = await request(`ar://${txId}`, options)
        const parsedData = await parser(response)
        setData(parsedData)
        return parsedData
      } catch (err) {
        setError(err)
        throw err
      } finally {
        setLoading(false)
      }
    },
    [request, parser],
  )

  const reset = useCallback(() => {
    setData(null)
    setError(null)
  }, [])

  return {
    data,
    loading,
    error,
    fetchData,
    reset,
  }
}

// Usage examples
import { useEffect } from 'react'

function TextComponent({ txId }) {
  const { data, loading, error, fetchData } = useArweaveData()

  useEffect(() => {
    fetchData(txId)
  }, [txId, fetchData])

  return loading ? <div>Loading...</div> : <pre>{data}</pre>
}

function JsonComponent({ txId }) {
  const { data, loading, error, fetchData } = useArweaveData(
    async (response) => {
      const text = await response.text()
      return JSON.parse(text)
    },
  )

  useEffect(() => {
    fetchData(txId)
  }, [txId, fetchData])

  return loading ? (
    <div>Loading...</div>
  ) : (
    <pre>{JSON.stringify(data, null, 2)}</pre>
  )
}

function ImageComponent({ txId }) {
  const { data, loading, error, fetchData } = useArweaveData(
    async (response) => {
      const blob = await response.blob()
      return URL.createObjectURL(blob)
    },
  )

  useEffect(() => {
    fetchData(txId)

    // Cleanup blob URL
    return () => {
      if (data) URL.revokeObjectURL(data)
    }
  }, [txId, fetchData, data])

  return loading ? (
    <div>Loading...</div>
  ) : (
    <img src={data} alt="Arweave content" />
  )
}

Error Handling

Comprehensive Error Handling

import { useWayfinderRequest } from '@ar.io/wayfinder-react'
import { useState, useCallback } from 'react'

function RobustDataFetcher({ txId }) {
  const request = useWayfinderRequest()
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(null)
  const [retryCount, setRetryCount] = useState(0)

  const fetchData = useCallback(
    async (retryAttempt = 0) => {
      setLoading(true)
      setError(null)
      setRetryCount(retryAttempt)

      try {
        const response = await request(`ar://${txId}`)

        // Check response status
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`)
        }

        const text = await response.text()
        setData(text)
      } catch (err) {
        setError(err)

        // Handle different error types
        if (err.name === 'TimeoutError') {
          console.error('Request timed out')
        } else if (err.name === 'NetworkError') {
          console.error('Network error occurred')
        } else if (err.name === 'VerificationError') {
          console.error('Data verification failed')
        } else if (err.message.includes('404')) {
          console.error('Transaction not found')
        } else {
          console.error('Unknown error:', err)
        }
      } finally {
        setLoading(false)
      }
    },
    [txId, request],
  )

  const retry = useCallback(() => {
    fetchData(retryCount + 1)
  }, [fetchData, retryCount])

  const clearError = useCallback(() => {
    setError(null)
  }, [])

  return (
    <div>
      <button onClick={() => fetchData(0)} disabled={loading}>
        {loading ? 'Loading...' : 'Fetch Data'}
      </button>

      {error && (
        <div className="error-container">
          <div className="error-message">
            Error: {error.message}
            {retryCount > 0 && ` (Attempt ${retryCount + 1})`}
          </div>
          <div className="error-actions">
            <button onClick={retry}>Retry</button>
            <button onClick={clearError}>Dismiss</button>
          </div>
        </div>
      )}

      {data && (
        <div className="data-container">
          <h3>Data (Attempt {retryCount + 1})</h3>
          <pre>{data}</pre>
        </div>
      )}
    </div>
  )
}

Performance Optimization

Memoization and Caching

import { useWayfinderRequest } from '@ar.io/wayfinder-react'
import { useState, useCallback, useMemo, useRef } from 'react'

function OptimizedDataFetcher({ txIds }) {
  const request = useWayfinderRequest()
  const [dataCache, setDataCache] = useState(new Map())
  const [loadingStates, setLoadingStates] = useState(new Map())
  const [errors, setErrors] = useState(new Map())
  const abortControllersRef = useRef(new Map())

  // Memoized fetch function
  const fetchData = useCallback(
    async (txId) => {
      // Check cache first
      if (dataCache.has(txId)) {
        return dataCache.get(txId)
      }

      // Cancel previous request for this txId
      const existingController = abortControllersRef.current.get(txId)
      if (existingController) {
        existingController.abort()
      }

      // Create new abort controller
      const controller = new AbortController()
      abortControllersRef.current.set(txId, controller)

      setLoadingStates((prev) => new Map(prev).set(txId, true))
      setErrors((prev) => {
        const newErrors = new Map(prev)
        newErrors.delete(txId)
        return newErrors
      })

      try {
        const response = await request(`ar://${txId}`, {
          signal: controller.signal,
        })
        const text = await response.text()

        // Update cache
        setDataCache((prev) => new Map(prev).set(txId, text))

        return text
      } catch (err) {
        if (err.name !== 'AbortError') {
          setErrors((prev) => new Map(prev).set(txId, err))
        }
        throw err
      } finally {
        setLoadingStates((prev) => {
          const newStates = new Map(prev)
          newStates.delete(txId)
          return newStates
        })
        abortControllersRef.current.delete(txId)
      }
    },
    [request, dataCache],
  )

  // Memoized data processing
  const processedData = useMemo(() => {
    return txIds.map((txId) => ({
      txId,
      data: dataCache.get(txId),
      loading: loadingStates.get(txId) || false,
      error: errors.get(txId),
    }))
  }, [txIds, dataCache, loadingStates, errors])

  // Cleanup on unmount
  useEffect(() => {
    return () => {
      // Cancel all pending requests
      abortControllersRef.current.forEach((controller) => {
        controller.abort()
      })
    }
  }, [])

  return (
    <div>
      {processedData.map(({ txId, data, loading, error }) => (
        <div key={txId} className="data-item">
          <h4>Transaction: {txId}</h4>

          {loading && <div>Loading...</div>}
          {error && <div className="error">Error: {error.message}</div>}
          {data && <pre>{data}</pre>}

          <button onClick={() => fetchData(txId)} disabled={loading}>
            {loading ? 'Loading...' : 'Fetch'}
          </button>
        </div>
      ))}
    </div>
  )
}

TypeScript Support

Typed Usage

import { useWayfinderRequest } from '@ar.io/wayfinder-react'
import { useState, useCallback } from 'react'

interface ApiResponse {
  id: string
  name: string
  data: any[]
}

interface DataFetcherProps {
  txId: string
  onSuccess?: (data: ApiResponse) => void
  onError?: (error: Error) => void
}

const TypedDataFetcher: React.FC<DataFetcherProps> = ({
  txId,
  onSuccess,
  onError,
}) => {
  const request = useWayfinderRequest()
  const [data, setData] = useState<ApiResponse | null>(null)
  const [loading, setLoading] = useState<boolean>(false)
  const [error, setError] = useState<Error | null>(null)

  const fetchData = useCallback(async (): Promise<void> => {
    setLoading(true)
    setError(null)

    try {
      const response = await request(`ar://${txId}`)
      const text = await response.text()
      const parsedData: ApiResponse = JSON.parse(text)

      setData(parsedData)
      onSuccess?.(parsedData)
    } catch (err) {
      const error = err as Error
      setError(error)
      onError?.(error)
    } finally {
      setLoading(false)
    }
  }, [txId, request, onSuccess, onError])

  return (
    <div>
      <button onClick={fetchData} disabled={loading}>
        {loading ? 'Loading...' : 'Fetch Typed Data'}
      </button>

      {error && <div>Error: {error.message}</div>}
      {data && (
        <div>
          <h3>{data.name}</h3>
          <p>ID: {data.id}</p>
          <p>Items: {data.data.length}</p>
        </div>
      )}
    </div>
  )
}

Generic Hook with TypeScript

import { useWayfinderRequest } from '@ar.io/wayfinder-react'
import { useState, useCallback } from 'react'

interface UseArweaveDataResult<T> {
  data: T | null
  loading: boolean
  error: Error | null
  fetchData: (txId: string) => Promise<T>
  reset: () => void
}

function useArweaveData<T>(
  parser: (response: Response) => Promise<T>,
): UseArweaveDataResult<T> {
  const request = useWayfinderRequest()
  const [data, setData] = useState<T | null>(null)
  const [loading, setLoading] = useState<boolean>(false)
  const [error, setError] = useState<Error | null>(null)

  const fetchData = useCallback(
    async (txId: string): Promise<T> => {
      setLoading(true)
      setError(null)

      try {
        const response = await request(`ar://${txId}`)
        const parsedData = await parser(response)
        setData(parsedData)
        return parsedData
      } catch (err) {
        const error = err as Error
        setError(error)
        throw error
      } finally {
        setLoading(false)
      }
    },
    [request, parser],
  )

  const reset = useCallback((): void => {
    setData(null)
    setError(null)
  }, [])

  return {
    data,
    loading,
    error,
    fetchData,
    reset,
  }
}

// Usage
interface UserData {
  name: string
  email: string
  avatar?: string
}

function UserProfile({ txId }: { txId: string }) {
  const { data, loading, error, fetchData } = useArweaveData<UserData>(
    async (response) => {
      const text = await response.text()
      return JSON.parse(text) as UserData
    },
  )

  useEffect(() => {
    fetchData(txId)
  }, [txId, fetchData])

  if (loading) return <div>Loading user...</div>
  if (error) return <div>Error: {error.message}</div>
  if (!data) return <div>No user data</div>

  return (
    <div>
      <h2>{data.name}</h2>
      <p>{data.email}</p>
      {data.avatar && <img src={data.avatar} alt="Avatar" />}
    </div>
  )
}

Testing

Mocking the Hook

import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { useWayfinderRequest } from '@ar.io/wayfinder-react'

// Mock the hook
jest.mock('@ar.io/wayfinder-react', () => ({
  useWayfinderRequest: jest.fn(),
}))

const mockUseWayfinderRequest = useWayfinderRequest as jest.MockedFunction<
  typeof useWayfinderRequest
>

describe('DataFetcher', () => {
  beforeEach(() => {
    jest.clearAllMocks()
  })

  test('fetches and displays data successfully', async () => {
    const mockRequest = jest.fn().mockResolvedValue({
      ok: true,
      text: () => Promise.resolve('test data'),
    })

    mockUseWayfinderRequest.mockReturnValue(mockRequest)

    render(<DataFetcher txId="test-tx-id" />)

    fireEvent.click(screen.getByText('Fetch Data'))

    expect(mockRequest).toHaveBeenCalledWith('ar://test-tx-id')

    await waitFor(() => {
      expect(screen.getByText('test data')).toBeInTheDocument()
    })
  })

  test('handles errors gracefully', async () => {
    const mockRequest = jest.fn().mockRejectedValue(new Error('Network error'))

    mockUseWayfinderRequest.mockReturnValue(mockRequest)

    render(<DataFetcher txId="test-tx-id" />)

    fireEvent.click(screen.getByText('Fetch Data'))

    await waitFor(() => {
      expect(screen.getByText('Error: Network error')).toBeInTheDocument()
    })
  })
})

When to Use

Use useWayfinderRequest when you need:

  • Simple data fetching: Basic retrieval of data from ar:// URLs
  • Clean component code: Focus on data handling without Wayfinder complexity
  • Custom data processing: Parse JSON, handle binary data, or transform responses
  • Request customization: Override routing or verification settings per request
  • Custom hooks: Build reusable data fetching abstractions

For other use cases, consider:

Was this page helpful?