Comments (14)
Yeah the macro doesn't have any opinion about the type of what it's returning, although if you have multiple root nodes it should be wrapped in a fragment (<></>)
The bigger issue here would be the type of what you're returning: if you had two elements you could return a Vec, but you have one Element and one Fn() -> Element. What would you hope the type would be that you're returning there?
from leptos.
I'm not sure how it works under the hood, but I would expect the macro to call IntoChild::into_child(/* .. */)
on each root element and then convert the children into an Element
, this way it would always return either Element
or Vec<Element>
.
from leptos.
Yeah that would be one possible approach... It would return Child
rather than Element
(because it could be a text node, a Vec<Node>
, etc.) You'd have to match on that Child
to get access to the underlying DOM element.
The Element
type is literally a web_sys::Element
so in your example {focused}
can never be an Element
; it's not an Element
, but a function that returns an Element
. It can be a Child
, but that's basically an internal type for the renderer.
I guess it would be possible to return each node as an enum of all the possible node types, but I kind of feel like that would be worse for most use cases.
Sidebar: I know it's just a simple example, but in the example it's probably actually a better idea to do something like
let focused = move || {
if focus() {
"focused"
} else {
"not focused"
}
};
view! { cx,
<div>
<input on:focus=move |_| set_focus(true) on:blur=move |_| set_focus(false) />
<p>{focused}</p>
</div>
}
Because this causes only the text node inside the p
to change when focus()
changes, rather than replacing a whole element. This is one of the reasons wrapping something in an element isn't a big deal in most cases... it's often the more efficient way to do things anyway!
from leptos.
I think having an opaque type returned by view! {}
is not a big deal, as long as there exists a way of getting the element, such as adding a create_ref
which would allow one to do something like <div ref=ref_ />
.
I had originally structured the example like that on purpose to also demonstrate that I didn't see an easy way of returning an empty view
, which is very common for UI component libs as they often have to toggle between there being a node, and there not being a node, such as in the following example:
let helper_text = view! { cx,
if some_signal() {
view! { <label>The help is on the way</label> }
} else { view! { cx, } }
};
view! { cx,
<div class="form-control">
<label>"The input"</label>
<input />
{helper_text}
</div>
}
from leptos.
In this specific case, what you probably want is to use an Option
, so something more like
let helper_text = move || some_signal().then(|| {
view! { cx, <label>The help is on the way</label> }
});
view! { cx,
<div class="form-control">
<label>"The input"</label>
<input />
{helper_text}
</div>
}
This actually works fine because Fn() -> Option<C>
is implemented where C: IntoChild
.
from leptos.
That is correct, that is much better and what I did want to originally achieve.
This, however, doesn't seem to be possible with the macro, perhaps you can shed light on how to achieve the same result without relying on wrapping with a <span />
? This is a common pattern for context providers:
fn Provider(cx: Scope) -> Element {
// ...
// Init the provider stuff
// ...
let children = move || {
is_initialized().then(|| {
provide_context(cx, ProvideCtx(/* ... */));
children()
})
};
view! { cx,
<span> // <-- ideally, remove these
{children}
</span> // <-- ideally, remove these
}
}
I know the pattern above lends itself to be async, and thus should probably use Suspense
, but the problem is the same; there's no way to return Fn() -> Option<Element>
as in your example without first wrapping with a dummy <span />
.
A potential solution would be to not use the macro and change the fn
signature to return impl Fn() -> Option<Element>
, and then use this provider as a child of something else, but this just bubbles the problem up, eventually you'll need to have a <div>
or <span>
wrapping the provider, which isn't too much of a big deal, but not ideal.
from leptos.
I found a workaround which seems to work in the meantime.
If we create a dummy component:
#[component]
pub fn Fragment(cx: Scope, children: Box<dyn Fn() -> Vec<Element>>) -> Vec<Element> {
children()
}
And then we use this in the view! {}
macro, it works without needing to create any wrapper elements.
view! {
<Fragment>
/* do your thing */
</Fragment>
}
@gbj would this be a potential solution for this issue? To add a Fragment
component to the lib and have the macro create a Fragment
when using <></>
?
Edit: No, I see it does not, because the fragment returns a Vec<Element>
, but would actually need to return Element
in order to work.
from leptos.
Re: the context provider example: Can't you achieve exactly what you're trying to achieve by simply returning children
? The view
macro doesn't have any secret sauce other than creating HTML/DOM elements and making sure they are reactively updated. For a context provider, I'd assume you're just trying to do some work to provide that context and pass through the children
; you can just return children
.
More generally: leptos::Element
is a type alias for web_sys::Element
. If you want to return something more generic from your component functions, you can always return impl IntoChild
or even Child
(by returning view! { cx, ... }.into_child(cx)
.
Is the goal with the Fragment
component to solve the original problem of being able to (basically) return a heterogeneous list without a wrapping DOM element? In that case what you really need to return is probably Vec<Child>
, by returning something like children().into_iter().map(move |child| child.into_child(cx)).collect()
.
from leptos.
This almost works, except for two situations.
- Mountable is not implemented for Child, therefore you always need at least a top level wrapping element.
- Components receive a
Box::new(something)
, so I'm not sure if it would allow receiving children of mixed types? Mixed types beingElement
,Vec<Element>
, andChild
I presume point 2 is nil, but I play around with it and report back. 1. for sure doesn't work.
from leptos.
- Hm yeah. I had not actually thought of this before, but if you really wanted to mount a
Child
to the DOM as the root, you could callleptos::insert(cx, parent, child, Marker::NoChildren, None)
.insert
is basically an internal function but I can add some docs as it would be useful for a case like this. - Yeah the
children
prop is pretty much bound toBox::new(dyn Fn() -> Vec<T>)
— I've messed around with it a lot and this is kind of the best option to hydrate things in the correct order and allow a certain amount of generic-ness. For example the router allows a<Routes>
component that takes<Route>
children, which returnRouteDefinition
and not any kind of element type.
You're right that this prevents you from doing that Fragment
component yourself.
from leptos.
Why not impl IntoChild
for Vec<Child>
and have components always return Child
instead of Element
or Vec<Element>
? Then just provide a helper method on Child
which would return the closest web_sys::Node
. This would require only implementing Mountable
only for Child
and simplifies the API, I think.
from leptos.
I'd be open to doing this in the macro in certain circumstances: for example, if you're creating a fragment, and not every item is an element (so, your first example). This can be detected at macro time, and allows returning Vec<Child>
in this case while sticking with Element
or Vec<Element>
when those are known to be true.
The larger issue, believe it or not, is that Child
doesn't actually have a Child::Children
variant. At the moment the renderer can handle things if a Child
is a Vec<Node>
but not a Vec<Child>
.
The whole DOM renderer is built on this type so I'll have to think about how it would work to handle it recursively.
I think this may be a good compromise solution.
from leptos.
Okay I just had an idea: if the fragment syntax <></>
returns an actual DocumentFragment
, it should actually be possible to mix and match types in exactly the way we want to. DocumentFragment
is a Node
so I'm pretty sure it implements IntoChild
all on its own.
I'm going to play around with that today and see if I can come up with a good solution that doesn't involve introducing another type and more runtime complexity.
from leptos.
I'm going to close this issue for now... This is clearly what we're doing with #116 and we can archive this discussion.
from leptos.
Related Issues (20)
- DynChild is populated as a single Child in the components' children property HOT 4
- Breaking change in view macro between 0.6.10 and 0.6.11
- Allow custom derive for server_fns with custom encoding HOT 3
- Add Trigger::dispose
- leptos_macro 0.6.11 leptos::view! breaks with some comments
- Non-'static signals
- the format!() bug in <a href={format!()}> HOT 2
- Effects/Memos should't be able to "own" a signal HOT 4
- Nested Suspense ignores SSR Mode and breaks Hydration HOT 2
- Error when running server function inside spawn_local HOT 5
- Click events are registered twice when two leptos custom elements are on a page HOT 3
- about mobile support
- Recursive effects never run after recursing HOT 2
- Clippy warnings in some components with rust 1.78 HOT 1
- rkyv, avoid the copy to byte ? HOT 4
- Check ActionForm input names at compile time HOT 4
- Allow server components in islands architecture to call code behind ssr without need for #[server] HOT 1
- porting over some react code to my project, having hydration issues
- leptos_router parent Route attr view is not generating view for dynamic routes HOT 4
- relese build causes SIGSEGV and SIGBUS while dev build is just fine HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from leptos.