All files / src/hooks/useNewCompute useNewCompute.ts

100% Statements 16/16
100% Branches 6/6
100% Functions 5/5
100% Lines 14/14

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 852x   2x                                                                                                                         2x 9x 9x 9x   9x   9x 9x 4x       9x 5x   5x     14x    
import { type DependencyList, useEffect, useRef } from 'react'
import type { Reaction } from 'watch-state'
import { Compute } from 'watch-state'
 
/**
 * Creates a `Compute` instance that can be passed as a static prop to child components.
 * Reactivity is activated only in components that call `useWatch` on the Observable.
 * This pattern optimizes re-renders: parent components don't re-render when computed values change.
 *
 * @template T - The type of the computed value
 * @param watcher - A function that returns the computed value. This function can access reactive `State` or `Compute` instances.
 * @param deps - Optional dependency array that triggers recomputation when values change. Use this for props or external values that should trigger an update.
 * @returns A `Compute` instance containing the computed value.
 *
 * @example
 * ```tsx
 * // Basic usage //
 *
 * import { State } from 'watch-state'
 * import { useWatch, useNewCompute } from '@watch-state/react'
 *
 * const $name = new State('Mike')
 * const $surname = new State('Deight')
 *
 * const Parent = () => {
 *   const $fullName = useNewCompute(() => {
 *     return `${$name.value} ${$surname.value[0]}.`
 *   })
 *
 *   return <Child $fullName={$fullName} />
 * }
 *
 * const Child = ({ $fullName }) => {
 *   const fullName = useWatch($fullName)
 *
 *   return <div>{fullName}</div>
 * }
 * ```
 *
 * @example
 * ```tsx
 * // With dependencies //
 *
 * import { State } from 'watch-state'
 * import { useWatch, useNewCompute } from '@watch-state/react'
 *
 * const $name = new State('Mike')
 *
 * const Parent = ({ surname }) => {
 *   const $fullName = useNewCompute(() => {
 *     return `${$name.value} ${surname[0]}.`
 *   }, [surname])
 *
 *   return <Child $fullName={$fullName} />
 * }
 *
 * const Child = ({ $fullName }) => {
 *   const fullName = useWatch($fullName)
 *
 *   return <div>{fullName}</div>
 * }
 * ```
 */
export function useNewCompute <T> (watcher: Reaction<T>, deps?: DependencyList) {
  const result = useRef<Compute<T>>()
  const watcherRef = useRef(watcher)
  const updateRef = useRef(false)
 
  watcherRef.current = watcher
 
  useEffect(() => {
    if (deps && updateRef.current) {
      result.current.update()
    }
  }, deps)
 
  useEffect(() => {
    updateRef.current = true
 
    return () => result.current.destroy()
  }, [])
 
  return result.current || (result.current = new Compute(() => watcherRef.current(), true))
}