\$\begingroup\$
\$\endgroup\$
3
Rate my React dropdown that auto closes when the user clicks outside of it. This is an example in Typescript that I did for practice.
import React, {useEffect, useRef, useState} from "react";
import "./Dropdown.css";
import classNames from "classnames";
interface DropdownProps {
label: string;
}
const Dropdown: React.FC<DropdownProps> = ({children, label}) => {
const [showDropdown, setShowDropdown] = useState<boolean>(false);
const dropdownContents = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClick = (event: MouseEvent) => {
if (
dropdownContents.current &&
event.target instanceof Node &&
!dropdownContents.current.contains(event.target)) {
setShowDropdown(false);
}
};
document.addEventListener("click", handleClick);
return () => document.removeEventListener("click", handleClick);
}, []);
return (
<div ref={dropdownContents} className="dropdown-wrapper">
<button className="dropdown-btn" onClick={() => setShowDropdown((show) => !show)}>{label}</button>
<div className={classNames(["dropdown-contents"], {visible: showDropdown})}>
{children}
</div>
</div>
)
}
export default Dropdown;
.dropdown-wrapper {
position: relative;
}
.dropdown-btn {
border-radius: 0;
border: 1px solid black;
padding: 0.5rem 1rem;
background: #fff;
font-weight: bold;
}
.dropdown-btn:focus {
outline: 1px solid blue;
}
.dropdown-btn:hover {
background: #efefef;
}
.dropdown-btn:active {
background: #bbb;
}
.dropdown-contents {
position: absolute;
left: 0;
top: 100%;
display: none;
-webkit-box-shadow: 5px 5px 15px 5px #000000;
box-shadow: 5px 5px 15px 5px #a8a8a8;
padding: 0.5rem;
}
.visible {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
asked Mar 11, 2022 at 2:01
1 Answer 1
\$\begingroup\$
\$\endgroup\$
Good job in getting something handcrafted to work; must have been a fun learning experience!
With a production / longer term view on things, here's my review on what you've done:
- The "click outside" implementation was probably a good learning experience. But, in production I wouldn't reinvent such a common utility as this. Instead, I'd use something like
useOutSideClick
. It makes the code more focused and elegant to read. - Destructure the import for
FC
(you have done for everything else fromreact
), rather than the inlineReact.FC
. It's consistent and it may even help treeshaking. - Take advantage of TypeScript inference for the
useState
usage; no need to explictly set this toboolean
. - There's a conspicuous lack of state management with the children of the dropdown component - is that left to the consumer? E.g. maintaining a "selected" item state, etc. Perhaps you felt that was out of scope for this review...
- I appreciate you're probably not after this advice, having handcrafted this component yourself: but for such a common UI element, I'd recommend an off the shelf dropdown component, such as React Select.
- Consider a CSS-in-JS solution, to avoid the global CSS styles. Something like Styled Components. This might be a bit out of scope here though.
To conclude, here's my refactored code based on some of the above points:
import React, { useRef, useState, FC } from "react";
import "./Dropdown.css";
import classNames from "classnames";
import { useOutsideClick } from "rooks";
interface DropdownProps {
label: string;
}
const Dropdown: FC<DropdownProps> = ({ children, label }) => {
const [showDropdown, setShowDropdown] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useOutsideClick(ref, () => setShowDropdown(false));
return (
<div ref={ref} className="dropdown-wrapper">
<button
className="dropdown-btn"
onClick={() => setShowDropdown((show) => !show)}
>
{label}
</button>
<div
className={classNames(["dropdown-contents"], { visible: showDropdown })}
>
{children}
</div>
</div>
);
};
export default Dropdown;
Good luck and have fun with your component!
answered Mar 16, 2022 at 15:16
lang-js
import React, {useEffect, useRef, useState} from "react";
toconst {useEffect, useRef, useState} - React;
. See this MSO post for more information. \$\endgroup\$