Feat: massive changes to definition of components

This change switches back to the original `ctx<props>` syntax for
commponents. This lets lifetime elision to remove the need to match
exactly which lifetime (props or ctx) gets  carried to the output. As
such, `Props` is currently required to be static. It *is* possible to
loosen this restriction, and will be done in the future, though only
through adding metadata about the props through the Props derive
macro. Implementing the IS_STATIC trait is unsafe, so the derive macro
will do it through some heuristics.

For now, this unlocks sharing vnodes from parents to children, enabling
pass-thru components, fragments, portals, etc.
This commit is contained in:
Jonathan Kelley committed 2021-06-01 18:33:15 -04:00
1 parent c1fd848f89
commit 508c560320
82 files changed
+692 -558

No files matched your search

+4 -4
View File
@@ -21,7 +21,7 @@ This atom of state is initialized with a value of `"Green"`. The atom that is re
This is then later used in components like so:
```rust
fn App(ctx: Context, props: &()) -> DomTree {
fn App(ctx: Context<()>) -> VNode {
// The recoil root must be initialized at the top of the application before any use_recoil hooks
recoil::init_recoil_root(&ctx, |_| {});
@@ -36,7 +36,7 @@ fn App(ctx: Context, props: &()) -> DomTree {
Atoms are considered "Writable" objects since any consumer may also set the Atom's value with their own:
```rust
fn App(ctx: Context, props: &()) -> DomTree {
fn App(ctx: Context<()>) -> VNode {
let color = recoil::use_read(ctx, Light);
let set_color = recoil::use_write(ctx, Light);
rsx!{in ctx,
@@ -53,7 +53,7 @@ fn App(ctx: Context, props: &()) -> DomTree {
"Reading" a value with use_read subscribes that component to any changes to that value while "Writing" a value does not. It's a good idea to use `write-only` whenever it makes sense to prevent unnecessary evaluations. Both `read` and `write` may be combined together to provide a `use_state` style hook.
```rust
fn App(ctx: Context, props: &()) -> DomTree {
fn App(ctx: Context<()>) -> VNode {
let (color, set_color) = recoil::use_read_write(ctx, Light);
rsx!{in ctx,
div {
@@ -125,7 +125,7 @@ const CloudRatings: AtomFamily<&str, i32> = |api| {
Whenever you `select` on a `Family`, the ID of the entry is tracked. Other subscribers will only be updated if they too select the same family with the same key and that value is updated. Otherwise, these subscribers will never re-render on an "insert", "remove", or "update" of the collection. You could easily implement this yourself with Atoms, immutable datastructures, and selectors, but our implementation is more efficient and first-class.
```rust
fn App(ctx: Context, props: &()) -> DomTree {
fn App(ctx: Context<()>) -> VNode {
let (rating, set_rating) = recoil::use_read_write(ctx, CloudRatings.select("AWS"));
rsx!{in ctx,
div {
+1 -1
View File
@@ -28,7 +28,7 @@ impl TitleController {
}
fn main() {
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(|ctx, _| {
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(|ctx| {
let title = use_read(ctx, &TITLE);
let subtitle = use_read(ctx, &SUBTITLE);
let controller = use_recoil_api(ctx, TitleController::new);
+1 -1
View File
@@ -19,7 +19,7 @@ fn update_title(api: &RecoilApi) {
}
}
static App: FC<()> = |ctx, _| {
static App: FC<()> = |ctx| {
let title = use_read(ctx, &TITLE);
let next_light = use_recoil_api(ctx, |api| move |_| update_title(&api));
+1 -1
View File
@@ -31,7 +31,7 @@ const TODOS: EcsModel<u32, TodoModel> = |builder| {};
// const SELECT_TITLE: SelectorBorrowed<u32, &str> = |s, k| TODOS.field(0).select(k);
// const SELECT_SUBTITLE: SelectorBorrowed<u32, &str> = |s, k| TODOS.field(1).select(k);
static App: FC<()> = |ctx, _| {
static App: FC<()> = |ctx| {
use_init_recoil_root(ctx, |_| {});
// let title = use_recoil_value(ctx, &C_SELECTOR);
+3 -3
View File
@@ -13,7 +13,7 @@ struct Todo {
content: String,
}
static App: FC<()> = |ctx, _| {
static App: FC<()> = |ctx| {
use_init_recoil_root(ctx, move |cfg| {});
let todos = use_read_family(ctx, &TODOS);
@@ -36,8 +36,8 @@ struct ChildProps {
id: Uuid,
}
static Child: FC<ChildProps> = |ctx, props| {
let (todo, set_todo) = use_read_write(ctx, &TODOS.select(&props.id));
static Child: FC<ChildProps> = |ctx| {
let (todo, set_todo) = use_read_write(ctx, &TODOS.select(&ctx.id));
rsx! { in ctx,
li {
+3 -3
View File
@@ -12,7 +12,7 @@ struct Todo {
contents: String,
}
static App: FC<()> = |ctx, _| {
static App: FC<()> = |ctx| {
use_init_recoil_root(ctx, |_| {});
let todos = use_read(ctx, &TODOS);
@@ -28,8 +28,8 @@ struct ChildProps {
id: Uuid,
}
static Child: FC<ChildProps> = |ctx, props| {
let todo = use_read(ctx, &TODOS).get(&props.id).unwrap();
static Child: FC<ChildProps> = |ctx| {
let todo = use_read(ctx, &TODOS).get(&ctx.id).unwrap();
// let (todo, set_todo) = use_read_write(ctx, &TODOS);
rsx! { in ctx,
+1 -1
View File
@@ -3,7 +3,7 @@ use recoil::*;
const COUNT: Atom<i32> = |_| 0;
static App: FC<()> = |ctx, _| {
static App: FC<()> = |ctx| {
use_init_recoil_root(ctx, |_| {});
let (count, set_count) = use_read_write(ctx, &COUNT);
+1 -1
View File
@@ -17,7 +17,7 @@ const D_SELECTOR: SelectorFamilyBorrowed<i32, i32> = |api, key| -> &i32 {
a
};
static App: FC<()> = |ctx, _| {
static App: FC<()> = |ctx| {
use_init_recoil_root(ctx, |_| {});
let title = use_read(ctx, &C_SELECTOR);
+4 -4
View File
@@ -5,7 +5,7 @@ const A: Atom<i32> = |_| 0;
const B: Atom<i32> = |_| 0;
const C: Selector<i32> = |api| api.get(&A) + api.get(&B);
static App: FC<()> = |ctx, _| {
static App: FC<()> = |ctx| {
use_init_recoil_root(ctx, |_| {});
rsx! { in ctx,
div {
@@ -16,12 +16,12 @@ static App: FC<()> = |ctx, _| {
}
};
static Banner: FC<()> = |ctx, _| {
static Banner: FC<()> = |ctx| {
let count = use_read(ctx, &C);
ctx.render(rsx! { h1 { "Count: {count}" } })
};
static BtnA: FC<()> = |ctx, _| {
static BtnA: FC<()> = |ctx| {
let (a, set) = use_read_write(ctx, &A);
rsx! { in ctx,
div { "a"
@@ -31,7 +31,7 @@ static BtnA: FC<()> = |ctx, _| {
}
};
static BtnB: FC<()> = |ctx, _| {
static BtnB: FC<()> = |ctx| {
let (b, set) = use_read_write(ctx, &B);
rsx! { in ctx,
div { "b"
+2 -2
View File
@@ -93,8 +93,8 @@ const ContentCards: SelectorFamily<Uuid, ContentCard> = |api, key| api.on_get_as
data
})
static ContentCard: FC<()> = |ctx, props| {
let body = async match use_recoil_value()(props.id).await {
static ContentCard: FC<()> = |ctx| {
let body = async match use_recoil_value()(ctx.id).await {
Ok(content) => rsx!{in ctx, p {"{content}"} }
Err(e) => rsx!{in ctx, p {"Failed to load"}}
};
+28 -22
View File
@@ -31,7 +31,7 @@ mod traits {
// Atoms, selectors, and their family variants are readable
pub trait Readable<T: AtomValue>: Sized + Copy {
fn use_read<'a>(self, ctx: Context<'a>) -> &'a T {
fn use_read<'a, P: 'static>(self, ctx: Context<'a, P>) -> &'a T {
hooks::use_read(ctx, self)
}
@@ -39,7 +39,7 @@ mod traits {
// If the atom is currently pending, that future will resolve to pending
// If the atom is currently ready, the future will immediately resolve
// if the atom switches from ready to pending, the component will re-run, returning a pending future
fn use_read_async<'a>(self, ctx: Context<'a>) -> &'a T {
fn use_read_async<'a, P>(self, ctx: Context<'a, P>) -> &'a T {
todo!()
}
@@ -53,11 +53,11 @@ mod traits {
// Only atoms and atom families are writable
// Selectors and selector families are not
pub trait Writable<T: AtomValue>: Readable<T> + Sized {
fn use_read_write<'a>(self, ctx: Context<'a>) -> (&'a T, &'a Rc<dyn Fn(T)>) {
fn use_read_write<'a, P>(self, ctx: Context<'a, P>) -> (&'a T, &'a Rc<dyn Fn(T)>) {
todo!()
}
fn use_write<'a>(self, ctx: Context<'a>) -> &'a Rc<dyn Fn(T)> {
fn use_write<'a, P>(self, ctx: Context<'a, P>) -> &'a Rc<dyn Fn(T)> {
todo!()
}
}
@@ -91,7 +91,7 @@ mod atoms {
use super::*;
use dioxus_core::prelude::Context;
fn _test(ctx: Context) {
fn _test(ctx: Context<()>) {
const EXAMPLE_ATOM: Atom<i32> = |_| 10;
// ensure that atoms are both read and write
@@ -159,7 +159,7 @@ mod atomfamily {
use super::*;
const Titles: AtomHashMap<u32, &str> = |map| {};
fn test(ctx: Context) {
fn test(ctx: Context<()>) {
let title = Titles.select(&10).use_read(ctx);
let t2 = use_read(ctx, &Titles.select(&10));
}
@@ -370,7 +370,7 @@ mod hooks {
use super::*;
use dioxus_core::{hooks::use_ref, prelude::Context};
pub fn use_init_recoil_root(ctx: Context, cfg: impl Fn(())) {
pub fn use_init_recoil_root<P>(ctx: Context<P>, cfg: impl Fn(())) {
ctx.use_create_context(move || RefCell::new(RecoilRoot::new()))
}
@@ -381,12 +381,12 @@ mod hooks {
///
/// You can use this method to create controllers that perform much more complex actions than set/get
/// However, be aware that "getting" values through this hook will not subscribe the component to any updates.
pub fn use_recoil_api<'a>(ctx: Context<'a>) -> &Rc<RecoilContext> {
pub fn use_recoil_api<'a, P>(ctx: Context<'a, P>) -> &Rc<RecoilContext> {
ctx.use_context::<RecoilContext>()
}
pub fn use_write<'a, T: AtomValue>(
ctx: Context<'a>,
pub fn use_write<'a, T: AtomValue, P>(
ctx: Context<'a, P>,
// todo: this shouldn't need to be static
writable: impl Writable<T>,
) -> &'a Rc<dyn Fn(T)> {
@@ -412,7 +412,10 @@ mod hooks {
/// Read the atom and get the Rc directly to the Atom's slot
/// This is useful if you need the memoized Atom value. However, Rc<T> is not as easy to
/// work with as
pub fn use_read_raw<'a, T: AtomValue>(ctx: Context<'a>, readable: impl Readable<T>) -> &Rc<T> {
pub fn use_read_raw<'a, T: AtomValue, P: 'static>(
ctx: Context<'a, P>,
readable: impl Readable<T>,
) -> &Rc<T> {
struct ReadHook<T> {
value: Rc<T>,
consumer_id: u32,
@@ -446,7 +449,10 @@ mod hooks {
}
///
pub fn use_read<'a, T: AtomValue>(ctx: Context<'a>, readable: impl Readable<T>) -> &'a T {
pub fn use_read<'a, T: AtomValue, P: 'static>(
ctx: Context<'a, P>,
readable: impl Readable<T>,
) -> &'a T {
use_read_raw(ctx, readable).as_ref()
}
@@ -465,8 +471,8 @@ mod hooks {
/// // equivalent to:
/// let (title, set_title) = (use_read(ctx, &Title), use_write(ctx, &Title));
/// ```
pub fn use_read_write<'a, T: AtomValue + 'static>(
ctx: Context<'a>,
pub fn use_read_write<'a, T: AtomValue + 'static, P: 'static>(
ctx: Context<'a, P>,
writable: impl Writable<T>,
) -> (&'a T, &'a Rc<dyn Fn(T)>) {
(use_read(ctx, writable), use_write(ctx, writable))
@@ -492,8 +498,8 @@ mod hooks {
///
/// modify_atom(&|a| *a += 1)
/// ```
pub fn use_modify<'a, T: AtomValue + 'static + Clone>(
ctx: Context<'a>,
pub fn use_modify<'a, T: AtomValue + 'static + Clone, P>(
ctx: Context<'a, P>,
writable: impl Writable<T>,
) -> impl Fn(&dyn Fn()) {
|_| {}
@@ -502,8 +508,8 @@ mod hooks {
/// Use a family collection directly
/// !! Any changes to the family will cause this subscriber to update
/// Try not to put this at the very top-level of your app.
pub fn use_read_family<'a, K, V>(
ctx: Context<'a>,
pub fn use_read_family<'a, K, V, P>(
ctx: Context<'a, P>,
t: &AtomHashMap<K, V>,
) -> &'a im_rc::HashMap<K, V> {
todo!()
@@ -525,11 +531,11 @@ mod utils {
/// This tiny util wraps your main component with the initializer for the recoil root.
/// This is useful for small programs and the examples in this crate
pub fn RecoilApp<T: 'static>(
root: impl for<'a> Fn(Context<'a>, &'a T) -> DomTree,
) -> impl for<'a> Fn(Context<'a>, &'a T) -> DomTree {
move |ctx, props| {
root: impl for<'a> Fn(Context<'a, T>) -> VNode,
) -> impl for<'a> Fn(Context<'a, T>) -> VNode {
move |ctx| {
use_init_recoil_root(ctx, |_| {});
root(ctx, props)
root(ctx)
}
}
}