diff --git a/src/Search/SearchAssistance.js b/src/Search/SearchAssistance.js index 8175083..c9498d7 100644 --- a/src/Search/SearchAssistance.js +++ b/src/Search/SearchAssistance.js @@ -15,7 +15,7 @@ const SearchAssistance = ({ children, className, relativeTo, ...rest }) => { SearchAssistance.propTypes = { children: PropTypes.node.isRequired, - className: PropTypes.string, + className: PropTypes.string, relativeTo: PropTypes.string.isRequired, position: PropTypes.oneOf(POSITIONS), }; diff --git a/src/Search/index.js b/src/Search/index.js index 42f8e59..9e71335 100644 --- a/src/Search/index.js +++ b/src/Search/index.js @@ -1,8 +1,9 @@ import classnames from 'classnames'; import PropTypes from 'prop-types'; -import React from 'react'; +import React, { useRef } from 'react'; import Icon from '../Icon'; import { wcBool } from '../utils'; +import { useEffectExceptOnMount } from '../hooks/useEffectExceptOnMounted'; /** * @see https://helixdesignsystem.github.io/helix-ui/components/search/ @@ -18,11 +19,35 @@ const Search = ({ optional, required, wrapperId, + value, ...rest }) => { + /** + * Show clear icon when value changes programmatically by triggering 'input' event manually. + * @see https://stackoverflow.com/questions/23892547/what-is-the-best-way-to-trigger-onchange-event-in-react-js + */ + const inputRef = useRef(); + useEffectExceptOnMount(() => { + const input = inputRef.current; + const nativeInputValueSetter = Object.getOwnPropertyDescriptor( + window.HTMLInputElement.prototype, + 'value' + ).set; + nativeInputValueSetter.call(input, value); + const ev = new Event('input', { bubbles: true }); + input.dispatchEvent(ev); + }, [value]); + return ( - + diff --git a/src/hooks/useEffectExceptOnMounted.js b/src/hooks/useEffectExceptOnMounted.js new file mode 100644 index 0000000..89bdfa2 --- /dev/null +++ b/src/hooks/useEffectExceptOnMounted.js @@ -0,0 +1,23 @@ +import React from 'react'; +/** + * Identical to React.useEffect, except that it never runs on mount. This is the equivalent of + * the componentDidUpdate lifecycle function. + * @see https://stackoverflow.com/questions/53253940/make-react-useeffect-hook-not-run-on-initial-render + * @param {function:function} effect - A useEffect effect. + * @param {array} dependencies - useEffect dependency list. + */ +export const useEffectExceptOnMount = (effect, dependencies) => { + const mounted = React.useRef(false); + React.useEffect(() => { + if (mounted.current) { + const unmount = effect(); + return () => unmount && unmount(); + } else { + mounted.current = true; + } + }, dependencies); + + React.useEffect(() => { + return () => (mounted.current = false); + }, []); +};