From f944ca0c0cc3f7d52125763e8b1d535183e8d3b5 Mon Sep 17 00:00:00 2001 From: Nicko Winner-Arroyo Date: Thu, 2 Jul 2020 12:24:59 -0400 Subject: [PATCH 1/2] search "clear icon" now always shows when value changes --- src/Search/SearchAssistance.js | 2 +- src/Search/index.js | 27 +++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) 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..068b592 100644 --- a/src/Search/index.js +++ b/src/Search/index.js @@ -1,6 +1,6 @@ import classnames from 'classnames'; import PropTypes from 'prop-types'; -import React from 'react'; +import React, { useEffect, useRef } from 'react'; import Icon from '../Icon'; import { wcBool } from '../utils'; @@ -18,11 +18,34 @@ const Search = ({ optional, required, wrapperId, + value, ...rest }) => { + /** + * Show clear icon when value changes programmatically by triggering 'input' event manually. + */ + const inputRef = useRef(); + useEffect(() => { + 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 ( - + From 39acd91ca621654a3a058deff41d1be283652633 Mon Sep 17 00:00:00 2001 From: Nicko Winner-Arroyo Date: Thu, 2 Jul 2020 12:39:34 -0400 Subject: [PATCH 2/2] Only watch for changes after component is mounted --- src/Search/index.js | 6 ++++-- src/hooks/useEffectExceptOnMounted.js | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 src/hooks/useEffectExceptOnMounted.js diff --git a/src/Search/index.js b/src/Search/index.js index 068b592..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, { useEffect, useRef } 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/ @@ -23,9 +24,10 @@ const Search = ({ }) => { /** * 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(); - useEffect(() => { + useEffectExceptOnMount(() => { const input = inputRef.current; const nativeInputValueSetter = Object.getOwnPropertyDescriptor( window.HTMLInputElement.prototype, 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); + }, []); +};