-
Notifications
You must be signed in to change notification settings - Fork 887
feat(site): add WorkspacesButton component #10011
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
||
// Ref value is for effects and event listeners; state value is for React | ||
// renders. Have to duplicate state because after the initial render, it's | ||
// never safe to reference ref contents inside a render path, especially with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because React never rerenders the component? Why is particularly the case with React 18?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So technically it's a concern with all versions of React that use hooks – the React team really wants you to treat refs like black boxes during renders. As in, the ref itself is fine – it's just a box for storing values, and you can work with the box and attach it to elements. But the only times it's safe to peak into the box to read from it or update the contents are one of two situations:
- On the very first render when you're setting up the ref for the first time. For example:
// This is only safe if the other effects and event handlers can't change the ref to null
const abortControllerRef = useRef<AbortController>(null!);
if (abortController.current === null) {
abortControllerRef.current = new AbortController();
}
- Inside the various useEffect hooks or event handlers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But it's more of a problem with React 18's concurrency mode, which has the ability to stop/throw away, de-prioritize, and redo renders. They can only do that because renders are supposed to be pure. Reading from/writing refs is technically a side effect/"side cause", though, so there's a risk that as their render engine changes (which might happen soon with React Forget), there's more risks of things breaking if the rules aren't followed 100%
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They can only do that because renders are supposed to be pure, and reading from/writing refs is technically a side effect/"side cause", so there's a risk that as their render behavior changes (which might happen soon with React Forget), there's more risks of things breaking if the rules aren't followed 100%
Ahhh, makes sense. Thanks for the explanation!
@@ -0,0 +1,149 @@ | |||
/** | |||
* @file Abstracts over MUI's Popover component to simplify using it (and hide) | |||
* some of the wonkier parts of the API. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be useful to hint at some of the wonkier API parts of MUI, or maybe link to their docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, good point. I guess if I had to summarize up my concern, it feels to me like their current API (as in, the entire conceit of it) defeats the point of React?
Like, the point of JSX is that you don't have to touch live DOM nodes during actual render logic, and only have to worry about them when you use effects and event handlers as escape hatches. But MUI has you take a whole real DOM node (which is physically incapable of existing on the first render), and store it in React state, to be accessed in renders
It feels backwards to me, and I've seen the component get set up multiple different ways in the codebase to make it happy, when it feels like there should be one obvious way of doing things
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it feels to me like their current API (as in, the entire conceit of it) defeats the point of React?
lolol
Your abstraction makes sense to me. Maybe you could make a contribution!
sx={{ display: "flex", flexFlow: "column nowrap" }} | ||
anchorButton={ | ||
<Button startIcon={<AddIcon />} variant="contained"> | ||
Create Workspace… |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the ellipsis?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a UI concern – it just helps communicate that there's no immediate action when you click the button, and you'll see more options when you do.
Kayla had a thread about this recently:
https://codercom.slack.com/archives/CJURPL8DN/p1694725326331259
@Kira-Pilot Could I get more info on what you did to get the popover to stick around longer than it should've, and where/how you ran the code? Did you basically:
The reason I'm asking is because right now the component assumes that MUI will just unmount the entire dropdown if the button has no reason to be on screen anymore, which seems like it should be enough. But I've been having trouble replicating the issue. Even this code, which is supposed to be the nuclear option that wipes everything away when the component unmounts, never actually triggers: const popoverRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const popover = popoverRef.current;
return () => {
if (popover !== null) {
popover.innerHTML = "";
console.log("Nodes burned to the ground");
}
};
}, []);
return <Popover ref={popoverRef} {...otherProps}>{children}</Popover> |
Okay, took a lot of research, and I had to do some slightly cursed things under the hood, but there's a solution in place for making sure the popover closes before a Router page transition. The API should hopefully be really straightforward, too. Working on styling fixes now |
@Parkreiner sounds like you got it figured out but just to reply to your comment: yes, those are the steps to reproduce. I would see this issue only after a page refresh. The popover would eventually disappear after lingering for a bit. Glad you were able to diagnose! |
Looks like there are a couple of storybook errors - maybe we're missing a provider somewhere? |
void Promise.resolve().then(() => { | ||
navigate(to, linkProps); | ||
}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⭐
@Kira-Pilot Yeah, looks like that's on me. I have the button component calling the permissions and organization ID hooks directly, which works for the app, but makes the tests break Gonna ask about this in the dev channel – I'm guessing our approach is to always parameterize these kinds of things from the pages component |
Yeah, it's a major bummer and leads to some weird patterns. I am OOO today but I went ahead and approved your PR so you're not blocked; feel free to approve the snapshots yourself when they resolve. |
Okay, sounds good. Thank you! |
Solves #9335 and lays some groundwork for solving #10012 and #10014
Really sorry this took so long. This PR updates the button for creating workspaces with a more conspicuous dropdown menu.
Things changed
PopoverContainer
simplifies the process for using MUI's kind of wonkyPopover
component, and removes the need to set up custom hooks and refs before even being able to start using the componentOverflowY
provides a reusable "stretchy" component for wrapping any content and making sure that it scrolls properly, but also shrinks correctly as the viewport's height decreases.Screen.Recording.2023-10-03.at.9.06.01.AM.mov
Left to do (this PR)
Left to do (separate PRs during downtime)
PopoverContainer
andOverflowY
Out of scope
Other notes