S
SolidJS•9mo ago
Xzayler

Dynamically rendering components

My objective in a nutshell is making a small user card appear on an element hover, like on twitter. The way I'm making it is the following: This is a barebones representation
// UserPopup.tsx
export default function UserPopup(props: { userHandle: string }) {
const [user] = createResource(() => {
return getUserSummary(props.userHandle);
});
// addFollow is a server function doing db stuff
const follow = useAction(action(addFollow));

return (
<Show when={user()} fallback={<div>Loading...</div>}>
<div>user()!.name</div>
<button onClick={() => follow(user()!.id)}>
</Show>
)}
// UserPopup.tsx
export default function UserPopup(props: { userHandle: string }) {
const [user] = createResource(() => {
return getUserSummary(props.userHandle);
});
// addFollow is a server function doing db stuff
const follow = useAction(action(addFollow));

return (
<Show when={user()} fallback={<div>Loading...</div>}>
<div>user()!.name</div>
<button onClick={() => follow(user()!.id)}>
</Show>
)}
And then I have a wrapper for this popup
// UserWrapper.tsx

export default function UserWrapper(props) {
const [element, setElement] = createSignal<JSX.Element>(null);

const activate = (handle: string) => {
setElement(() => UserPopup({ userHandle: handle }));
};

const deactivate = () => {
setElement(null);
};

return (
// Additional stuff calling activate onMouseEnter and deactivate onMouseLeave
<div>{element()}</div>
)}
// UserWrapper.tsx

export default function UserWrapper(props) {
const [element, setElement] = createSignal<JSX.Element>(null);

const activate = (handle: string) => {
setElement(() => UserPopup({ userHandle: handle }));
};

const deactivate = () => {
setElement(null);
};

return (
// Additional stuff calling activate onMouseEnter and deactivate onMouseLeave
<div>{element()}</div>
)}
With this method I get the error: 'use' router primitives can be only used inside a Route. which I guess is because I'm trying to create UserPopup just in the air without a parent, but I'm not sure how I should be creating UserPopup within a context, or do I just have to not use useAction and find something else? The other problem with this is I'm getting the warnings computations created outside a createRoot or render will never be disposed. So I tried using both render and mount and attach the UserPopup element I create directly to the element I want it to go in but nothing happened, this was my attempt:
// UserWrapper
let tooltipbox;

const activate = (handle: string) => {
const dispose = render(
() => <UserPopup userHandle={props.handle}>,
tooltipbox! as HTMLDivElement
);
};
...
...
return <div ref={tooltipbox}></div>
// UserWrapper
let tooltipbox;

const activate = (handle: string) => {
const dispose = render(
() => <UserPopup userHandle={props.handle}>,
tooltipbox! as HTMLDivElement
);
};
...
...
return <div ref={tooltipbox}></div>
So what is the right way to do this?
13 Replies
REEEEE
REEEEE•9mo ago
Use Portal
Xzayler
XzaylerOP•9mo ago
I tried doing that like so:
const activate = (handle: string) => {
setElement(() => {
<Portal mount={tooltipbox as HTMLElement}>
<UserPopup userHandle={handle} />
</Portal>
});
};
const activate = (handle: string) => {
setElement(() => {
<Portal mount={tooltipbox as HTMLElement}>
<UserPopup userHandle={handle} />
</Portal>
});
};
And the result I see is exactly the same as without portal The issue seems to be me using useAction outside of any context, but idk how I could implement this tooltip functionality without creating components on the fly.
REEEEE
REEEEE•9mo ago
Is there a reason to set the element in activate though? Also in this case, you aren't returning anything for the setElement
Xzayler
XzaylerOP•9mo ago
Yeah I copied the code wrong in my reply. It's setting it right in my code except the data fetching or anything reactive doesn't seem to work in the created component. My goal is for the element to be created only on hover, and then be destroyed when the mouse leaves the hovered element. Other options would be: - Having one tooltip component created on page load, whose position and displayed data is changed. I could do this I guess but I'd have to implement refetching and calculating positions and stuff and it seems a little excessive, there's definitely a better way. - Creating a separate tooltip component for each wrapper and then just using Show to hide them. I tried this and it resulted in a lot of flickering every time I hovered. There are a lot of these wrappers present on the page at one time, and I also want them to refetch data when re-hovered. And final reason is I guess I just want to learn how to create ephemeral components.
REEEEE
REEEEE•9mo ago
You could take a look at how Kobalte implements it if anything
Xzayler
XzaylerOP•9mo ago
Oh good idea. Thanks! My biggest issue now is that creating components and then mounting them can leave them undestroyed and stuck in memory. ANd that's how all my modals, tooltips currently work...
REEEEE
REEEEE•9mo ago
using Show is probably your best option I wonder what the flickering you mentioned is about 🤔 I'd probably have activate and deactive basically set a signal for showing and unshowing
Xzayler
XzaylerOP•9mo ago
NVM I solved it by simply wrapping the Show in a Suspense It's exactly what I did, and I just tried that again and had a Show when={active()} where active is a bool signal accessor and there was the component I wanted wrapped in this Show component, but every time I hover over the element, the whole page goes black for a split second and then the tooltip shows. However when the page goes black it also counts as a MouseLeave event and so the active signal is set to false which causes another flicker, and since my mouse is still above the hoverable element the whole loop starts from the beginning. Basically every time the when changes everything goes black for a little bit.
REEEEE
REEEEE•9mo ago
that is very weird oh LOL
Xzayler
XzaylerOP•9mo ago
Why does that happen though? Tbf I don't understand and I just noticed everything under the closest suspense boundary was rerendering
REEEEE
REEEEE•9mo ago
Well if you're fetching data using a resource, the resource will trigger the nearest Suspense boundary In this case, the nearest might have been the app level boundary
Xzayler
XzaylerOP•9mo ago
Right! Yes I am in fact using resource. Sorry for being dumb and thanks for the help.
REEEEE
REEEEE•9mo ago
No problem 🙂
Want results from more Discord servers?
Add your server