use-scope-value.ts 1.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
  1. import {
  2. type Dispatch,
  3. type SetStateAction,
  4. useCallback,
  5. useEffect,
  6. useState,
  7. } from 'react'
  8. import _ from 'lodash'
  9. import { useIdeContext } from '../context/ide-context'
  10. /**
  11. * Binds a property in an Angular scope making it accessible in a React
  12. * component. The interface is compatible with React.useState(), including
  13. * the option of passing a function to the setter.
  14. *
  15. * The generic type is not an actual guarantee because the value for a path is
  16. * returned as undefined when there is nothing in the scope store for that path.
  17. */
  18. export default function useScopeValue<T = any>(
  19. path: string, // dot '.' path of a property in the Angular scope
  20. deep = false
  21. ): [T, Dispatch<SetStateAction<T>>] {
  22. const { scopeStore } = useIdeContext()
  23. const [value, setValue] = useState<T>(() => scopeStore.get(path))
  24. useEffect(() => {
  25. return scopeStore.watch<T>(
  26. path,
  27. (newValue: T) => {
  28. // NOTE: this is deliberately wrapped in a function,
  29. // to avoid calling setValue directly with a value that's a function
  30. setValue(() => newValue)
  31. },
  32. deep
  33. )
  34. }, [path, scopeStore, deep])
  35. const scopeSetter = useCallback(
  36. (newValue: SetStateAction<T>) => {
  37. setValue(val => {
  38. const actualNewValue = _.isFunction(newValue) ? newValue(val) : newValue
  39. scopeStore.set(path, actualNewValue)
  40. return actualNewValue
  41. })
  42. },
  43. [path, scopeStore]
  44. )
  45. return [value, scopeSetter]
  46. }