After searching the subject "Detect outside click React Hooks component", I can't figure out the solution for improvement performance my current application.
Context: I have multiple React components:
- App: root component, has
itemSelecting
state to detect current item selecting (FirstComponent
orSecondComponent
, two different components). It has anmousedown/mouseup
event listener to detect outside clickFirstComponent
orSecondComponent
- App: root component, has
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
import './styles.css';
import FirstComponent from './components/FirstComponent';
import SecondComponent from './components/SecondComponent';
import DropDownComponent from './components/DropdownComponent';
function App() {
const [itemSelecting, setItemSelecting] = useState(0);
function handleClickOutside(event) {
// FIXME: how to detect outside click 4 components
// console.log(event.target);
if (itemSelecting !== -1) setItemSelecting(0);
console.log('click first/second component');
}
useEffect(() => {
if (itemSelecting !== -1) {
document.addEventListener('mousedown', handleClickOutside);
} else {
document.removeEventListener('mousedown', handleClickOutside);
}
}, []);
const handleClick = value => {
if (value === itemSelecting) setItemSelecting(0);
else setItemSelecting(value);
};
return (
<React.Fragment>
<div className="App">
<FirstComponent
label="01"
selected={itemSelecting === 1}
handleClick={() => handleClick(1)}
/>
<SecondComponent
label="02"
selected={itemSelecting === 2}
handleClick={() => handleClick(2)}
/>
<FirstComponent
label="03"
selected={itemSelecting === 3}
handleClick={() => handleClick(3)}
/>
<SecondComponent
label="04"
selected={itemSelecting === 4}
handleClick={() => handleClick(4)}
/>
</div>
<hr />
<DropDownComponent />
</React.Fragment>
);
}
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
- FirstComponent: the square component has
selected
props passed byApp
. It can be selected (inside click) / unselected (outside click or click again when its selected)
import React from 'react';
import cn from 'classnames';
import styles from '../styles.module.css';
const FirstComponent = ({ label, selected, handleClick }) => {
const selectedClassName = selected ? styles.selected : '';
return (
<div
className={cn([styles.component, styles.first, selectedClassName])}
onClick={handleClick}>
<span>{label}</span>
</div>
);
};
export default FirstComponent;
- SecondComponent: the circle component has
selected
props passed byApp
. It can be selected (inside click) / unselected (outside click or click again when its selected)
import React from 'react';
import cn from 'classnames';
import styles from '../styles.module.css';
const SecondComponent = ({ label, selected, handleClick }) => {
const selectedClassName = selected ? styles.selected : '';
return (
<div
className={cn([styles.component, styles.second, selectedClassName])}
onClick={handleClick}>
<span>{label}</span>
</div>
);
};
export default SecondComponent;
- DropdownComponent: the dropdown component can be expanded (
inside click
) / collapsed (outside click
). It has anothermousedown/mouseup
event listener to detect outside clickDropDownComponent
.
import React, { useState, useRef, useEffect } from 'react';
import PropTypes from 'prop-types';
import styles from '../styles.module.css';
const DropDownComponent = () => {
const nodeRef = useRef(null);
const listRef = useRef(null);
const [isOpen, setIsOpen] = useState(false);
const handleClick = event => {
if (nodeRef && nodeRef.current.contains(event.target)) {
// inside click
if (
listRef &&
listRef.current &&
listRef.current.contains(event.target)
) {
setTimeout(() => {
setIsOpen(false);
}, 500);
}
return;
}
setIsOpen(false);
console.log('outside click dropdown');
};
useEffect(() => {
document.addEventListener('mousedown', handleClick);
return () => document.removeEventListener('mousedown', handleClick);
}, []);
return (
<div ref={nodeRef} className={styles.dropdown}>
<button className={styles.dropbtn} onClick={() => setIsOpen(!isOpen)}>
Dropdown
</button>
{isOpen && (
<div ref={listRef} className={styles.dropdownContent}>
<a>Link 1</a>
<a>Link 2</a>
<a>Link 3</a>
</div>
)}
</div>
);
};
DropDownComponent.defaultProps = {
classNameType: null,
isOpen: false,
handleIsOpen: () => {},
title: null,
};
DropDownComponent.propTypes = {
classNameType: PropTypes.string,
isOpen: PropTypes.bool,
handleIsOpen: PropTypes.func,
title: PropTypes.string,
};
export default DropDownComponent;
- Problem: In the same time, it have some outside click event listener redundant.
Example: when clicking on one of
FirstComponent
/SecondComponent
, it appears:
outside click dropdown
click first/second component
- Target: how can I detect once at a time the outside click event listener on multiple components?
I want to optimize: when clicking on one of
FirstComponent
/SecondComponent
, only outside clickDropdownComponent
is invoked.
Note: In fact, I would like to implement ref (useRef
) inside each components (FirstComponent
/ SecondComponent
) to resolve it but I don't know how (FIXME in App.js)
The source code demo is on CodeSandbox.
Thanks in advance.