UNPKG

@tldraw/state-react

Version:

tldraw infinite canvas SDK (react bindings for state).

8 lines (7 loc) 5.24 kB
{ "version": 3, "sources": ["../../src/lib/track.ts"], "sourcesContent": ["import React, { forwardRef, FunctionComponent, memo } from 'react'\nimport { useStateTracking } from './useStateTracking'\n\n/**\n * Proxy handlers object used to intercept function calls to React components.\n * This enables automatic signal tracking by wrapping component execution\n * in reactive tracking context.\n *\n * The proxy intercepts the function call (apply trap) and wraps it with\n * useStateTracking to enable automatic dependency tracking for signals\n * accessed during render.\n *\n * @example\n * ```ts\n * // Used internally by track() function\n * const ProxiedComponent = new Proxy(MyComponent, ProxyHandlers)\n * ```\n *\n * @internal\n */\nexport const ProxyHandlers = {\n\t/**\n\t * This is a function call trap for functional components. When this is called, we know it means\n\t * React did run 'Component()', that means we can use any hooks here to setup our effect and\n\t * store.\n\t *\n\t * With the native Proxy, all other calls such as access/setting to/of properties will be\n\t * forwarded to the target Component, so we don't need to copy the Component's own or inherited\n\t * properties.\n\t *\n\t * @see https://github.com/facebook/react/blob/2d80a0cd690bb5650b6c8a6c079a87b5dc42bd15/packages/react-reconciler/src/ReactFiberHooks.old.js#L460\n\t */\n\tapply(Component: FunctionComponent, thisArg: any, argumentsList: any) {\n\t\t// eslint-disable-next-line react-hooks/rules-of-hooks\n\t\treturn useStateTracking(Component.displayName ?? Component.name ?? 'tracked(???)', () =>\n\t\t\tComponent.apply(thisArg, argumentsList)\n\t\t)\n\t},\n}\n\n/**\n * React internal symbol for identifying memoized components.\n * Used to detect if a component is already wrapped with React.memo().\n *\n * @example\n * ```ts\n * const isMemoComponent = component['$$typeof'] === ReactMemoSymbol\n * ```\n *\n * @internal\n */\nexport const ReactMemoSymbol = Symbol.for('react.memo')\n\n/**\n * React internal symbol for identifying forward ref components.\n * Used to detect if a component is wrapped with React.forwardRef().\n *\n * @example\n * ```ts\n * const isForwardRefComponent = component['$$typeof'] === ReactForwardRefSymbol\n * ```\n *\n * @internal\n */\nexport const ReactForwardRefSymbol = Symbol.for('react.forward_ref')\n\n/**\n * Returns a tracked version of the given component.\n * Any signals whose values are read while the component renders will be tracked.\n * If any of the tracked signals change later it will cause the component to re-render.\n *\n * This also wraps the component in a React.memo() call, so it will only re-render\n * when props change OR when any tracked signals change. This provides optimal\n * performance by preventing unnecessary re-renders while maintaining reactivity.\n *\n * The function handles special React component types like forwardRef and memo\n * components automatically, preserving their behavior while adding reactivity.\n *\n * @param baseComponent - The React functional component to make reactive to signal changes\n * @returns A memoized component that re-renders when props or tracked signals change\n *\n * @example\n * ```ts\n * import { atom } from '@tldraw/state'\n * import { track, useAtom } from '@tldraw/state-react'\n *\n * const Counter = track(function Counter(props: CounterProps) {\n * const count = useAtom('count', 0)\n * const increment = useCallback(() => count.set(count.get() + 1), [count])\n * return <button onClick={increment}>{count.get()}</button>\n * })\n *\n * // Component automatically re-renders when count signal changes\n * ```\n *\n * @example\n * ```ts\n * // Works with forwardRef components\n * const TrackedInput = track(React.forwardRef<HTMLInputElement, InputProps>(\n * function TrackedInput(props, ref) {\n * const theme = useValue(themeSignal)\n * return <input ref={ref} style={{ color: theme.textColor }} {...props} />\n * }\n * ))\n * ```\n *\n * @public\n */\nexport function track<T extends FunctionComponent<any>>(\n\tbaseComponent: T\n): React.NamedExoticComponent<React.ComponentProps<T>> {\n\tlet compare = null\n\tconst $$typeof = baseComponent['$$typeof' as keyof typeof baseComponent]\n\tif ($$typeof === ReactMemoSymbol) {\n\t\tbaseComponent = (baseComponent as any).type\n\t\tcompare = (baseComponent as any).compare\n\t}\n\tif ($$typeof === ReactForwardRefSymbol) {\n\t\treturn memo(forwardRef(new Proxy((baseComponent as any).render, ProxyHandlers) as any)) as any\n\t}\n\n\treturn memo(new Proxy(baseComponent, ProxyHandlers) as any, compare) as any\n}\n"], "mappings": "AAAA,SAAgB,YAA+B,YAAY;AAC3D,SAAS,wBAAwB;AAmB1B,MAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAY5B,MAAM,WAA8B,SAAc,eAAoB;AAErE,WAAO;AAAA,MAAiB,UAAU,eAAe,UAAU,QAAQ;AAAA,MAAgB,MAClF,UAAU,MAAM,SAAS,aAAa;AAAA,IACvC;AAAA,EACD;AACD;AAaO,MAAM,kBAAkB,OAAO,IAAI,YAAY;AAa/C,MAAM,wBAAwB,OAAO,IAAI,mBAAmB;AA4C5D,SAAS,MACf,eACsD;AACtD,MAAI,UAAU;AACd,QAAM,WAAW,cAAc,UAAwC;AACvE,MAAI,aAAa,iBAAiB;AACjC,oBAAiB,cAAsB;AACvC,cAAW,cAAsB;AAAA,EAClC;AACA,MAAI,aAAa,uBAAuB;AACvC,WAAO,KAAK,WAAW,IAAI,MAAO,cAAsB,QAAQ,aAAa,CAAQ,CAAC;AAAA,EACvF;AAEA,SAAO,KAAK,IAAI,MAAM,eAAe,aAAa,GAAU,OAAO;AACpE;", "names": [] }