Widget content effects fire before it is actually rendered in widget
Closed this issue ยท 3 comments
Describe the bug
This is more noticeable when I navigate between frontstages - to reproduce issue widget in question should not be present in another frontstage because it needs to be unmounted first.
When rendering react component as widget contents, all effects are being fired before widget is actually visible in widget DOM. When observing it with breakpoints at useEffect
/useLayoutEffect
I can only see this when under highlighted element I expect to see rendered content:
This messes up layout in cases where effects were making changes to DOM or when it requires DOM to make calculations. I investigated further and used MutationObserver to confirm that widget content is actually being appended after effects fire (ContentRenderer.tsx):
Which is called because of (FrontstageDef.tsx):
To Reproduce
- Open test app with at least 2 frontstages, one fronstage has a widget that has
useEffect
in it. - Set breakpoint for effect and navigate between frontstages.
- Inspect elements tree and notice widget is not actually placed inside DOM yet even though effect fired already.
Expected Behavior
DOM is already rendered before effects fire.
Screenshots
No response
Desktop (please complete the applicable information)
- OS: Windows
- Browser: Electron
- Version: 25.8.1 (npm version)
- iTwin.js AppUI Version: 4.10.0
- iTwin.js Version: 4.4.3
Additional context
No response
That is correct, we are reparenting widget content DOM node to display widget content in different parts of the UI (i.e. different panel sections or floating widgets).
React element tree is stable per frontstage to prevent re-mounts when moving/docking/merging widgets in the UI.
Widget react nodes are loaded first and are portaled to a detached DOM element (which is what you are observing).
I.e. widget rendered w/ WidgetState.Hidden
will run all React lifecycle methods (like useEffect
, but the DOM element will not be added to the page).
We have a custom hook useTransientState that you can use to save/restore DOM properties that are not persisted when element parent is changed (like scroll position).
The function onSave
is called before removing widget content DOM node and onRestore
is called after appending the DOM node.
I think you could utilize useTransientState
hook for your use case as well (instead of doing your calculations in useEffect
, do them in onRestore
). Would that work for you?
Could you describe a little more your use-case, what calculation are you doing and what are you trying to accomplish?
@GerardasB This info was useful to me, I utilized useTransientState
hook's onSave
and onRestore
callback parameters to make sure I am doing logic in a rendered widget. Our use case: after component mount we are using React.useLayoutEffect
to adjust position: absolute
element's coordinates by changing left
style property - it is calculated from DOM. Before widget is mounted in dom we calculate wrong left
value then after it is mounted it seems sizing is different, but old left
value is still in place.
While this fix resolved this issue for me it seems onRestore
is not being called when popping out widget. Don't know if its intentional or not, just something I noticed.