Integrating transitions into design systems #63
Replies: 1 comment 1 reply
-
TL;DR; currently we use it pretty high level and pass isPending down but it's not ideal. I think we need to do some more work on explaining the UX and designs that might be appropriate to implement using this feature. @gaearon and I was talking about this in terms of examples for documentation. Spinners might not be the best design paradigm to use as the canonical UI for thinking about this. Partially just because it's kind of an outdated design paradigm. But it also doesn't describe the full ubiquity that we expect transitions to have. Ideally almost every update should be wrapped in a transition somehow. This ensures that even if your component isn't slow now, it doesn't risk becoming slow and conflicting with another interaction like starting an animation. Similarly if you add a data dependency or you're streaming the data in from server component the interaction can easily become slightly async even if it isn't today. Because you're optimistically expecting the data to stream in soon. We're really betting on anchoring a lot on this. This is somewhat in conflict with gradual adoption of concurrent rendering though so it might not be good to push for universal usage quite yet for that reason. The simplest UI paradigm that might need to use a pending flag is to simply retain the "pressed" or "active" state. That's something we're not used to thinking about because we get that subtly for "free" by blocking the main thread today. It remains pressed until you transition to the new state - but it has all the other downsides. With that transition becoming somewhat async it would immediately turn off the button's pressed/active state - which might not be what you want. So you can use the pending flag to keep it on or show an alternate pending state. This might actually be enough to let the user know something is blocked for a lot of cases. Especially when you're not expecting the transition to be particularly slow such as when you're transitioning into a quick loading state. In terms of which layer of abstraction it belongs in, that's a tricky one. We've found that putting it too low can be problematic because of the controlled components case. However, we also give special treatment to all "discrete events" to flush one after another like onKeyUp, onClick etc. For almost every event it would be tricky to put it too low. It's better to wrap startTransition where your abstraction starts implicitly becoming more "async". It's hard to give a specific answer to this but you might see it when you see it. Like at some point the abstraction level clearly doesn't specify that something must happen within this event anymore it's too high level and not connected to the event anymore. One option would be for every component to expose two different callbacks but that's pretty confusing. It also just moves the problem upwards and if you call startTransition from a one of the discrete ones, then that wouldn't merge the two pending flags necessarily but instead. The semantics of when transitions merge, e.g. nested startTransition calls or startTransitions in the same event is a bit weakly defined now because we're not sure. But they actually all merge into one right now and maybe that's the solution. For example if you do this:
Currently that groups the transitions spawned from within onClick so you can effectively make all transitions spawned from within your event grouped. But yea, this is under explored so safest now is to exclude it from too low level components. |
Beta Was this translation helpful? Give feedback.
-
This topic was moved from a comment here: #41 (comment). However, I may have misunderstood the goal of transitions in the initial release of React 18. These questions apply more in the context of data fetching, which sounds like it might be a post 18 feature? The post on startTransition did mention spinners though, which is what got me thinking about this. Would be curious to know how you think about spinners in the context of time slicing as well though. Original post below.
One thing I found interesting in the old docs about concurrent mode was the section about baking transitions into design systems / component libraries. I'm wondering if this is still the recommendation of the team after more experience integrating concurrent UI into Facebook.com, or whether leaving this decision to the application code has proven useful in some cases.
Some questions to start with:
onChange
event in a transition and the component was controlled, then the update of the text in the textfield itself would also be lower priority as it depends on the parent component to re-render in response to the event. In the example above, you do this by storing two separate states for the input value and the current filter text, but I can see this being challenging for controlled components.isPending
state, or would you initiate it within the design system component and somehow expose this pending state elsewhere for the spinner to appear?Beta Was this translation helpful? Give feedback.
All reactions