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
🐛 BUG: passing children via props does not work properly on React components #681
Comments
I went as far as to implement a fix for this but I now realize that we don't support this. Remember that a The way to pass children in a |
We've been talking about Where I ran into issues specifically: <Tabs>
<TabsItem>
some content
</TabsItem>
<TabsItem>
some content
</TabsItem>
</Tabs> It's important to note that in my case, Tabs/TabsItem communicate through a Now, in an Astro file, I tried to use them, in a way that one of those TabsItems has a Prism component inside it. I marked all the components with // index.astro
<Tabs client:load>
<TabsItem client:load>
some content
</TabsItem>
<TabsItem client:load>
<Prism code={someCode} />
</TabsItem>
</Tabs> In order to make the context work, I created a React component that takes all the data in as props: // TabsWrapper.jsx
import React from 'react';
import { Tabs, TabsItem } from '../../patterns/index.ts';
const TabsWrapper = (props) => {
const { items } = props;
return (
<Tabs>
{items.map((i) => {
return (
<TabsItem {...i} key={i.id} />
);
})}
</Tabs>
)
}
export default TabsWrapper; Then, in my <TabsWrapper
client:load
items={[
{
children: 'first content',
},
{
children: 'second content',
}
]}
/> and this works! The components share the context, so tab switching works and all. However, as I said above, the content of the <TabsWrapper
client:load
items={[
{
children: 'content',
},
{
children: <Prism code={code} />,
}
]}
/> This does not render at all unfortunately. So, to reiterate - using React components that want to share some state through React Context API and still accept HTML/JSX/Astro content as props is not supported? |
I agree with @mihkeleidast that this should be supported. I think it’s probably a good idea to handle this more generally by just allowing Astro elements to resolve inside-out by implicitly The fact that Astro renders are async-per-element is an implementation detail, and one most people wouldn’t expect coming from other component libraries. Pushing this limitation on users is probably going to continue to be a footgun. I'm happy to look into this when I have some time, probably this weekend, if the Astro team doesn’t want to take it on but would be open to a contribution. |
I think this is a fine addition if applied generally. Want to keep in mind that:
Will reopen with the understanding that this is about any props. I'll let someone else take this. |
Maybe overly pedantic, but to avoid another round of clarification in case someone else picks this up before I do: it’s not even necessarily just about props, it’s about any Astro element/ |
I think that's going a bit too far. I don't want to walk a potentially deep nested object looking for h() promises. This is already an edge case, let's not destroy perf in the pursuit of correctness. Frontmatter scripts aren't a problem, if those are passed into Astro content it will be resolved already, if passed into a child component as props then fixing props takes care of that. |
Okay, this seems like a needless overreaction to a sincere interest in helping. It’s not an edge case, it’s extraordinarily common to use component elements as values in expressions. This use case is what prompted my clarification. It wouldn’t be addressed by resolving props directly. I don’t think there’s a performance penalty for handling expressions generally rather than special casing them at the immediate prop level. In both cases you need to know there’s an inner expression that needs to be resolved by Astro, in both cases the existing call stack is already well suited to resolving efficiently. It’s probably more efficient to But I do think there’s a persistent maintenance problem for Astro in using semantics in uncanny valley ways. If you want to fork JavaScript, by all means go for it. But it’s probably not gonna be a happy outcome for anyone. |
To clarify, there’s no need to walk anything. Expressions are already going to evaluate from the inside out. You just need |
We would need to walk all objects passed as props to find h() derived promises. <SomeComponent prop={{ nested: { another: { level: <div></div> } } }} /> We would need to walk this object to find the nested promise. Passing objects as props is common, so we'd be needlessly walking objects on the off-chance that they are doing this. Objects can even be recursive, so you have to account for objects you've already visited. If you've ever written a "deep clone" before, this is a lot like. I don't think we should support this. If people really need the HTML string they can await it. There's an argument to made that trying to hide that Astro vdom is Promise-wrapped is too much. |
We don't have an answer for this just yet, but the original use case is totally valid. We will take some time to consider how one should approach this problem in Astro. For some context:
We can try to paper over the differences (with our compiler or with the renderer interface), but at the end of the day, there will be places where this fact inevitably surfaces. Figuring out how to hide this isn't a major priority at the moment. |
@eyelidlessness If supporting this pattern is really straight-forward, we can support it, but I suspect there's a lot of yet-to-be-discovered complexity here. I appreciate that you've been very upfront with your concerns about the |
In broad strokes, this is how I’d go about it:
This shouldn’t have much performance impact at all, beyond the inherent complexity of real world component trees. |
And the next thing I tried in my TabsWrapper was:
and this works wonderfully! Maybe this would just need to be documented as a valid usecase? I think necessary workarounds are fine and this seems like a necessary one, if the alternative is some kind of perf regression. |
Okay taking my words back about this working "wonderfully". It worked partially in an Astro component inside a map:
Where it runs into issues if the
This partially works as it renders but some of the HTML ends up duplicated etc, but the client-side hydration is partially broken, there's As a separate Astro component, this does not work, but maybe it could?
This fails with |
Thanks for the discussion guys. I found it super useful in understanding where things are at the moment. I arrived here after struggling to implement a simple React context provider inside a Layout component (in an attempt to understand) how global state could work inside astro. I personally could not find a way to get a provider/consumer to work so wondering what is the plan/strategy/vision for working with global/shared state. By global, I'm thinking i.e user/account state or even a simpler global state for say working with modals and sidebars toggling etc. Thanks for the amazing work so far. It's been fun testing things out. |
@eyelidlessness I don't think that algorithm will cover nested content like in this example: <SomeComponent prop={{ nested: { another: { level: <div></div> } } }} /> Upon reflection this problem is deeper than Astro HTML being promised based. The promise part is an annoyance, but at its heart this issue is about framework interop. Astro HTML, even when resolved, is not a React component. It's an HTML string. So a React user (or Svelte user, etc) can't just use markup that's passed to it from props. That markup has to be converted to a framework-specific node type. That's what you see happening with So a solution to this problem needs to account for this issue (and probably needs coordination with renderers, some how). |
Would a shallow resolution satisfy the desire of this issue? As I talked about above, this seems hard/impossible to solve when passing into an object. Also this requires renderer cooperation to turn the HTML strings into framework components. If a shallow solution is acceptable I'd be willing to take the issue on. |
Could there maybe be a utility component / function the user could manually wrap the contents with? Or would that also be as hard to implement as a completely automatic resolution? What I mean is, manually awaiting a nested content almost worked per my examples above, but there was an issue with nested |
Investigated the nested See https://codepen.io/risker/pen/dyWqxLW?editors=1000 for a very simple reproduction. Anyone know if this would be difficult to escape in Astro? If that issue is fixed, I think the rest of this issue has workarounds - |
I think there's a few good ideas here. I'll summarize what's been discussed in this thread:
Since this change is non-trivial, and the solution is unclear, I think this needs to be resolved via the RFC process. The use-case is pretty clear but the design is not. Going to close this bug. |
@matthewp @mihkeleidast You closed this ticket, because it was supposed to be solved in the RFC process. However, the linked RFCs (#751 and from there #877) are not very helpful for me and are already closed. Can someone summarize the status of the described use case in #681 (comment)? Is this now possible somehow? Or is this being worked on? Are there any OPEN PRs/Discussions around this? Would be nice to have an update for all the "users" of Astro finding this Issue via Google, who are not Astro maintainers/devs. |
Those linked RFCs are not at all about this issue; imho this issue should not be closed until the replacement discussion/RFC has been created. Since it's been a year since the original closure, and I couldn't actually find any RFC about this, it seems this problem has been dropped. |
Any improvements? We like React because it is flexible. We want the same for Astro. Please do not ignore the requests of the community. |
What package manager are you using?
npm
What operating system are you using?
WSL/Ubuntu
Describe the Bug
React allows passing children in various ways, some examples:
Some of these do not render at all in Astro, and others that do render do not produce the same output as in pure React.
Here's a CodeSandbox that shows how these render in pure React: https://codesandbox.io/s/loving-wing-7vtk4?file=/src/App.js
Steps to Reproduce
Link to Minimal Reproducible Example (Optional)
https://github.com/mihkeleidast/astro-jsx-children-demo
The text was updated successfully, but these errors were encountered: