Skip to content

Commit 85d7f4a

Browse files
authored
feat: improved global state system (#223)
* feat: made global state accessible through function This removes the function parameter method of accessing it. * refactor: made global state functions private on `RenderCtx` The macros don't handle global state now, and the user should just need `.get_global_state()`/`.try_get_global_state()`. * refactor: removed global state glob imports These used to be necessary to ensure that the macros could access the intermediate types, but the new functional access system prevents this! * chore: removed old commented code from tempalte macro * feat: removed need for importing intermediate reactive types These are now accessed purely through trait linkage, meaning no more weird glob imports for apps that stored their reactive state in different modules (or apps that were accessing state from another page outside that page). * fix: fixed `BorrowMut` errors on global state thawing * refactor: privatized global state on `RenderCtx` This prevents `BorrowMut` panics that are fairly likely if users access the global state manually. If this affects anyone's apps, please let me know! * chore: removed `get_render_ctx!()` macro This is pointless with `RenderCtx::from_ctx`. Also added `RenderCtx` to `perseus::prelude` for convenience. * chore: cleaned up a few things BREAKING CHANGES: removed `get_render_ctx!(cx)` in favor of `RenderCtx::from_ctx(cx)`; privatized global state on `RenderCtx`; made global state accessible through `render_ctx.get_global_state::<T>(cx)`, rather than through template function parameter
1 parent 825e990 commit 85d7f4a

File tree

23 files changed

+402
-295
lines changed

23 files changed

+402
-295
lines changed

examples/core/freezing_and_thawing/src/templates/about.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1+
use perseus::prelude::*;
12
use perseus::state::Freeze;
2-
use perseus::{Html, Template};
33
use sycamore::prelude::*;
44

5-
use crate::global_state::*;
5+
use crate::global_state::AppStateRx;
66

77
#[perseus::template_rx]
8-
pub fn about_page<'a, G: Html>(cx: Scope<'a>, _: (), global_state: AppStateRx<'a>) -> View<G> {
8+
pub fn about_page<'a, G: Html>(cx: Scope<'a>) -> View<G> {
99
// This is not part of our data model, we do NOT want the frozen app
1010
// synchronized as part of our page's state, it should be separate
1111
let frozen_app = create_signal(cx, String::new());
12-
let render_ctx = perseus::get_render_ctx!(cx);
12+
let render_ctx = RenderCtx::from_ctx(cx);
13+
14+
let global_state = render_ctx.get_global_state::<AppStateRx>(cx);
1315

1416
view! { cx,
1517
p(id = "global_state") { (global_state.test.get()) }

examples/core/freezing_and_thawing/src/templates/index.rs

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,22 @@
1+
use perseus::prelude::*;
12
use perseus::state::Freeze;
2-
use perseus::{Html, RenderFnResultWithCause, Template};
33
use sycamore::prelude::*;
44

5-
use crate::global_state::*;
5+
use crate::global_state::AppStateRx;
66

77
#[perseus::make_rx(IndexPropsRx)]
88
pub struct IndexProps {
99
username: String,
1010
}
1111

1212
#[perseus::template_rx]
13-
pub fn index_page<'a, G: Html>(
14-
cx: Scope<'a>,
15-
state: IndexPropsRx<'a>,
16-
global_state: AppStateRx<'a>,
17-
) -> View<G> {
13+
pub fn index_page<'a, G: Html>(cx: Scope<'a>, state: IndexPropsRx<'a>) -> View<G> {
1814
// This is not part of our data model, we do NOT want the frozen app
1915
// synchronized as part of our page's state, it should be separate
2016
let frozen_app = create_signal(cx, String::new());
21-
let render_ctx = perseus::get_render_ctx!(cx);
17+
let render_ctx = RenderCtx::from_ctx(cx);
18+
19+
let global_state = render_ctx.get_global_state::<AppStateRx>(cx);
2220

2321
view! { cx,
2422
// For demonstration, we'll let the user modify the page's state and the global state arbitrarily

examples/core/global_state/src/templates/about.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
use perseus::{Html, Template};
2-
use sycamore::prelude::{view, Scope, SsrNode, View};
1+
use perseus::prelude::*;
2+
use sycamore::prelude::*;
33

4-
use crate::global_state::*; // See the index page for why we need this
4+
use crate::global_state::AppStateRx;
55

6-
// This template needs global state, but doesn't have any state of its own, so
7-
// the first argument is the unit type `()` (which the macro will detect)
86
#[perseus::template_rx]
9-
pub fn about_page<'a, G: Html>(cx: Scope<'a>, _: (), global_state: AppStateRx<'a>) -> View<G> {
7+
pub fn about_page<'a, G: Html>(cx: Scope<'a>) -> View<G> {
8+
let global_state = RenderCtx::from_ctx(cx).get_global_state::<AppStateRx>(cx);
9+
1010
view! { cx,
1111
// The user can change the global state through an input, and the changes they make will be reflected throughout the app
1212
p { (global_state.test.get()) }

examples/core/global_state/src/templates/index.rs

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
use perseus::{Html, Template};
2-
use sycamore::prelude::{view, Scope, SsrNode, View};
1+
use perseus::prelude::*;
2+
use sycamore::prelude::*;
33

4-
use crate::global_state::*; // This is necessary because Perseus generates an invisible intermediary
5-
// `struct` that the template needs access to
4+
use crate::global_state::AppStateRx;
65

7-
// This template needs global state, but doesn't have any state of its own, so
8-
// the first argument is the unit type `()` (which the macro will detect)
6+
// Note that this template takes no state of its own in this example, but it
7+
// certainly could
98
#[perseus::template_rx]
10-
pub fn index_page<'a, G: Html>(cx: Scope<'a>, _: (), global_state: AppStateRx<'a>) -> View<G> {
9+
pub fn index_page<'a, G: Html>(cx: Scope<'a>) -> View<G> {
10+
// We access the global state through the render context, extracted from
11+
// Sycamore's context system
12+
let global_state = RenderCtx::from_ctx(cx).get_global_state::<AppStateRx>(cx);
13+
1114
view! { cx,
1215
// The user can change the global state through an input, and the changes they make will be reflected throughout the app
1316
p { (global_state.test.get()) }

examples/core/idb_freezing/src/templates/about.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
use perseus::{Html, Template};
1+
use perseus::prelude::*;
22
use sycamore::prelude::*;
33

4-
use crate::global_state::*;
4+
use crate::global_state::AppStateRx;
55

66
#[perseus::template_rx]
7-
pub fn about_page<'a, G: Html>(cx: Scope<'a>, _: (), global_state: AppStateRx<'a>) -> View<G> {
7+
pub fn about_page<'a, G: Html>(cx: Scope<'a>) -> View<G> {
88
// This is not part of our data model
99
let freeze_status = create_signal(cx, String::new());
1010
// It's faster to get this only once and rely on reactivity
1111
// But it's unused when this runs on the server-side because of the target-gate
1212
// below
13-
#[allow(unused_variables)]
14-
let render_ctx = perseus::get_render_ctx!(cx);
13+
let render_ctx = RenderCtx::from_ctx(cx);
14+
let global_state = render_ctx.get_global_state::<AppStateRx>(cx);
1515

1616
view! { cx,
1717
p(id = "global_state") { (global_state.test.get()) }

examples/core/idb_freezing/src/templates/index.rs

+5-9
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,23 @@
1-
use perseus::{Html, RenderFnResultWithCause, Template};
1+
use perseus::prelude::*;
22
use sycamore::prelude::*;
33

4-
use crate::global_state::*;
4+
use crate::global_state::AppStateRx;
55

66
#[perseus::make_rx(IndexPropsRx)]
77
pub struct IndexProps {
88
username: String,
99
}
1010

1111
#[perseus::template_rx]
12-
pub fn index_page<'a, G: Html>(
13-
cx: Scope<'a>,
14-
state: IndexPropsRx<'a>,
15-
global_state: AppStateRx<'a>,
16-
) -> View<G> {
12+
pub fn index_page<'a, G: Html>(cx: Scope<'a>, state: IndexPropsRx<'a>) -> View<G> {
1713
// This is not part of our data model
1814
let freeze_status = create_signal(cx, String::new());
1915
let thaw_status = create_signal(cx, String::new());
2016
// It's faster to get this only once and rely on reactivity
2117
// But it's unused when this runs on the server-side because of the target-gate
2218
// below
23-
#[allow(unused_variables)]
24-
let render_ctx = perseus::get_render_ctx!(cx);
19+
let render_ctx = RenderCtx::from_ctx(cx);
20+
let global_state = render_ctx.get_global_state::<AppStateRx>(cx);
2521

2622
view! { cx,
2723
// For demonstration, we'll let the user modify the page's state and the global state arbitrarily

examples/core/router_state/src/templates/index.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
use perseus::{router::RouterLoadState, Html, Template};
2-
use sycamore::prelude::{create_memo, view, Scope, View};
1+
use perseus::prelude::*;
2+
use perseus::router::RouterLoadState;
3+
use sycamore::prelude::*;
34

45
#[perseus::template_rx]
56
pub fn router_state_page<G: Html>(cx: Scope) -> View<G> {
6-
let load_state = perseus::get_render_ctx!(cx).router.get_load_state(cx);
7+
let load_state = RenderCtx::from_ctx(cx).router.get_load_state(cx);
78
// This uses Sycamore's `create_memo` to create a state that will update
89
// whenever the router state changes
910
let load_state_str = create_memo(cx, || match (*load_state.get()).clone() {

examples/demos/auth/src/templates/index.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use crate::global_state::*;
2-
use perseus::{Html, Template};
2+
use perseus::prelude::*;
33
use sycamore::prelude::*;
44

55
#[perseus::template_rx]
6-
fn index_view<'a, G: Html>(cx: Scope<'a>, _: (), AppStateRx { auth }: AppStateRx<'a>) -> View<G> {
6+
fn index_view<'a, G: Html>(cx: Scope<'a>) -> View<G> {
7+
let AppStateRx { auth } = RenderCtx::from_ctx(cx).get_global_state::<AppStateRx>(cx);
8+
79
let AuthDataRx { state, username } = auth;
810
// This isn't part of our data model because it's only used here to pass to the
911
// login function

packages/perseus-macro/src/rx_state.rs

+12-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ pub fn make_rx_impl(mut orig_struct: ItemStruct, name_raw: Ident) -> TokenStream
1111
// fields, we'll just copy the struct and change the parts we want to
1212
// We won't create the final `struct` yet to avoid more operations than
1313
// necessary
14+
// Note that we leave this as whatever visibility the original state was to
15+
// avoid compiler errors (since it will be exposed as a trait-linked type
16+
// through the ref struct)
1417
let mut mid_struct = orig_struct.clone(); // This will use `RcSignal`s, and will be stored in context
1518
let ItemStruct {
1619
ident: orig_name,
@@ -301,12 +304,17 @@ pub fn make_rx_impl(mut orig_struct: ItemStruct, name_raw: Ident) -> TokenStream
301304
::serde_json::to_string(&unrx).unwrap()
302305
}
303306
}
304-
#[derive(::std::clone::Clone)]
305-
#ref_struct
306-
impl #generics #mid_name #generics {
307-
pub fn to_ref_struct(self, cx: ::sycamore::prelude::Scope) -> #ref_name #generics {
307+
// TODO Generics
308+
impl ::perseus::state::MakeRxRef for #mid_name {
309+
type RxRef<'a> = #ref_name<'a>;
310+
fn to_ref_struct<'a>(self, cx: ::sycamore::prelude::Scope<'a>) -> #ref_name<'a> {
308311
#make_ref_fields
309312
}
310313
}
314+
#[derive(::std::clone::Clone)]
315+
#ref_struct
316+
impl<'a> ::perseus::state::RxRef for #ref_name<'a> {
317+
type RxNonRef = #mid_name;
318+
}
311319
}
312320
}

0 commit comments

Comments
 (0)