I need to look at the sate of a prop inside of an eventhandler to decide on what should happen. But due to stale prop this dont work. I have solved this using useRef, but this will create new eventhandlers and cleaning them up dont work because the eventhandler method is a anonymous function. I dont know how to pass props to it and not create a anonymous function.
I have looked at these stackoverflow anwsers:
https://stackoverflow.com/a/62666824/3727466
https://stackoverflow.com/a/64770671/3727466
Do anyone have an idea to improve this?
Edit: To awnser my own question, I fixed this by creating a method inside of the useEffect. This creates a method with a reference and the eventhandler can remove itself when the props changes before adding a new listener. But I more than welcome other suggestions or solutions on how to handle this situation. Where you need to check some changing prop inside of an eventhandler.
EditTwo: Looks like the useEffectEvent will solve edgeCases like this in the future: https://react.dev/reference/react/experimental_useEffectEvent
https://react.dev/learn/separating-events-from-effects#declaring-an-effect-event
const SearchInputCollapsible = ({
handleSearchChange,
searchText,
alwaysOpen,
}) => {
const [isOpen, setIsOpen] = useState(alwaysOpen || false);
const ref = useRef();
const inputRef = useRef(null);
const getRef = (r) => {
if (inputRef.current !== r.current) inputRef.current = r.current;
};
const shouldClose = (event, text, open) => {
console.log(" ~ shouldClose")
if (text.length > 0) return;
if (event.type === "focusout") {
handleSearchChange("");
setIsOpen(false);
return;
}
const rect = ref.current?.getBoundingClientRect();
const isOutsideOfInput =
rect &&
(event.clientY < rect.top ||
event.clientY > rect.bottom ||
event.clientX < rect.left ||
event.clientX > rect.right);
if (open && !isOutsideOfInput) return;
handleSearchChange("");
setIsOpen(false);
};
useEffect(() => {
if (alwaysOpen) return;
const handleClose = (event) => {
shouldClose(event, searchText, isOpen);
};
if (isOpen) {
inputRef?.current?.focus();
document.addEventListener("mousedown", handleClose);
ref.current?.addEventListener("focusout", handleClose);
}
return () => {
document.removeEventListener("mousedown", handleClose);
ref.current?.removeEventListener("focusout", handleClose);
};
}, [isOpen, searchText]);
const clearAndClose = () => {
if (!alwaysOpen) setIsOpen(false);
handleSearchChange("");
};
const handleOpenCloseClick = () => {
if (alwaysOpen) return;
setIsOpen(!isOpen);
handleSearchChange("");
};
return (
<Wrapper {...{ isOpen, alwaysOpen, showClearButton: searchText }}>
<Icon {...{ onClick: handleOpenCloseClick }} />
<CollapseWrapper {...{ isOpen }}>
<InnerCollapseWrapper>
<Input
{...{
value: searchText,
handleEvent: (e) => handleSearchChange(e.target.value),
getRef,
}}
/>
</InnerCollapseWrapper>
</CollapseWrapper>
{isOpen && <CloseButton {...{ onClick: clearAndClose }} />}
</Wrapper>
);
};