use-scope-value.ts 1.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
  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. export default function useScopeValue<T = any>(
  16. path: string, // dot '.' path of a property in the Angular scope
  17. deep = false
  18. ): [T, Dispatch<SetStateAction<T>>] {
  19. const { $scope } = useIdeContext()
  20. const [value, setValue] = useState<T>(() => _.get($scope, path))
  21. useEffect(() => {
  22. return $scope.$watch(
  23. path,
  24. (newValue: T) => {
  25. setValue(() => {
  26. // NOTE: this is deliberately wrapped in a function,
  27. // to avoid calling setValue directly with a value that's a function
  28. return deep ? _.cloneDeep(newValue) : newValue
  29. })
  30. },
  31. deep
  32. )
  33. }, [path, $scope, deep])
  34. const scopeSetter = useCallback(
  35. (newValue: SetStateAction<T>) => {
  36. setValue(val => {
  37. const actualNewValue = _.isFunction(newValue) ? newValue(val) : newValue
  38. $scope.$applyAsync(() => _.set($scope, path, actualNewValue))
  39. return actualNewValue
  40. })
  41. },
  42. [path, $scope]
  43. )
  44. return [value, scopeSetter]
  45. }