Open greenseeker opened 6 months ago
Thanks for the report.
Seems to be caused by the level of isolation we add when components need to use State related stuff. With
rx.tooltip(
rx.icon_button(
"home",
disabled=True
),
content="Go to homepage"
)
the tooltip will show up properly.
Need to investigate on Radix side, it might be a bug from them.
Trimmed down code that reproduce the issue :
/** @jsxImportSource @emotion/react */
import { Fragment } from "react";
import {
Box as RadixThemesBox,
Tooltip as RadixThemesTooltip,
} from "@radix-ui/themes";
export function Box_f821d43fc5d8628e6aa9573e5ba02f7f() {
return <RadixThemesBox>{`home`}</RadixThemesBox>;
}
export default function Component() {
return (
<Fragment>
<RadixThemesTooltip content={`Go to homepage`}>
<Box_f821d43fc5d8628e6aa9573e5ba02f7f />
</RadixThemesTooltip>
</Fragment>
);
}
Moving component directly in the same function where Tooltip is used make the code work:
/** @jsxImportSource @emotion/react */
import { Fragment } from "react";
import {
Box as RadixThemesBox,
Tooltip as RadixThemesTooltip,
} from "@radix-ui/themes";
export default function Component() {
return (
<Fragment>
<RadixThemesTooltip content={`Go to homepage`}>
<RadixThemesBox>{`home`}</RadixThemesBox>
</RadixThemesTooltip>
</Fragment>
);
}
After posting I went looking for a workaround with different components, but it seems to impact anything that would appear on hover, not just rx.tooltip.
If there's any workaround for this, please let me know, even if it's hacky. My app is just a mockup, so I'm not worried about it being done "right". Even if there's some way to set the alt property and use default browser tooltips, that'd be good.
If there's any workaround for this, please let me know
from reflex.components.component import MemoizationLeaf
from reflex.components.base.fragment import Fragment
class IntegralFragment(Fragment, MemoizationLeaf):
pass
...
IntegralFragment.create(
rx.tooltip(
rx.icon_button(
"home",
on_click=rx.redirect("/"),
disabled=(State.router.page.path=="/")
),
content="Go to homepage"
),
),
Don't ask me why this works, needs further investigation. But it does appear to workaround the problem.
Okay this actually helped a lot https://github.com/radix-ui/primitives/discussions/1166
Turns out that Reflex's StatefulComponent memoization is not handling ref and prop forwarding, which Radix is depending on.
If i change the code to be like this
export const Button_4f9c226eb6db5a68f02ace30e6d02ba7 = forwardRef((props, forwardedRef) => {
const state = useContext(StateContexts.state)
return (
<button ref={forwardedRef} {...props}>
{`fooey ${state.router.session.client_token}`}
</button>
)
})
Then <Button_4f9c226eb6db5a68f02ace30e6d02ba7 />
can work as a child of the tooltip.
I wonder if it makes sense to eventually structure all of the memoized components in this way to maximize compatibility.
@masenf what's that workaround doing?
what's that workaround doing?
The workaround is making it so that child of the tooltip does not get memoized separately. The IntegralFragment
becomes its own React component with the state context of all its children inside.
I have another minimal example, which causes the same error and @masenf 's workaround makes it work.
import reflex as rx
class SimpleState(rx.State):
key: str = "Don't open menu"
@rx.page(route="/page2")
def page2() -> rx.Component:
return rx.menu.root(
rx.menu.trigger(
rx.button(SimpleState.key),
),
rx.menu.content(
rx.menu.item("Edit", shortcut="β E"),
),
)
The menu is rendered correctly, but as the inscription on the button promises, the menu will not open. Even if you change the text to "Open menu"
.
I can not oversee if this is the exact same problem or a different one. If different, please let me know and I will create a new issue.
is the exact same problem
yes, i believe it's the same issue. Radix is wanting to pass a ref down into the component wrapped by the trigger/tooltip/close/etc and the memoized StatefulComponent code just does not support it, so it doesn't inherit the special behavior.
I think the fix is relatively clear, I just haven't had time to put it in and test it.
@masenf I just encountered it again (see below). Even with the workaround (hopefully applied correctly (I'm not a react-expert)) it doesn't work (OK, the error is gone, but the click not propagated). Sorry for the messy example, I tried to isolate it as good as possible.
# index.py in fresh sidebar app
from reflex_custom_components_playground.templates import template
import reflex as rx
from reflex.components.component import MemoizationLeaf
from reflex.components.base.fragment import Fragment
class IntegralFragment(Fragment, MemoizationLeaf):
pass
class IndexState(rx.State):
icon: str = "home"
def set_skull(self) -> None:
self.icon = "skull"
def set_trash(self) -> None:
self.icon = "trash"
@template(route="/", title="Home")
def index() -> rx.Component:
return rx.vstack(
rx.text(IndexState.icon),
rx.table.root(
rx.table.header(
rx.table.row(
rx.table.cell(),
rx.table.column_header_cell(
"Delete without asking",
IntegralFragment.create(
rx.tooltip(
rx.icon(
tag="skull",
on_click=IndexState.set_skull,
),
content="Skull icon",
),
),
),
rx.table.column_header_cell(
"Delete asking politely",
rx.dialog.root(
rx.dialog.trigger(
IntegralFragment.create(
rx.tooltip(
rx.icon(
tag="trash-2",
# on_click=IndexState.set_trash,
),
content="Trash icon",
),
),
),
rx.dialog.content(
rx.dialog.title("action.name"),
rx.dialog.description(
"action.confirmation_description"
),
rx.dialog.close(
rx.hstack(
rx.button(
"action.name",
size="3",
id="confirm",
background_color="green",
on_click=IndexState.set_trash, # type: ignore[reportArgumentType]
),
rx.button(
"Cancel",
size="3",
background_color="red",
id="abort",
),
)
),
),
),
),
),
),
rx.table.body(
rx.table.row(
rx.table.cell("John Doe"),
rx.table.cell(
"Delete without asking",
IntegralFragment.create(
rx.tooltip(
rx.icon(
tag="skull",
on_click=IndexState.set_skull,
),
content="Skull icon",
),
),
),
rx.table.cell(
"Delete asking politely",
rx.dialog.root(
rx.dialog.trigger(
IntegralFragment.create(
rx.tooltip(
rx.icon(
tag="trash-2",
# on_click=IndexState.set_trash,
),
content="Trash icon",
),
),
),
rx.dialog.content(
rx.dialog.title("action.name"),
rx.dialog.description(
"action.confirmation_description"
),
rx.dialog.close(
rx.hstack(
rx.button(
"action.name",
size="3",
id="confirm",
background_color="green",
on_click=IndexState.set_trash, # type: ignore[reportArgumentType]
),
rx.button(
"Cancel",
size="3",
background_color="red",
id="abort",
),
)
),
),
),
),
),
),
),
)
Sometimes you need multiple boxes. :mechanic:
rx.dialog.trigger(
rx.box(
rx.tooltip(
rx.box(button),
content=action.name,
)
)
),
It feels like in sokoban. :smile:
Then
<Button_4f9c226eb6db5a68f02ace30e6d02ba7 />
can work as a child of the tooltip.I wonder if it makes sense to eventually structure all of the memoized components in this way to maximize compatibility.
@masenf Did you reach a conclusion concerning this? Unfortunately I cannot be of any help here due to a lack of react comprehension.
@masenf In their documentation they write it is best practice to always use forwardRef
Whilst this isn't necessary for all parts, we recommend always doing it so that you are not concerned with implementation details. This is also generally good practice anyway for leaf components.
Describe the bug Tooltips are not displayed for (icon_)buttons once that button has addition properties specified.
To Reproduce Use the following code in a page. Hovering over the icon_button will not display the tooltip.
If you comment out the
on_click
anddisabled
lines, then the tooltip works.Expected behavior The tooltip should still be displayed.
Specifics (please complete the following information):