use-scope-value.ts 1.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
  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. ): [T, Dispatch<SetStateAction<T>>] {
  21. const { scopeStore } = useIdeContext()
  22. const [value, setValue] = useState<T>(() => scopeStore.get(path))
  23. useEffect(() => {
  24. return scopeStore.watch<T>(path, (newValue: T) => {
  25. // NOTE: this is deliberately wrapped in a function,
  26. // to avoid calling setValue directly with a value that's a function
  27. setValue(() => newValue)
  28. })
  29. }, [path, scopeStore])
  30. const scopeSetter = useCallback(
  31. (newValue: SetStateAction<T>) => {
  32. setValue(val => {
  33. const actualNewValue = _.isFunction(newValue) ? newValue(val) : newValue
  34. scopeStore.set(path, actualNewValue)
  35. return actualNewValue
  36. })
  37. },
  38. [path, scopeStore]
  39. )
  40. return [value, scopeSetter]
  41. }