Skip to Content

useForesight

useForesight is an experimental feature, so this interface may change.

This is a React hook that integrates with js.foresight to predict user interactions based on mouse trajectory, keyboard navigation, and scroll behavior. It enables proactive prefetching and UI optimization before users actually interact with elements.

The useForesight hook integrates with the js.foresight library to provide predictive interaction detection. This allows you to trigger callbacks before users actually click or interact with elements, enabling smart prefetching strategies.

import { useForesight } from '@suspensive/react-dom' import { usePrefetchQuery } from '@suspensive/react-query' const PostCard = ({ post }: { post: Post }) => { const prefetchComments = usePrefetchQuery({ queryKey: ['posts', post.id, 'comments'], queryFn: () => getPostComments(post.id), }) const { ref } = useForesight({ callback: () => { // This will be called when user is likely to interact with the element prefetchComments() }, name: `post-${post.id}`, hitSlop: 10, }) return ( <div ref={ref}> <h2>{post.title}</h2> <p>{post.description}</p> <Link to={`/posts/${post.id}/comments`}>See comments</Link> </div> ) }

Basic Usage

The hook returns a ref function and registration information that you can use to track interaction predictions.

import { useForesight } from '@suspensive/react-dom'
import { useState } from 'react'

export const Example = () => {
  const [lastPrediction, setLastPrediction] = useState('')

  const { ref, isRegistered, registerResult } = useForesight({
    callback: () => {
      setLastPrediction(
        `Predicted interaction at ${new Date().toLocaleTimeString()}`
      )
    },
    name: 'basic-example',
  })

  return (
    <div style={{ padding: 20 }}>
      <button
        ref={ref}
        style={{
          padding: '15px 30px',
          backgroundColor: isRegistered ? 'lightblue' : 'lightgray',
          border: 'none',
          borderRadius: 4,
          cursor: 'pointer',
          fontSize: 16,
        }}
        onClick={() => alert('Actually clicked!')}
      >
        Hover or move towards me
      </button>

      <div style={{ marginTop: 15 }}>
        <p>
          <strong>Registration Status:</strong>{' '}
          {isRegistered ? 'Registered' : 'Not Registered'}
        </p>
        <p>
          <strong>Touch Device:</strong>{' '}
          {registerResult?.isTouchDevice ? 'Yes' : 'No'}
        </p>
        <p>
          <strong>Limited Connection:</strong>{' '}
          {registerResult?.isLimitedConnection ? 'Yes' : 'No'}
        </p>
        <p>
          <strong>Last Prediction:</strong> {lastPrediction || 'None yet'}
        </p>
      </div>
    </div>
  )
}

options.callback

The callback function is called when the user is predicted to interact with the element.

import { useForesight } from '@suspensive/react-dom'
import { useState } from 'react'

export const Example = () => {
  const [predictions, setPredictions] = useState([])

  const { ref } = useForesight({
    callback: () => {
      setPredictions((prev) => [
        ...prev,
        `Prediction ${prev.length + 1} at ${new Date().toLocaleTimeString()}`,
      ])
    },
    name: 'callback-example',
    reactivateAfter: 1000, // Allow multiple predictions
  })

  return (
    <div style={{ padding: 20 }}>
      <div
        ref={ref}
        style={{
          padding: '30px',
          backgroundColor: 'lightcoral',
          borderRadius: 8,
          cursor: 'pointer',
          textAlign: 'center',
          userSelect: 'none',
        }}
        onClick={() => alert('Clicked!')}
      >
        Interactive Area - Move your mouse towards me!
      </div>

      <div style={{ marginTop: 20, padding: 10, backgroundColor: '#f0f0f0' }}>
        <h3>Predictions:</h3>
        <ul>
          {predictions.slice(-5).map((prediction, i) => (
            <li key={i}>{prediction}</li>
          ))}
        </ul>
        {predictions.length > 5 && (
          <p style={{ fontSize: 12, color: 'gray' }}>
            Showing last 5 predictions ({predictions.length} total)
          </p>
        )}
      </div>
    </div>
  )
}

options.hitSlop

hitSlop expands the interaction detection area around the element.

import { useForesight } from '@suspensive/react-dom'
import { useState } from 'react'

export const Example = () => {
  const [hitSlop, setHitSlop] = useState(0)
  const [predictions, setPredictions] = useState(0)

  const { ref } = useForesight({
    callback: () => setPredictions((prev) => prev + 1),
    name: 'hitslop-example',
    hitSlop,
    reactivateAfter: 500,
  })

  return (
    <div style={{ padding: 50 }}>
      <div style={{ marginBottom: 20 }}>
        <label>
          Hit Slop:
          <input
            type="range"
            min="0"
            max="50"
            value={hitSlop}
            onChange={(e) => setHitSlop(Number(e.target.value))}
            style={{ marginLeft: 10 }}
          />
          {hitSlop}px
        </label>
      </div>

      <div
        ref={ref}
        style={{
          padding: '20px',
          backgroundColor: 'lightgreen',
          border: '2px solid green',
          borderRadius: 4,
          display: 'inline-block',
          cursor: 'pointer',
        }}
      >
        Target Element
      </div>

      <div style={{ marginTop: 20 }}>
        <p>
          Predictions: <strong>{predictions}</strong>
        </p>
        <p style={{ fontSize: 12, color: 'gray' }}>
          Move your mouse around the element to see how hitSlop affects
          detection.
        </p>
      </div>
    </div>
  )
}

options.disabled

Use disabled to conditionally enable/disable foresight tracking.

import { useForesight } from '@suspensive/react-dom'
import { useState } from 'react'

export const Example = () => {
  const [disabled, setDisabled] = useState(false)
  const [predictions, setPredictions] = useState(0)

  const { ref, isRegistered } = useForesight({
    callback: () => setPredictions((prev) => prev + 1),
    name: 'disabled-example',
    disabled,
    reactivateAfter: 1000,
  })

  return (
    <div style={{ padding: 20 }}>
      <div style={{ marginBottom: 20 }}>
        <label>
          <input
            type="checkbox"
            checked={disabled}
            onChange={(e) => setDisabled(e.target.checked)}
          />
          Disable tracking
        </label>
      </div>

      <div
        ref={ref}
        style={{
          padding: '20px 40px',
          backgroundColor: isRegistered ? 'lightblue' : 'lightgray',
          border: 'none',
          borderRadius: 4,
          cursor: 'pointer',
          display: 'inline-block',
        }}
      >
        {isRegistered ? 'Tracking Active' : 'Tracking Disabled'}
      </div>

      <div style={{ marginTop: 15 }}>
        <p>
          Predictions: <strong>{predictions}</strong>
        </p>
        <p>
          Status:{' '}
          <strong>{isRegistered ? 'Registered' : 'Not Registered'}</strong>
        </p>
      </div>
    </div>
  )
}

options.autoInitialize

Control whether to automatically initialize the ForesightManager.

import { useForesight } from '@suspensive/react-dom'
import { useState } from 'react'

export const Example = () => {
  const [autoInit, setAutoInit] = useState(true)
  const [predictions, setPredictions] = useState(0)

  const { ref, isRegistered, registerResult } = useForesight({
    callback: () => setPredictions((prev) => prev + 1),
    name: 'auto-init-example',
    autoInitialize: autoInit,
    reactivateAfter: 1000,
  })

  return (
    <div style={{ padding: 20 }}>
      <div style={{ marginBottom: 20 }}>
        <label>
          <input
            type="checkbox"
            checked={autoInit}
            onChange={(e) => setAutoInit(e.target.checked)}
          />
          Auto-initialize ForesightManager
        </label>
      </div>

      <div
        ref={ref}
        style={{
          padding: '20px 40px',
          backgroundColor: isRegistered ? 'lightgreen' : 'lightcoral',
          border: 'none',
          borderRadius: 4,
          cursor: 'pointer',
          display: 'inline-block',
        }}
      >
        Hover me
      </div>

      <div style={{ marginTop: 15 }}>
        <p>
          Auto Initialize: <strong>{autoInit ? 'Enabled' : 'Disabled'}</strong>
        </p>
        <p>
          Registered: <strong>{isRegistered ? 'Yes' : 'No'}</strong>
        </p>
        <p>
          Predictions: <strong>{predictions}</strong>
        </p>
      </div>
    </div>
  )
}

Return Value

The hook returns an object with the following properties:

  • ref: A ref function to attach to the target element
  • registerResult: Information about the registration result (includes isTouchDevice, isLimitedConnection, etc.)
  • isRegistered: Boolean indicating whether the element is currently registered with ForesightManager
import { useForesight } from '@suspensive/react-dom'
import { useState } from 'react'

export const Example = () => {
  const [callbackCount, setCallbackCount] = useState(0)

  const foresightResult = useForesight({
    callback: () => setCallbackCount((prev) => prev + 1),
    name: 'return-value-example',
    reactivateAfter: 2000,
  })

  return (
    <div style={{ padding: 20 }}>
      <div
        ref={foresightResult.ref}
        style={{
          padding: '20px 30px',
          backgroundColor: 'lightyellow',
          border: '1px solid orange',
          borderRadius: 4,
          cursor: 'pointer',
          display: 'inline-block',
        }}
      >
        Interactive Element
      </div>

      <div
        style={{
          marginTop: 20,
          padding: 15,
          backgroundColor: '#f5f5f5',
          borderRadius: 4,
        }}
      >
        <h3>Hook Return Value:</h3>
        <ul>
          <li>
            <strong>isRegistered:</strong>{' '}
            {String(foresightResult.isRegistered)}
          </li>
          <li>
            <strong>registerResult.isTouchDevice:</strong>{' '}
            {String(foresightResult.registerResult?.isTouchDevice)}
          </li>
          <li>
            <strong>registerResult.isLimitedConnection:</strong>{' '}
            {String(foresightResult.registerResult?.isLimitedConnection)}
          </li>
          <li>
            <strong>Callback invoked:</strong> {callbackCount} times
          </li>
        </ul>
      </div>
    </div>
  )
}
Last updated on