OBJECTIVE
I'm developing a UI chat interface as a project to learn React + TS. When a chat message is long enough and so provides enough vertical space, its action buttons (such as copy message, share, etc.) should appear vertically stacked to the right of the message's container. When there isn't enough space, meaning the stacked buttons would be taller than the message's container, the buttons shouldn't appear at all:
PROBLEM
The code below isn't working. The buttons still appear as normal. Strangely, when the window is resized, the button's visibility toggles and they either disappear/reappear depending on whether they are currently showing (because of React's hot reload feature, either state can be the starting one).
WHAT I HAVE TRIED
import React, { useLayoutEffect, useRef, useState, useCallback } from "react";
import lodash from 'lodash';
// import statements removed for brevity
// props interface removed for brevity
const Message: React.FC<MessageProps> = ({ sender, messageContents, actionButtons, children }) => {
const chatMessageRef = useRef<HTMLDivElement>(null);
const sideButtonsRef = useRef<HTMLDivElement>(null);
const [showSideButtons, setShowSideButtons] = useState(true);
const checkAndSetButtonVisibility = useCallback(() => {
const messageContainer = chatMessageRef.current;
const sideButtons = sideButtonsRef.current;
if (messageContainer && sideButtons) {
setShowSideButtons(messageContainer.offsetHeight > sideButtons.offsetHeight);
}
}, []);
// Using lodash's throttle function to limit how often the checkAndSetButtonVisibility function can be invoked
const debouncedCheck = useCallback(lodash.debounce(checkAndSetButtonVisibility, 1500), [checkAndSetButtonVisibility]);
useLayoutEffect(() => {
window.addEventListener('resize', debouncedCheck);
checkAndSetButtonVisibility(); // Initial check for the correct state
return () => {
window.removeEventListener('resize', debouncedCheck);
debouncedCheck.cancel(); // Cancel any pending execution of the throttled function on cleanup
};
}, [debouncedCheck, checkAndSetButtonVisibility]);
return (
<div className={`chat-message-outer-container ${sender.toLowerCase()}`}>
<div className="chat-message-inner-container">
<div className="chat-message-info">
{sender === MessageSender.Agent ? (
<div className="chat-list-item-ai-model-logo-message">
<img className={"message-avatar"} src={aiLogo} alt="AI Model Logo" />
</div>
) : (
<div className="account-avatar-message">
<img className={"message-avatar"} src={userAvatar} alt="User avatar" />
</div>
)}
<div className="chat-message-info-sender">{sender}</div>
</div>
<div className="chat-message-select-checkbox">
<label className="button checkbox-field">
<input type="checkbox"/>
<span>
<br/>
</span>
</label>
</div>
<div className="chat-message-wrapper">
<div ref={chatMessageRef} className={`chat-message ${sender.toLowerCase()}-message`}>
<div className="chat-message-contents">{messageContents}</div>
</div>
</div>
<div
ref={sideButtonsRef}
className="chat-message-buttons-side"
style={{ display: showSideButtons ? 'flex' : 'none' }} // Use the state to control visibility
>
{actionButtons}
</div>
<div className="chat-message-bottom">
{children}
<div className="chat-message-action-row">
<div className="chat-message-tag-list">
<Tag tagName={'Tag name'}/>
<AddElementButton type={"new-tag-message"}/>
</div>
<div className="chat-message-buttons-bottom">
{actionButtons}
</div>
</div>
</div>
</div>
</div>
);
};
If I understand your code correctly, your decision whether to hide or show the buttons depends on the height of the button panel, which will change as soon as you set display:none
, which in turn can change the visibility of the button panel. I would not be surprised if this leads to weird feedback effects.
You can try to hide the button panel by setting visibility:hidden
instead of display:none
, which should ensure the button panel keeps its height value. If the hidden element interferes with the overall layout because it still takes up space, you can additionally set position:absolute
so the chat message input can take up the free space.