React - Advanced Hooks

These hooks are less used on a daily basis, but when you need them, they are indispensable! Performance, layout, unique identifiers... it's all there.


useLayoutEffect - Synchronous and Blocking

Difference with useEffect

import { useState, useEffect, useLayoutEffect, useRef } from 'react'

function LayoutEffectDemo() {
  const [count, setCount] = useState(0)
  const [height, setHeight] = useState(0)
  const divRef = useRef()

  // āš ļø useEffect - Executes AFTER the paint (asynchronous)
  useEffect(() => {
    console.log('šŸ“ useEffect - AFTER paint')
    // Can cause a visual "flash" if we modify the DOM
  })

  // āœ… useLayoutEffect - Executes BEFORE the paint (synchronous)
  useLayoutEffect(() => {
    console.log('šŸ“ useLayoutEffect - BEFORE paint')
    
    if (divRef.current) {
      const rect = divRef.current.getBoundingClientRect()
      setHeight(rect.height)
    }
  }) // Blocks the paint until this code is executed!

  return (
    <div>
      <h3>useLayoutEffect Demo</h3>
      
      <div
        ref={divRef}
        style={{
          padding: '20px',
          border: '2px solid #007bff',
          backgroundColor: '#f8f9fa',
          // The height changes dynamically
          fontSize: count > 5 ? '24px' : '16px'
        }}
      >
        <p>Count: {count}</p>
        <p>This div is {height}px high</p>
        <p>Content that can change size...</p>
      </div>

      <button onClick={() => setCount(c => c + 1)}>
        Increment
      </button>
    </div>
  )
}

Use Case: Avoiding the Flash

function TooltipWithLayoutEffect() {
  const [isVisible, setIsVisible] = useState(false)
  const [position, setPosition] = useState({ top: 0, left: 0 })
  const tooltipRef = useRef()
  const triggerRef = useRef()

  // āœ… Calculate the position BEFORE the paint to avoid the flash
  useLayoutEffect(() => {
    if (isVisible && tooltipRef.current && triggerRef.current) {
      const triggerRect = triggerRef.current.getBoundingClientRect()
      const tooltipRect = tooltipRef.current.getBoundingClientRect()
      
      // Calculate the optimal position
      let top = triggerRect.bottom + 8
      let left = triggerRect.left + (triggerRect.width / 2) - (tooltipRect.width / 2)
      
      // Check for screen overflows
      if (left + tooltipRect.width > window.innerWidth) {
        left = window.innerWidth - tooltipRect.width - 8
      }
      if (left < 8) {
        left = 8
      }
      if (top + tooltipRect.height > window.innerHeight) {
        top = triggerRect.top - tooltipRect.height - 8
      }

      setPosition({ top, left })
    }
  }, [isVisible])

  return (
    <div>
      <h3>Tooltip with useLayoutEffect</h3>
      
      <button
        ref={triggerRef}
        onMouseEnter={() => setIsVisible(true)}
        onMouseLeave={() => setIsVisible(false)}
        style={{ margin: '100px' }}
      >
        Hover me for tooltip
      </button>

      {isVisible && (
        <div
          ref={tooltipRef}
          style={{
            position: 'fixed',
            top: position.top,
            left: position.left,
            background: '#333',
            color: 'white',
            padding: '8px 12px',
            borderRadius: '4px',
            fontSize: '14px',
            zIndex: 1000,
            // Without useLayoutEffect, this tooltip would "flash"
            // to an incorrect position and then reposition itself
          }}
        >
          I am a well-positioned tooltip!
        </div>
      )}
    </div>
  )
}

useId - Unique Identifiers

Problem of Static IDs

// āŒ PROBLEM - Hardcoded IDs cause conflicts
function BadForm() {
  return (
    <div>
      <label htmlFor="name">Name:</label>
      <input id="name" type="text" />
      
      <label htmlFor="email">Email:</label>
      <input id="email" type="email" />
    </div>
  )
}

// If this component is used multiple times on the same page:
function App() {
  return (
    <div>
      <BadForm /> {/* IDs : name, email */}
      <BadForm /> {/* IDs : name, email ← CONFLICT! */}
    </div>
  )
}

Solution with useId

function GoodForm() {
  const nameId = useId()
  const emailId = useId()
  const descriptionId = useId()

  return (
    <div>
      <h4>Form with unique IDs</h4>
      
      <div>
        <label htmlFor={nameId}>Name:</label>
        <input id={nameId} type="text" />
      </div>
      
      <div>
        <label htmlFor={emailId}>Email:</label>
        <input id={emailId} type="email" />
      </div>
      
      <div>
        <label htmlFor={descriptionId}>Description:</label>
        <textarea id={descriptionId} rows={3} />
      </div>
    </div>
  )
}

// Now we can use the component multiple times!
function App() {
  return (
    <div>
      <GoodForm /> {/* IDs : :r1:, :r2:, :r3: */}
      <GoodForm /> {/* IDs : :r4:, :r5:, :r6: */}
      <GoodForm /> {/* IDs : :r7:, :r8:, :r9: */}
    </div>
  )
}

Advanced Patterns with useId

function FormFieldWithId({ label, type = 'text', ...props }) {
  const id = useId()
  
  return (
    <div style={{ marginBottom: '15px' }}>
      <label htmlFor={id} style={{ display: 'block', marginBottom: '5px' }}>
        {label}
      </label>
      <input id={id} type={type} {...props} />
    </div>
  )
}

function RadioGroupWithId({ name, options, value, onChange }) {
  const groupId = useId()
  
  return (
    <fieldset>
      <legend>Choose an option</legend>
      {options.map((option, index) => {
        const optionId = `${groupId}-${index}`
        
        return (
          <div key={option.value} style={{ marginBottom: '8px' }}>
            <input
              type="radio"
              id={optionId}
              name={name}
              value={option.value}
              checked={value === option.value}
              onChange={onChange}
            />
            <label htmlFor={optionId} style={{ marginLeft: '8px' }}>
              {option.label}
            </label>
          </div>
        )
      })}
    </fieldset>
  )
}

function ComplexForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    category: '',
    newsletter: false
  })

  const handleInputChange = (field, value) => {
    setFormData(prev => ({ ...prev, [field]: value }))
  }

  const categoryOptions = [
    { value: 'dev', label: 'Developer' },
    { value: 'design', label: 'Designer' },
    { value: 'pm', label: 'Product Manager' }
  ]

  return (
    <form>
      <h3>Advanced Form</h3>
      
      <FormFieldWithId
        label="Full Name"
        value={formData.name}
        onChange={e => handleInputChange('name', e.target.value)}
        placeholder="Your name..."
      />
      
      <FormFieldWithId
        label="Email"
        type="email"
        value={formData.email}
        onChange={e => handleInputChange('email', e.target.value)}
        placeholder="your@email.com"
      />
      
      <RadioGroupWithId
        name="category"
        options={categoryOptions}
        value={formData.category}
        onChange={e => handleInputChange('category', e.target.value)}
      />
      
      <CheckboxWithId
        label="Subscribe to the newsletter"
        checked={formData.newsletter}
        onChange={e => handleInputChange('newsletter', e.target.checked)}
      />
      
      <button type="submit">Send</button>
      
      <pre style={{ marginTop: '20px', background: '#f8f9fa', padding: '10px' }}>
        {JSON.stringify(formData, null, 2)}
      </pre>
    </form>
  )
}

function CheckboxWithId({ label, checked, onChange }) {
  const id = useId()
  
  return (
    <div style={{ marginBottom: '15px' }}>
      <input
        type="checkbox"
        id={id}
        checked={checked}
        onChange={onChange}
      />
      <label htmlFor={id} style={{ marginLeft: '8px' }}>
        {label}
      </label>
    </div>
  )
}
```## useTransition and useDeferredValue - Performance

### Update Priorities

```jsx
import { useState, useTransition, useDeferredValue, useMemo } from 'react'

function PerformanceDemo() {
  const [input, setInput] = useState('')
  const [isPending, startTransition] = useTransition()
  
  // Deferred version of the input for heavy calculations
  const deferredInput = useDeferredValue(input)
  
  // Simulation of a very expensive search
  const searchResults = useMemo(() => {
    if (!deferredInput) return []
    
    console.log('šŸ” Expensive search for:', deferredInput)
    
    // Simulation of 10000 results with calculations
    const results = []
    for (let i = 0; i < 10000; i++) {
      if (i.toString().includes(deferredInput)) {
        results.push({
          id: i,
          title: `Result ${i} for "${deferredInput}"`,
          score: Math.random() * 100
        })
      }
    }
    
    return results.slice(0, 100) // Limit to 100 results
  }, [deferredInput])

  const handleInputChange = (value) => {
    // Urgent update of the input (responsive)
    setInput(value)
    
    // Start a transition for less prioritized updates
    startTransition(() => {
      // This part will be deferred if the app is busy
      console.log('šŸ”„ Transition started for the search')
    })
  }

  return (
    <div>
      <h3>Performance with Transitions</h3>
      
      <div>
        <input
          value={input}
          onChange={e => handleInputChange(e.target.value)}
          placeholder="Search (type numbers)..."
          style={{ 
            padding: '10px', 
            width: '300px',
            fontSize: '16px'
          }}
        />
        {isPending && <span style={{ marginLeft: '10px' }}>ā³ Searching...</span>}
      </div>

      <div style={{ marginTop: '20px' }}>
        <p>
          Current input: "{input}" | 
          Searching for: "{deferredInput}" | 
          Results: {searchResults.length}
        </p>
        
        {deferredInput !== input && (
          <p style={{ color: '#007bff' }}>
            šŸ”„ Searching for "{input}"...
          </p>
        )}
      </div>

      <div style={{ 
        maxHeight: '300px', 
        overflow: 'auto',
        border: '1px solid #ddd',
        marginTop: '10px'
      }}>
        {searchResults.map(result => (
          <div key={result.id} style={{ 
            padding: '8px', 
            borderBottom: '1px solid #eee' 
          }}>
            <strong>{result.title}</strong>
            <span style={{ 
              float: 'right', 
              color: '#666',
              fontSize: '14px'
            }}>
              Score: {result.score.toFixed(2)}
            </span>
          </div>
        ))}
      </div>
    </div>
  )
}

Practical Case: Filtered List

function LargeFilterableList() {
  const [filter, setFilter] = useState('')
  const [sortBy, setSortBy] = useState('name')
  const [isPending, startTransition] = useTransition()
  
  const deferredFilter = useDeferredValue(filter)
  const deferredSortBy = useDeferredValue(sortBy)

  // Simulated large dataset
  const allItems = useMemo(() => {
    return Array.from({ length: 50000 }, (_, i) => ({
      id: i,
      name: `Item ${i}`,
      category: ['A', 'B', 'C'][i % 3],
      price: Math.random() * 100,
      rating: Math.random() * 5
    }))
  }, [])

  // Deferred (non-blocking) filtering and sorting
  const processedItems = useMemo(() => {
    console.log('šŸ” Processing items with filter:', deferredFilter)
    
    let filtered = allItems
    
    if (deferredFilter) {
      filtered = allItems.filter(item =>
        item.name.toLowerCase().includes(deferredFilter.toLowerCase()) ||
        item.category.toLowerCase().includes(deferredFilter.toLowerCase())
      )
    }
    
    // Sorting
    filtered.sort((a, b) => {
      switch (deferredSortBy) {
        case 'name': return a.name.localeCompare(b.name)
        case 'price': return b.price - a.price // Descending
        case 'rating': return b.rating - a.rating // Descending
        default: return 0
      }
    })
    
    return filtered.slice(0, 1000) // Limit the display
  }, [allItems, deferredFilter, deferredSortBy])

  const handleFilterChange = (value) => {
    setFilter(value)
    
    startTransition(() => {
      // This update is deferred
      console.log('šŸ”„ Filter transition started')
    })
  }

  const handleSortChange = (value) => {
    startTransition(() => {
      setSortBy(value)
    })
  }

  return (
    <div>
      <h3>Filterable List Performance ({allItems.length} items)</h3>
      
      <div style={{ marginBottom: '20px' }}>
        <input
          value={filter}
          onChange={e => handleFilterChange(e.target.value)}
          placeholder="Filter by name or category..."
          style={{ 
            padding: '8px', 
            marginRight: '10px',
            width: '250px'
          }}
        />
        
        <select
          value={sortBy}
          onChange={e => handleSortChange(e.target.value)}
          style={{ padding: '8px' }}
        >
          <option value="name">Sort by name</option>
          <option value="price">Sort by price</option>
          <option value="rating">Sort by rating</option>
        </select>
        
        {isPending && <span style={{ marginLeft: '10px' }}>ā³ Processing...</span>}
      </div>

      <div style={{ marginBottom: '10px' }}>
        <p>
          Filter: "{filter}" | 
          Processing: "{deferredFilter}" | 
          Results: {processedItems.length}
        </p>
      </div>

      <div style={{ 
        height: '400px', 
        overflow: 'auto',
        border: '1px solid #ddd'
      }}>
        {processedItems.map(item => (
          <div key={item.id} style={{
            padding: '12px',
            borderBottom: '1px solid #eee',
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center'
          }}>
            <div>
              <strong>{item.name}</strong>
              <span style={{ 
                marginLeft: '10px', 
                color: '#666',
                fontSize: '14px'
              }}>
                Category: {item.category}
              </span>
            </div>
            <div style={{ textAlign: 'right' }}>
              <div>{item.price.toFixed(2)}€</div>
              <div>⭐ {item.rating.toFixed(1)}</div>
            </div>
          </div>
        ))}
      </div>
    </div>
  )
}

useImperativeHandle - Custom APIs

Forward Ref with Custom API

import { forwardRef, useImperativeHandle, useRef, useState } from 'react'

// Component with custom API exposed
const AdvancedInput = forwardRef((props, ref) => {
  const inputRef = useRef()
  const [history, setHistory] = useState([])
  const [currentValue, setCurrentValue] = useState('')

  // Expose a custom API to the parent
  useImperativeHandle(ref, () => ({
    // Focus methods
    focus: () => inputRef.current?.focus(),
    blur: () => inputRef.current?.blur(),
    
    // Value methods
    getValue: () => currentValue,
    setValue: (value) => {
      setCurrentValue(value)
      addToHistory(value)
    },
    clear: () => {
      setCurrentValue('')
      inputRef.current?.focus()
    },
    
    // Selection methods
    selectAll: () => inputRef.current?.select(),
    getSelection: () => ({
      start: inputRef.current?.selectionStart || 0,
      end: inputRef.current?.selectionEnd || 0
    }),
    setSelection: (start, end) => {
      inputRef.current?.setSelectionRange(start, end)
    },
    
    // History
    getHistory: () => history,
    clearHistory: () => setHistory([]),
    undo: () => {
      if (history.length > 1) {
        const newHistory = history.slice(0, -1)
        const previousValue = newHistory[newHistory.length - 1] || ''
        setHistory(newHistory)
        setCurrentValue(previousValue)
        return previousValue
      }
      return currentValue
    },
    
    // Validation
    isValid: () => {
      if (props.required && !currentValue.trim()) return false
      if (props.minLength && currentValue.length < props.minLength) return false
      if (props.pattern && !new RegExp(props.pattern).test(currentValue)) return false
      return true
    },
    
    // Animation
    shake: () => {
      if (inputRef.current) {
        inputRef.current.style.animation = 'shake 0.5s'
        setTimeout(() => {
          if (inputRef.current) {
            inputRef.current.style.animation = ''
          }
        }, 500)
      }
    }
  }), [currentValue, history, props.required, props.minLength, props.pattern])

  const addToHistory = (value) => {
    setHistory(prev => {
      const newHistory = [...prev, value]
      return newHistory.slice(-10) // Keep only the last 10
    })
  }

  const handleChange = (e) => {
    const newValue = e.target.value
    setCurrentValue(newValue)
    addToHistory(newValue)
    props.onChange?.(e)
  }

  return (
    <div>
      <input
        ref={inputRef}
        {...props}
        value={currentValue}
        onChange={handleChange}
        style={{
          padding: '10px',
          border: '2px solid #ddd',
          borderRadius: '4px',
          fontSize: '16px',
          ...props.style
        }}
      />
      
      <style jsx>{`
        @keyframes shake {
          0%, 100% { transform: translateX(0); }
          10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
          20%, 40%, 60%, 80% { transform: translateX(5px); }
        }
      `}</style>
    </div>
  )
})

AdvancedInput.displayName = 'AdvancedInput'

// Using the component with custom API
function ImperativeHandleDemo() {
  const inputRef1 = useRef()
  const inputRef2 = useRef()
  const inputRef3 = useRef()

  const handleValidate = () => {
    const inputs = [inputRef1, inputRef2, inputRef3]
    let allValid = true

    inputs.forEach(inputRef => {
      if (inputRef.current && !inputRef.current.isValid()) {
        inputRef.current.shake()
        allValid = false
      }
    })

    if (allValid) {
      alert('All fields are valid!')
    } else {
      alert('Some fields are not valid')
    }
  }

  const handleGetValues = () => {
    const values = {
      name: inputRef1.current?.getValue() || '',
      email: inputRef2.current?.getValue() || '',
      phone: inputRef3.current?.getValue() || ''
    }
    
    console.log('Current values:', values)
    alert(JSON.stringify(values, null, 2))
  }

  const handleClearAll = () => {
    ;[inputRef1, inputRef2, inputRef3].forEach(ref => {
      ref.current?.clear()
    })
  }

  const handleFillExample = () => {
    inputRef1.current?.setValue('Andy Cinquin')
    inputRef2.current?.setValue('andy@example.com')
    inputRef3.current?.setValue('0123456789')
  }

  return (
    <div>
      <h3>useImperativeHandle Demo</h3>
      
      <div style={{ display: 'flex', flexDirection: 'column', gap: '20px', maxWidth: '400px' }}>
        <div>
          <label style={{ display: 'block', marginBottom: '5px' }}>
            Name (required, min 3 characters)
          </label>
          <AdvancedInput
            ref={inputRef1}
            placeholder="Your name..."
            required
            minLength={3}
          />
        </div>

        <div>
          <label style={{ display: 'block', marginBottom: '5px' }}>
            Email (required, email format)
          </label>
          <AdvancedInput
            ref={inputRef2}
            placeholder="your@email.com"
            required
            pattern="[^@]+@[^@]+\.[^@]+"
          />
        </div>

        <div>
          <label style={{ display: 'block', marginBottom: '5px' }}>
            Phone (10 digits)
          </label>
          <AdvancedInput
            ref={inputRef3}
            placeholder="0123456789"
            pattern="[0-9]{10}"
          />
        </div>
      </div>

      <div style={{ marginTop: '20px', display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
        <button onClick={() => inputRef1.current?.focus()}>
          Focus Name
        </button>
        <button onClick={() => inputRef2.current?.selectAll()}>
          Select Email
        </button>
        <button onClick={() => inputRef3.current?.undo()}>
          Undo Phone
        </button>
        <button onClick={handleValidate}>
          Validate Form
        </button>
        <button onClick={handleGetValues}>
          See Values
        </button>
        <button onClick={handleClearAll}>
          Clear All
        </button>
        <button onClick={handleFillExample}>
          Fill Example
        </button>
      </div>

      <div style={{ marginTop: '20px' }}>
        <button onClick={() => {
          const history = inputRef1.current?.getHistory() || []
          console.log('Name history:', history)
          alert('History: ' + history.join(' → '))
        }}>
          See Name History
        </button>
      </div>
    </div>
  )
}

useDebugValue - Debug Custom Hooks

Debug Hook

import { useDebugValue, useState, useEffect } from 'react'

// Custom hook with debug
function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(navigator.onLine)

  // Display debug info in React DevTools
  useDebugValue(isOnline ? 'Online' : 'Offline')

  useEffect(() => {
    const handleOnline = () => setIsOnline(true)
    const handleOffline = () => setIsOnline(false)

    window.addEventListener('online', handleOnline)
    window.addEventListener('offline', handleOffline)

    return () => {
      window.removeEventListener('online', handleOnline)
      window.removeEventListener('offline', handleOffline)
    }
  }, [])

  return isOnline
}

// Custom hook with complex debug
function useApi(url) {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)

  // Debug value with object and conditional formatting
  useDebugValue(
    { url, loading, hasData: !!data, error: !!error },
    ({ url, loading, hasData, error }) => {
      if (loading) return `šŸ”„ Loading: ${url}`
      if (error) return `āŒ Error: ${url}`
      if (hasData) return `āœ… Success: ${url}`
      return `ā³ Idle: ${url}`
    }
  )

  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true)
        setError(null)
        const response = await fetch(url)
        if (!response.ok) throw new Error(`HTTP ${response.status}`)
        const result = await response.json()
        setData(result)
      } catch (err) {
        setError(err)
      } finally {
        setLoading(false)
      }
    }

    fetchData()
  }, [url])

  return { data, loading, error }
}

// Custom hook for form with detailed debug
function useForm(initialValues, validation) {
  const [values, setValues] = useState(initialValues)
  const [errors, setErrors] = useState({})
  const [touched, setTouched] = useState({})

  // Complete debug info
  useDebugValue(
    {
      fieldCount: Object.keys(values).length,
      errorCount: Object.keys(errors).length,
      touchedCount: Object.keys(touched).length,
      isValid: Object.keys(errors).length === 0,
      values,
      errors
    },
    (info) => {
      const status = info.isValid ? 'āœ…' : 'āŒ'
      return `${status} Form: ${info.fieldCount} fields, ${info.errorCount} errors, ${info.touchedCount} touched`
    }
  )

  const setValue = (field, value) => {
    setValues(prev => ({ ...prev, [field]: value }))
    
    // Real-time validation
    if (validation && validation[field]) {
      const error = validation[field](value)
      setErrors(prev => ({
        ...prev,
        [field]: error
      }))
    }
  }

  const setTouched = (field) => {
    setTouched(prev => ({ ...prev, [field]: true }))
  }

  const reset = () => {
    setValues(initialValues)
    setErrors({})
    setTouched({})
  }

  return {
    values,
    errors,
    touched,
    setValue,
    setTouched,
    reset,
    isValid: Object.keys(errors).length === 0
  }
}

// Usage with debug
function DebugDemo() {
  const isOnline = useOnlineStatus()
  const { data: users, loading, error } = useApi('https://jsonplaceholder.typicode.com/users?_limit=3')
  
  const form = useForm(
    { name: '', email: '' },
    {
      name: (value) => value.length < 3 ? 'Too short name' : null,
      email: (value) => !/\S+@\S+\.\S+/.test(value) ? 'Invalid email' : null
    }
  )

  return (
    <div>
      <h3>Debug Demo - Open React DevTools! šŸ› ļø</h3>
      
      <div style={{ marginBottom: '20px' }}>
        <p>Connection status: {isOnline ? '🟢 Online' : 'šŸ”“ Offline'}</p>
        <p>API: {loading ? 'Loading...' : error ? 'Error' : `${users?.length || 0} users`}</p>
      </div>

      <form>
        <div>
          <label>
            Name:
            <input
              value={form.values.name}
              onChange={e => form.setValue('name', e.target.value)}
              onBlur={() => form.setTouched('name')}
            />
          </label>
          {form.errors.name && form.touched.name && (
            <span style={{ color: 'red' }}>{form.errors.name}</span>
          )}
        </div>

        <div>
          <label>
            Email:
            <input
              value={form.values.email}
              onChange={e => form.setValue('email', e.target.value)}
              onBlur={() => form.setTouched('email')}
            />
          </label>
          {form.errors.email && form.touched.email && (
            <span style={{ color: 'red' }}>{form.errors.email}</span>
          )}
        </div>

        <button type="button" onClick={form.reset}>
          Reset
        </button>
      </form>

      <div style={{ marginTop: '20px', background: '#f8f9fa', padding: '10px' }}>
        <h4>Open React DevTools to see the debug info!</h4>
        <p>In the Components tab, look for the custom hooks</p>
        <p>Valid form: {form.isValid ? 'āœ…' : 'āŒ'}</p>
      </div>
    </div>
  )
}
```## Resources For Further Learning

- šŸ“š [useLayoutEffect Documentation](https://react.dev/reference/react/useLayoutEffect)
- šŸ†” [useId Documentation](https://react.dev/reference/react/useId)
- ⚔ [useTransition Documentation](https://react.dev/reference/react/useTransition)
- šŸ”„ [useDeferredValue Documentation](https://react.dev/reference/react/useDeferredValue)
- šŸŽÆ [useImperativeHandle Documentation](https://react.dev/reference/react/useImperativeHandle)