Skip to content

Commit

Permalink
Further API refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
Diggsey committed May 28, 2021
1 parent a1b7470 commit 7c49979
Show file tree
Hide file tree
Showing 18 changed files with 424 additions and 296 deletions.
69 changes: 35 additions & 34 deletions examples/router/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::rc::Rc;

use yew::prelude::*;
use yew_router::prelude::*;
use yew_router::{prelude::*, router::RouterListener};

mod components;
mod content;
Expand All @@ -9,7 +11,6 @@ use pages::{
author::Author, author_list::AuthorList, home::Home, page_not_found::PageNotFound, post::Post,
post_list::PostList,
};
use yew_router::router::RouterUpdate;

#[derive(Routable, PartialEq, Clone, Debug)]
pub enum Route {
Expand Down Expand Up @@ -38,24 +39,28 @@ impl Default for Route {

pub enum Msg {
ToggleNavbar,
RouteChanged(RouterUpdate<Route>),
RouteChanged(Rc<Route>),
}

pub struct Model {
link: ComponentLink<Self>,
router: Router<Route>,
route: Rc<Route>,
navbar_active: bool,
_listener: RouterListener<Route>,
}
impl Component for Model {
type Message = Msg;
type Properties = ();

fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self {
let router = Router::new(link.clone(), link.callback(Msg::RouteChanged));
let router = Router::new(link.clone());
let route = router.current();
let _listener = router.register(link.callback(Msg::RouteChanged));
Self {
link,
router,
route,
navbar_active: false,
_listener,
}
}

Expand All @@ -65,8 +70,8 @@ impl Component for Model {
self.navbar_active = !self.navbar_active;
true
}
Msg::RouteChanged(update) => {
self.router.update(update);
Msg::RouteChanged(route) => {
self.route = route;
true
}
}
Expand All @@ -81,9 +86,28 @@ impl Component for Model {
<>
{ self.view_nav() }

<main>
{ switch(&self.router.route()) }
</main>
<main>{
match &*self.route {
Route::Post { id } => {
html! { <Post seed=*id /> }
}
Route::Posts { page } => {
html! { <PostList page=*page /> }
}
Route::Author { id } => {
html! { <Author seed=*id /> }
}
Route::Authors => {
html! { <AuthorList /> }
}
Route::Home => {
html! { <Home /> }
}
Route::NotFound => {
html! { <PageNotFound /> }
}
}
}</main>
<footer class="footer">
<div class="content has-text-centered">
{ "Powered by " }
Expand Down Expand Up @@ -151,29 +175,6 @@ impl Model {
}
}

fn switch(routes: &Route) -> Html {
match routes {
Route::Post { id } => {
html! { <Post seed=*id /> }
}
Route::Posts { page } => {
html! { <PostList page=*page /> }
}
Route::Author { id } => {
html! { <Author seed=*id /> }
}
Route::Authors => {
html! { <AuthorList /> }
}
Route::Home => {
html! { <Home /> }
}
Route::NotFound => {
html! { <PageNotFound /> }
}
}
}

fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
yew::start_app::<Model>();
Expand Down
2 changes: 1 addition & 1 deletion examples/router/src/pages/post_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ impl Component for PostList {
type Properties = PostListProps;

fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
let router = Router::new(link, Callback::noop());
let router = Router::new(link);
Self { router, props }
}

Expand Down
24 changes: 12 additions & 12 deletions packages/yew-functional/src/hooks/use_context.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::{get_current_scope, use_hook};
use yew::context::ContextHandle;
use yew::{
context::{Context, ContextListener},
Callback,
};

/// Hook for consuming context values in function components.
/// The context of the type passed as `T` is returned. If there is no such context in scope, `None` is returned.
Expand Down Expand Up @@ -32,7 +35,7 @@ use yew::context::ContextHandle;
pub fn use_context<T: Clone + PartialEq + 'static>() -> Option<T> {
struct UseContextState<T2: Clone + PartialEq + 'static> {
initialized: bool,
context: Option<(T2, ContextHandle<T2>)>,
context: Option<(Context<T2>, ContextListener<T2>)>,
}

let scope = get_current_scope()
Expand All @@ -46,18 +49,15 @@ pub fn use_context<T: Clone + PartialEq + 'static>() -> Option<T> {
|state: &mut UseContextState<T>, updater| {
if !state.initialized {
state.initialized = true;
let callback = move |ctx: T| {
updater.callback(|state: &mut UseContextState<T>| {
if let Some(context) = &mut state.context {
context.0 = ctx;
}
true
});
};
state.context = scope.context::<T>(callback.into());
if let Some(context) = scope.context::<T>() {
let listener = context.register(Callback::from(move |_| {
updater.callback(|_: &mut UseContextState<T>| true);
}));
state.context = Some((context, listener));
}
}

Some(state.context.as_ref()?.0.clone())
Some(state.context.as_ref()?.0.current())
},
|state| {
state.context = None;
Expand Down
91 changes: 91 additions & 0 deletions packages/yew-router/src/components/consumer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use std::{
fmt::{self, Debug},
rc::Rc,
};

use yew::{Component, ComponentLink, Html, Properties};

use crate::{router::RouterListener, Routable, Router};

/// Wraps `Rc` around `Fn` so it can be passed as a prop.
pub struct RenderFn<T: Routable>(Rc<dyn Fn(&Router<T>) -> Html>);

impl<T: Routable> RenderFn<T> {
/// Creates a new [`RenderFn`]
///
/// It is recommended that you use [`Router::render`] instead
pub fn new(value: impl Fn(&Router<T>) -> Html + 'static) -> Self {
Self(Rc::new(value))
}
}

impl<T: Routable> Debug for RenderFn<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("RenderFn").finish()
}
}

impl<T: Routable> Clone for RenderFn<T> {
fn clone(&self) -> Self {
Self(Rc::clone(&self.0))
}
}

impl<T: Routable> PartialEq for RenderFn<T> {
fn eq(&self, other: &Self) -> bool {
// https://github.com/rust-lang/rust-clippy/issues/6524
#[allow(clippy::vtable_address_comparisons)]
Rc::ptr_eq(&self.0, &other.0)
}
}

/// Props for [`RouterConsumer`]
#[derive(Clone, PartialEq, Properties)]
pub struct RouterConsumerProps<T: Routable> {
/// Callback which returns [`Html`] to be rendered for the current route.
pub render: RenderFn<T>,
}

/// The context provider component.
///
/// Every child (direct or indirect) of this component may access the context value.
/// In order to consume contexts, [`ComponentLink::context`][Scope::context] method is used,
/// In function components the `use_context` hook is used.
#[derive(Debug)]
pub struct RouterConsumer<T: Routable> {
render: RenderFn<T>,
router: Router<T>,
_listener: RouterListener<T>,
}

impl<T: Routable> Component for RouterConsumer<T> {
type Message = ();
type Properties = RouterConsumerProps<T>;

fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
let router = Router::new(link.clone());
let _listener = router.register(link.callback(|_| ()));
Self {
render: props.render,
router,
_listener,
}
}

fn update(&mut self, _msg: Self::Message) -> bool {
true
}

fn change(&mut self, props: Self::Properties) -> bool {
if self.render != props.render {
self.render = props.render;
true
} else {
false
}
}

fn view(&self) -> Html {
(&self.render.0)(&self.router)
}
}
7 changes: 3 additions & 4 deletions packages/yew-router/src/components/link.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use crate::router::use_router;
use crate::services::router::RouterAction;
use crate::Routable;
use crate::{Routable, RouterAction};
use yew::prelude::*;

use yew_functional::function_component;

/// Props for [`Link`]
#[derive(Properties, Clone, PartialEq)]
pub struct LinkProps<R: Routable + Clone> {
pub struct LinkProps<R: Routable> {
/// CSS classes to add to the anchor element (optional).
#[prop_or_default]
pub classes: Classes,
Expand All @@ -19,7 +18,7 @@ pub struct LinkProps<R: Routable + Clone> {
#[function_component(Link)]
pub fn link<R>(props: &LinkProps<R>) -> Html
where
R: Routable + Clone + PartialEq + 'static,
R: Routable,
{
let router = use_router::<R>();
let route = props.route.clone();
Expand Down
6 changes: 6 additions & 0 deletions packages/yew-router/src/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,9 @@
mod link;
pub use link::*;

mod mount;
pub use mount::*;

mod consumer;
pub use consumer::*;
63 changes: 63 additions & 0 deletions packages/yew-router/src/components/mount.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use std::rc::Rc;

use yew::{html, Callback, Children, Component, ComponentLink, ContextProvider, Html, Properties};

use crate::{context::RoutingContext, Routable, RouterAction};

/// Props for [`ContextProvider`]
#[derive(Debug, Clone, PartialEq, Properties)]
pub struct MountProps<T: Routable> {
/// Context value to be passed down
pub route: Rc<T>,
pub onroute: Callback<RouterAction<T>>,
/// Children
pub children: Children,
}

/// The context provider component.
///
/// Every child (direct or indirect) of this component may access the context value.
/// In order to consume contexts, [`ComponentLink::context`][Scope::context] method is used,
/// In function components the `use_context` hook is used.
#[derive(Debug)]
pub struct Mount<T: Routable> {
context: RoutingContext<T>,
children: Children,
}

impl<T: Routable> Component for Mount<T> {
type Message = ();
type Properties = MountProps<T>;

fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
Self {
children: props.children,
context: RoutingContext {
route: props.route,
onroute: props.onroute,
},
}
}

fn update(&mut self, _msg: Self::Message) -> bool {
false
}

fn change(&mut self, props: Self::Properties) -> bool {
self.children = props.children;
self.context = RoutingContext {
route: props.route,
onroute: props.onroute,
};
true
}

fn view(&self) -> Html {
let context = self.context.clone();
html! {
<ContextProvider<RoutingContext<T>> context=context>
{ self.children.clone() }
</ContextProvider<RoutingContext<T>>>
}
}
}
12 changes: 10 additions & 2 deletions packages/yew-router/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@ use std::rc::Rc;

use yew::Callback;

use crate::services::router::RouterAction;
use crate::Routable;
use crate::{Routable, RouterAction};

#[derive(Debug, PartialEq)]
pub struct RoutingContext<T: Routable> {
pub(crate) route: Rc<T>,
pub(crate) onroute: Callback<RouterAction<T>>,
}

impl<T: Routable> Clone for RoutingContext<T> {
fn clone(&self) -> Self {
Self {
route: self.route.clone(),
onroute: self.onroute.clone(),
}
}
}
Loading

0 comments on commit 7c49979

Please sign in to comment.