Get router working with new renderer

This commit is contained in:
Greg Johnston 2022-12-10 08:32:58 -05:00
parent f9cc57acb9
commit 5bdd150347
8 changed files with 79 additions and 65 deletions

View file

@ -40,13 +40,13 @@ where
#[builder(default, setter(strip_option))]
pub on_response: Option<Rc<dyn Fn(&web_sys::Response)>>,
/// Component children; should include the HTML of the form elements.
pub children: Box<dyn Fn() -> Vec<Element>>,
pub children: Box<dyn Fn() -> Vec<View>>,
}
/// An HTML [`form`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) progressively
/// enhanced to use client-side routing.
#[allow(non_snake_case)]
pub fn Form<A>(cx: Scope, props: FormProps<A>) -> Element
pub fn Form<A>(cx: Scope, props: FormProps<A>) -> View
where
A: ToHref + 'static,
{
@ -130,11 +130,12 @@ where
};
let children = children();
let method = method.unwrap_or("get");
view! { cx,
<form
method=method
action=action
action=move || action.get()
enctype=enctype
on:submit=on_submit
>
@ -158,14 +159,14 @@ where
/// manually using [leptos_server::Action::using_server_fn].
pub action: Action<I, Result<O, ServerFnError>>,
/// Component children; should include the HTML of the form elements.
pub children: Box<dyn Fn() -> Vec<Element>>,
pub children: Box<dyn Fn() -> Vec<View>>,
}
/// Automatically turns a server [Action](leptos_server::Action) into an HTML
/// [`form`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form)
/// progressively enhanced to use client-side routing.
#[allow(non_snake_case)]
pub fn ActionForm<I, O>(cx: Scope, props: ActionFormProps<I, O>) -> Element
pub fn ActionForm<I, O>(cx: Scope, props: ActionFormProps<I, O>) -> View
where
I: Clone + ServerFn + 'static,
O: Clone + Serializable + 'static,
@ -242,14 +243,14 @@ where
/// manually using [leptos_server::Action::using_server_fn].
pub action: MultiAction<I, Result<O, ServerFnError>>,
/// Component children; should include the HTML of the form elements.
pub children: Box<dyn Fn() -> Vec<Element>>,
pub children: Box<dyn Fn() -> Vec<View>>,
}
/// Automatically turns a server [MultiAction](leptos_server::MultiAction) into an HTML
/// [`form`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form)
/// progressively enhanced to use client-side routing.
#[allow(non_snake_case)]
pub fn MultiActionForm<I, O>(cx: Scope, props: MultiActionFormProps<I, O>) -> Element
pub fn MultiActionForm<I, O>(cx: Scope, props: MultiActionFormProps<I, O>) -> View
where
I: Clone + ServerFn + 'static,
O: Clone + Serializable + 'static,

View file

@ -1,5 +1,5 @@
use cfg_if::cfg_if;
use leptos::leptos_dom::IntoChild;
use leptos::leptos_dom::IntoView;
use leptos::*;
use typed_builder::TypedBuilder;
@ -43,9 +43,8 @@ where
/// [`a`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a)
/// progressively enhanced to use client-side routing.
#[derive(TypedBuilder)]
pub struct AProps<C, H>
pub struct AProps<H>
where
C: IntoChild,
H: ToHref + 'static,
{
/// Used to calculate the link's `href` attribute. Will be resolved relative
@ -63,15 +62,14 @@ where
#[builder(default)]
pub replace: bool,
/// The nodes or elements to be shown inside the link.
pub children: Box<dyn Fn() -> Vec<C>>,
pub children: Box<dyn Fn() -> Vec<View>>,
}
/// An HTML [`a`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a)
/// progressively enhanced to use client-side routing.
#[allow(non_snake_case)]
pub fn A<C, H>(cx: Scope, props: AProps<C, H>) -> Element
pub fn A<H>(cx: Scope, props: AProps<H>) -> View
where
C: IntoChild,
H: ToHref + 'static,
{
let location = use_location(cx);
@ -94,12 +92,6 @@ where
}
});
let mut children = (props.children)();
if children.len() != 1 {
debug_warn!("[Link] Pass exactly one child to <A/>. If you want to pass more than one child, nest them within an element.");
}
let child = children.remove(0);
cfg_if! {
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
view! { cx,
@ -109,7 +101,7 @@ where
prop:replace={props.replace}
aria-current=move || if is_active.get() { Some("page") } else { None }
>
{child}
{props.children}
</a>
}
} else {
@ -118,7 +110,7 @@ where
href=move || href().unwrap_or_default()
aria-current=move || if is_active() { Some("page") } else { None }
>
{child}
{props.children}
</a>
}
}

View file

@ -1,16 +1,33 @@
use std::{cell::RefCell, rc::Rc};
use crate::use_route;
use leptos::*;
/// Displays the child route nested in a parent route, allowing you to control exactly where
/// that child route is displayed. Renders nothing if there is no nested child.
#[component]
pub fn Outlet(cx: Scope) -> Child {
pub fn Outlet(cx: Scope) -> View {
let route = use_route(cx);
create_memo(cx, move |_| {
route.child().map(|child| {
provide_context(child.cx(), child.clone());
child.outlet().into_child(child.cx())
})
})
.into_child(cx)
let is_showing = Rc::new(RefCell::new(None));
let (outlet, set_outlet) = create_signal(cx, None);
create_effect(cx, move |_| {
let is_showing_val = { is_showing.borrow().clone() };
let child = route.child();
match (route.child(), &is_showing_val) {
(None, _) => {
set_outlet.set(None);
}
(Some(child), Some(path))
if Some(child.original_path().to_string()) == is_showing_val =>
{
// do nothing: we don't need to rerender the component, because it's the same
}
(Some(child), _) => {
*is_showing.borrow_mut() = Some(child.original_path().to_string());
provide_context(child.cx(), child.clone());
set_outlet.set(Some(child.outlet().into_view(cx)))
}
}
});
(move || outlet.get()).into_view(cx)
}

View file

@ -14,7 +14,7 @@ use crate::{
#[derive(TypedBuilder)]
pub struct RouteProps<E, F>
where
E: IntoChild,
E: IntoView,
F: Fn(Scope) -> E + 'static,
{
/// The path fragment that this route should match. This can be static (`users`),
@ -35,13 +35,13 @@ where
#[allow(non_snake_case)]
pub fn Route<E, F>(_cx: Scope, props: RouteProps<E, F>) -> RouteDefinition
where
E: IntoChild,
E: IntoView,
F: Fn(Scope) -> E + 'static,
{
RouteDefinition {
path: props.path,
children: props.children.map(|c| c()).unwrap_or_default(),
element: Rc::new(move |cx| (props.element)(cx).into_child(cx)),
element: Rc::new(move |cx| (props.element)(cx).into_view(cx)),
}
}
@ -87,17 +87,30 @@ impl RouteContext {
self.inner.cx
}
/// Returns the URL path of the current route.
/// Returns the URL path of the current route,
/// including param values in their places.
///
/// e.g., this will return `/article/0` rather than `/article/:id`.
/// For the opposite behavior, see [RouteContext::original_path].
pub fn path(&self) -> &str {
&self.inner.path
}
/// Returns the original URL path of the current route,
/// with the param name rather than the matched parameter itself.
///
/// e.g., this will return `/article/:id` rather than `/article/0`
/// For the opposite behavior, see [RouteContext::path].
pub fn original_path(&self) -> &str {
&self.inner.original_path
}
/// A reactive wrapper for the route parameters that are currently matched.
pub fn params(&self) -> Memo<ParamsMap> {
self.inner.params
}
pub(crate) fn base(cx: Scope, path: &str, fallback: Option<fn() -> Element>) -> Self {
pub(crate) fn base(cx: Scope, path: &str, fallback: Option<fn() -> View>) -> Self {
Self {
inner: Rc::new(RouteContextInner {
cx,
@ -106,7 +119,7 @@ impl RouteContext {
path: path.to_string(),
original_path: path.to_string(),
params: create_memo(cx, |_| ParamsMap::new()),
outlet: Box::new(move || fallback.map(|f| f().into_child(cx))),
outlet: Box::new(move || fallback.map(|f| f().into_view(cx))),
}),
}
}
@ -122,7 +135,7 @@ impl RouteContext {
}
/// The view associated with the current route.
pub fn outlet(&self) -> impl IntoChild {
pub fn outlet(&self) -> impl IntoView {
(self.inner.outlet)()
}
}
@ -134,7 +147,7 @@ pub(crate) struct RouteContextInner {
pub(crate) path: String,
pub(crate) original_path: String,
pub(crate) params: Memo<ParamsMap>,
pub(crate) outlet: Box<dyn Fn() -> Option<Child>>,
pub(crate) outlet: Box<dyn Fn() -> Option<View>>,
}
impl PartialEq for RouteContextInner {

View file

@ -27,22 +27,22 @@ pub struct RouterProps {
pub base: Option<&'static str>,
#[builder(default, setter(strip_option))]
/// A fallback that should be shown if no route is matched.
pub fallback: Option<fn() -> Element>,
pub fallback: Option<fn() -> View>,
/// The `<Router/>` should usually wrap your whole page. It can contain
/// any elements, and should include a [Routes](crate::Routes) component somewhere
/// to define and display [Route](crate::Route)s.
pub children: Box<dyn Fn() -> Vec<Element>>,
pub children: Box<dyn Fn() -> Vec<View>>,
}
/// Provides for client-side and server-side routing. This should usually be somewhere near
/// the root of the application.
#[allow(non_snake_case)]
pub fn Router(cx: Scope, props: RouterProps) -> impl IntoChild {
pub fn Router(cx: Scope, props: RouterProps) -> View {
// create a new RouterContext and provide it to every component beneath the router
let router = RouterContext::new(cx, props.base, props.fallback);
provide_context(cx, router);
props.children
props.children.into_view(cx)
}
/// Context type that contains information about the current router state.
@ -83,7 +83,7 @@ impl RouterContext {
pub(crate) fn new(
cx: Scope,
base: Option<&'static str>,
fallback: Option<fn() -> Element>,
fallback: Option<fn() -> View>,
) -> Self {
cfg_if! {
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
@ -136,9 +136,9 @@ impl RouterContext {
// 3) update the state
// this will trigger the new route match below
create_render_effect(cx, move |_| {
let LocationChange { value, state, .. } = source();
let LocationChange { value, state, .. } = source.get();
cx.untrack(move || {
if value != reference() {
if value != reference.get() {
set_reference.update(move |r| *r = value);
set_state.update(move |s| *s = state);
}

View file

@ -17,7 +17,7 @@ pub struct RoutesProps {
///
/// You should locate the `<Routes/>` component wherever on the page you want the routes to appear.
#[allow(non_snake_case)]
pub fn Routes(cx: Scope, props: RoutesProps) -> impl IntoChild {
pub fn Routes(cx: Scope, props: RoutesProps) -> View {
let router = use_context::<RouterContext>(cx).unwrap_or_else(|| {
log::warn!("<Routes/> component should be nested within a <Router/>.");
panic!()
@ -120,7 +120,7 @@ pub fn Routes(cx: Scope, props: RoutesProps) -> impl IntoChild {
if disposers.borrow().len() > i + 1 {
let mut disposers = disposers.borrow_mut();
let old_route_disposer = std::mem::replace(&mut disposers[i], disposer);
old_route_disposer.dispose();
//old_route_disposer.dispose();
} else {
disposers.borrow_mut().push(disposer);
}
@ -147,7 +147,7 @@ pub fn Routes(cx: Scope, props: RoutesProps) -> impl IntoChild {
});
// show the root route
create_memo(cx, move |prev| {
let root = create_memo(cx, move |prev| {
provide_context(cx, route_states);
route_states.with(|state| {
let root = state.routes.borrow();
@ -157,12 +157,13 @@ pub fn Routes(cx: Scope, props: RoutesProps) -> impl IntoChild {
}
if prev.is_none() || !root_equal.get() {
root.as_ref().map(|route| route.outlet().into_child(cx))
root.as_ref().map(|route| route.outlet().into_view(cx))
} else {
prev.cloned().unwrap()
}
})
})
});
(move || root.get()).into_view(cx)
}
#[derive(Clone, Debug, PartialEq)]

View file

@ -39,7 +39,7 @@
//! use leptos::*;
//! use leptos_router::*;
//!
//! pub fn router_example(cx: Scope) -> Element {
//! pub fn router_example(cx: Scope) -> View {
//! view! {
//! cx,
//! <div id="root">
@ -100,7 +100,7 @@
//! }
//!
//! #[component]
//! fn ContactList(cx: Scope) -> Element {
//! fn ContactList(cx: Scope) -> View {
//! // loads the contact list data once; doesn't reload when nested routes change
//! let contacts = create_resource(cx, || (), |_| contact_list_data());
//! view! {
@ -118,7 +118,7 @@
//! }
//!
//! #[component]
//! fn Contact(cx: Scope) -> Element {
//! fn Contact(cx: Scope) -> View {
//! let params = use_params_map(cx);
//! let data = create_resource(
//! cx,
@ -129,7 +129,7 @@
//! }
//!
//! #[component]
//! fn About(cx: Scope) -> Element {
//! fn About(cx: Scope) -> View {
//! todo!()
//! }
//!

View file

@ -1,13 +1,13 @@
use std::rc::Rc;
use leptos::leptos_dom::Child;
use leptos::leptos_dom::View;
use leptos::*;
#[derive(Clone)]
pub struct RouteDefinition {
pub path: &'static str,
pub children: Vec<RouteDefinition>,
pub element: Rc<dyn Fn(Scope) -> Child>,
pub element: Rc<dyn Fn(Scope) -> View>,
}
impl std::fmt::Debug for RouteDefinition {
@ -24,13 +24,3 @@ impl PartialEq for RouteDefinition {
self.path == other.path && self.children == other.children
}
}
impl Default for RouteDefinition {
fn default() -> Self {
Self {
path: Default::default(),
children: Default::default(),
element: Rc::new(|_| Child::Null),
}
}
}