Skip to content

Commit

Permalink
feat: improved global state system (#223)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
arctic-hen7 authored Nov 8, 2022
1 parent 825e990 commit 85d7f4a
Show file tree
Hide file tree
Showing 23 changed files with 402 additions and 295 deletions.
10 changes: 6 additions & 4 deletions examples/core/freezing_and_thawing/src/templates/about.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
use perseus::prelude::*;
use perseus::state::Freeze;
use perseus::{Html, Template};
use sycamore::prelude::*;

use crate::global_state::*;
use crate::global_state::AppStateRx;

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

let global_state = render_ctx.get_global_state::<AppStateRx>(cx);

view! { cx,
p(id = "global_state") { (global_state.test.get()) }
Expand Down
14 changes: 6 additions & 8 deletions examples/core/freezing_and_thawing/src/templates/index.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
use perseus::prelude::*;
use perseus::state::Freeze;
use perseus::{Html, RenderFnResultWithCause, Template};
use sycamore::prelude::*;

use crate::global_state::*;
use crate::global_state::AppStateRx;

#[perseus::make_rx(IndexPropsRx)]
pub struct IndexProps {
username: String,
}

#[perseus::template_rx]
pub fn index_page<'a, G: Html>(
cx: Scope<'a>,
state: IndexPropsRx<'a>,
global_state: AppStateRx<'a>,
) -> View<G> {
pub fn index_page<'a, G: Html>(cx: Scope<'a>, state: IndexPropsRx<'a>) -> View<G> {
// This is not part of our data model, we do NOT want the frozen app
// synchronized as part of our page's state, it should be separate
let frozen_app = create_signal(cx, String::new());
let render_ctx = perseus::get_render_ctx!(cx);
let render_ctx = RenderCtx::from_ctx(cx);

let global_state = render_ctx.get_global_state::<AppStateRx>(cx);

view! { cx,
// For demonstration, we'll let the user modify the page's state and the global state arbitrarily
Expand Down
12 changes: 6 additions & 6 deletions examples/core/global_state/src/templates/about.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use perseus::{Html, Template};
use sycamore::prelude::{view, Scope, SsrNode, View};
use perseus::prelude::*;
use sycamore::prelude::*;

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

// This template needs global state, but doesn't have any state of its own, so
// the first argument is the unit type `()` (which the macro will detect)
#[perseus::template_rx]
pub fn about_page<'a, G: Html>(cx: Scope<'a>, _: (), global_state: AppStateRx<'a>) -> View<G> {
pub fn about_page<'a, G: Html>(cx: Scope<'a>) -> View<G> {
let global_state = RenderCtx::from_ctx(cx).get_global_state::<AppStateRx>(cx);

view! { cx,
// The user can change the global state through an input, and the changes they make will be reflected throughout the app
p { (global_state.test.get()) }
Expand Down
17 changes: 10 additions & 7 deletions examples/core/global_state/src/templates/index.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
use perseus::{Html, Template};
use sycamore::prelude::{view, Scope, SsrNode, View};
use perseus::prelude::*;
use sycamore::prelude::*;

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

// This template needs global state, but doesn't have any state of its own, so
// the first argument is the unit type `()` (which the macro will detect)
// Note that this template takes no state of its own in this example, but it
// certainly could
#[perseus::template_rx]
pub fn index_page<'a, G: Html>(cx: Scope<'a>, _: (), global_state: AppStateRx<'a>) -> View<G> {
pub fn index_page<'a, G: Html>(cx: Scope<'a>) -> View<G> {
// We access the global state through the render context, extracted from
// Sycamore's context system
let global_state = RenderCtx::from_ctx(cx).get_global_state::<AppStateRx>(cx);

view! { cx,
// The user can change the global state through an input, and the changes they make will be reflected throughout the app
p { (global_state.test.get()) }
Expand Down
10 changes: 5 additions & 5 deletions examples/core/idb_freezing/src/templates/about.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
use perseus::{Html, Template};
use perseus::prelude::*;
use sycamore::prelude::*;

use crate::global_state::*;
use crate::global_state::AppStateRx;

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

view! { cx,
p(id = "global_state") { (global_state.test.get()) }
Expand Down
14 changes: 5 additions & 9 deletions examples/core/idb_freezing/src/templates/index.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
use perseus::{Html, RenderFnResultWithCause, Template};
use perseus::prelude::*;
use sycamore::prelude::*;

use crate::global_state::*;
use crate::global_state::AppStateRx;

#[perseus::make_rx(IndexPropsRx)]
pub struct IndexProps {
username: String,
}

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

view! { cx,
// For demonstration, we'll let the user modify the page's state and the global state arbitrarily
Expand Down
7 changes: 4 additions & 3 deletions examples/core/router_state/src/templates/index.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use perseus::{router::RouterLoadState, Html, Template};
use sycamore::prelude::{create_memo, view, Scope, View};
use perseus::prelude::*;
use perseus::router::RouterLoadState;
use sycamore::prelude::*;

#[perseus::template_rx]
pub fn router_state_page<G: Html>(cx: Scope) -> View<G> {
let load_state = perseus::get_render_ctx!(cx).router.get_load_state(cx);
let load_state = RenderCtx::from_ctx(cx).router.get_load_state(cx);
// This uses Sycamore's `create_memo` to create a state that will update
// whenever the router state changes
let load_state_str = create_memo(cx, || match (*load_state.get()).clone() {
Expand Down
6 changes: 4 additions & 2 deletions examples/demos/auth/src/templates/index.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use crate::global_state::*;
use perseus::{Html, Template};
use perseus::prelude::*;
use sycamore::prelude::*;

#[perseus::template_rx]
fn index_view<'a, G: Html>(cx: Scope<'a>, _: (), AppStateRx { auth }: AppStateRx<'a>) -> View<G> {
fn index_view<'a, G: Html>(cx: Scope<'a>) -> View<G> {
let AppStateRx { auth } = RenderCtx::from_ctx(cx).get_global_state::<AppStateRx>(cx);

let AuthDataRx { state, username } = auth;
// This isn't part of our data model because it's only used here to pass to the
// login function
Expand Down
16 changes: 12 additions & 4 deletions packages/perseus-macro/src/rx_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ pub fn make_rx_impl(mut orig_struct: ItemStruct, name_raw: Ident) -> TokenStream
// fields, we'll just copy the struct and change the parts we want to
// We won't create the final `struct` yet to avoid more operations than
// necessary
// Note that we leave this as whatever visibility the original state was to
// avoid compiler errors (since it will be exposed as a trait-linked type
// through the ref struct)
let mut mid_struct = orig_struct.clone(); // This will use `RcSignal`s, and will be stored in context
let ItemStruct {
ident: orig_name,
Expand Down Expand Up @@ -301,12 +304,17 @@ pub fn make_rx_impl(mut orig_struct: ItemStruct, name_raw: Ident) -> TokenStream
::serde_json::to_string(&unrx).unwrap()
}
}
#[derive(::std::clone::Clone)]
#ref_struct
impl #generics #mid_name #generics {
pub fn to_ref_struct(self, cx: ::sycamore::prelude::Scope) -> #ref_name #generics {
// TODO Generics
impl ::perseus::state::MakeRxRef for #mid_name {
type RxRef<'a> = #ref_name<'a>;
fn to_ref_struct<'a>(self, cx: ::sycamore::prelude::Scope<'a>) -> #ref_name<'a> {
#make_ref_fields
}
}
#[derive(::std::clone::Clone)]
#ref_struct
impl<'a> ::perseus::state::RxRef for #ref_name<'a> {
type RxNonRef = #mid_name;
}
}
}
Loading

0 comments on commit 85d7f4a

Please sign in to comment.