use-scope-value.js 1.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
  1. import { useCallback, useEffect, useState } from 'react'
  2. import PropTypes from 'prop-types'
  3. import _ from 'lodash'
  4. import { useIdeContext } from '../context/ide-context'
  5. /**
  6. * Binds a property in an Angular scope making it accessible in a React
  7. * component. The interface is compatible with React.useState(), including
  8. * the option of passing a function to the setter.
  9. *
  10. * @param {string} path - dot '.' path of a property in the Angular scope.
  11. * @param {boolean} deep
  12. * @returns {[any, function]} - Binded value and setter function tuple.
  13. */
  14. export default function useScopeValue(path, deep = false) {
  15. const { $scope } = useIdeContext({
  16. $scope: PropTypes.object.isRequired,
  17. })
  18. const [value, setValue] = useState(() => _.get($scope, path))
  19. useEffect(() => {
  20. return $scope.$watch(
  21. path,
  22. newValue => {
  23. setValue(() => {
  24. // NOTE: this is deliberately wrapped in a function,
  25. // to avoid calling setValue directly with a value that's a function
  26. return deep ? _.cloneDeep(newValue) : newValue
  27. })
  28. },
  29. deep
  30. )
  31. }, [path, $scope, deep])
  32. const scopeSetter = useCallback(
  33. newValue => {
  34. setValue(val => {
  35. const actualNewValue = _.isFunction(newValue) ? newValue(val) : newValue
  36. $scope.$applyAsync(() => _.set($scope, path, actualNewValue))
  37. return actualNewValue
  38. })
  39. },
  40. [path, $scope]
  41. )
  42. return [value, scopeSetter]
  43. }