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
React fails to manage children in Stencil <slot /> #2259
Comments
Just leaving notes that I had with @bbellmyers . Using deeply nested slots don't work without ShadowDOM because hyperscript efficiently moves the existing nodes. Afaik, we cannot tell React-DOM when an external change has been made. Possibles solutions, at least when compiling as React target, could be to make an internal HOC to render the stencil-deeply nested slot as direct children for React. |
This is looking like a potential blocker to our adoption of Stencil in our design system, which would need to service React and Elm projects (I'm currently working on an Elm output target for Stencil) and support IE11 (thus no Shadow DOM). At this stage we're thinking maybe we can use slotted child elements:
…as long as the list of menu items is stable (it's OK if the menu items themselves change – they just can’t be added/removed dynamically). And in cases where a dynamic list of children was needed, we’d make our components support an alternative API using a render prop:
Curious if anyone has any thoughts/experience to share about this sort of work-around. |
Hi, I’m in holidays, but we’re running a production version on our client and we didn’t encounter this problem again. We’re using React. Not sure if we had a workaround or we just used it in a way where this didn’t happen. Can you make a repo with the exact problem you have? Using props as “children” is technically doable, but I’d highly discourage it, mainly because of the overhead needed to support/test this. Edit: there was a different issue regarding the order and agressivity on how the components initialize, maybe related. |
@max-scopp I'm seeing the same issue that @bbellmyers reported. I've put together a fresh demo repo with the latest versions of Stencil, react-output-target, and create-react-app, but it's essentially the same thing Bruce shared previously: https://github.com/sentience/stencil-vdom-test Lots of detail in the README. |
For anyone else who encounters this, @bbellmyers has documented a pragmatic work-around that should save your bacon in the 99% case:
The main downside of this is that you will lose any private state from the component (including browser DOM state, such as keyboard focus or a text selection) when you force React to replace it. |
React gets confused if the parent component adds any dom levels that React doesn’t know about, basically. So one way to manage is have parent component do all its work with just <Host> if it can (don’t insert any levels) Or wrap the children in a single component that react DOES know about - a simple div will do, or in your case, maybe a <stencil-menu-item-group>? |
Just chiming in to say, this isn't just a problem in react but vue too (so probably not the right place to put this?).
|
Just wanted to add to the conversation with a potential solution(?) using React portals I came up with when running into the same issue. Mind you, I wouldn't call it the perfect solution to this problem but rather an alternative, perhaps. I should preface by saying I am making use of the React bindings as one of my output targets. I am then modifying the output of the bindings by changing the In the export const ComponentName = /*@__PURE__*/createReactComponent<JSX.ComponentName, HTMLComponentNameElement>('component-name'); Since the bindings alone do not solve for this issue in React, I created something of an HOC that would take in the component to render, as well as a selector for the element inside the stencil component where things will be slotted (Which means currently this only works for components using a single const ComponentNameRenamed = /*@__PURE__*/createReactComponent<JSX.ComponentName, HTMLComponentNameElement>('component-name');
export const ComponentName = withPortal<JSX.ComponentName, HTMLComponentNameElement>(ComponentNameRenamed, '.slotSelector') I then define a simple Portal component that takes children and a reference to an element that will act as the portal. In our case, we want the elment in our Stencil component's render method that contains the const Portal = (props: { children: React.ReactNode; el: Element; }) => {
return (
ReactDOM.createPortal(props.children, props.el)
)
} In my The implementation for const withPortal = <
PropType,
ElementType extends HTMLStencilElement
>(Component: ReactType, slotSelector: string) => {
return React.forwardRef(({children, ...props}: StencilReactExternalProps<PropType, ElementType>, ref: Ref<ElementType>) => {
const hostElementRef: Ref<ElementType> = ref && "current" in ref ? ref : React.createRef();
const [slotRef, setChildRef] = useState(null)
React.useEffect(() => {
const node = hostElementRef.current;
const selectChildElement = () => {
const childElement = node.querySelector(slotSelector)
setChildRef(childElement)
}
if(ref && !("current" in ref)) {
ref(hostElementRef.current);
}
node.componentOnReady ?
node.componentOnReady().then(selectChildElement)
: node.addEventListener('ready', selectChildElement)
}, []);
return (
<Component
ref={hostElementRef}
{...props}>
{slotRef && <Portal el={slotRef}>{children}</Portal>}
</Component>
)})
} All in all what you are left with is a React component that bypasses Stencil's handling of slotted children, allowing React to handle inserting the children itself via a Portal. I think one of the biggest pros to this solution is not having to re-render the entire component on every addition/removal of children. However, it would require additional tweaking for more complex components that use named slots. Additionally the use of React portals may be a hindrance if consumers of your component library are using SSR. |
Thanks for the issue! This issue is being closed due to inactivity. If this is still an issue with the latest version of Stencil, please create a new issue and ensure the template is fully filled out. Thank you for using Stencil! |
Reopening - this shouldn't have been closed. Sorry about that! |
The fix for this issue has been released as a part of today's Stencil 4.9.0 release under the |
Stencil version:
@stencil/core@1.8.1
I'm submitting a:
[X] bug report
[ ] feature request
[ ] support request => Please do not submit support requests here, use one of these channels: https://stencil-worldwide.herokuapp.com/ or https://forum.ionicframework.com/
Current behavior:
Given: one or more nested Stencil container tags with Shadow DOM off, which insert DOM levels between the Host and the
<slot />
.<slot />
.<slot />
inside component-aWhen React manages the children of the Stencil container tag, it fails to add and remove them correctly. This happens on every render after the first one.
Expected behavior:
Adding and removing items from the children array should all render in the expected slot.
Steps to reproduce:
See this github repo for an example.
React app that uses Stencil components: https://github.com/bbellmyers/stencil-slots
Stencil component library: https://github.com/bbellmyers/stencil-slot-order
Related code:
Component a:
Component b:
React render:
If the children array is longer or shorter, it fails to render correctly on the second render. re-rendering with any array of the same length works.
Other information:
The text was updated successfully, but these errors were encountered: