You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/next/en-US/reference/state/freezing.md
+3-5
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
If you use the reactive and global state systems to their full potential, your entire app can be represented as its state. So what if you could make all that state unreactive again, serialize it to a string, and keep it for later? Well, you'd be able to let your users pick up at the *exact* same place they were when they come back later. Imagine you're in the middle of filling out some forms and then your computer crashes. You boot back up and go to the website you were on. If it's built with Perseus and state freezing occurred just before the crash, you're right back to where you were. Same page, same inputs, same everything.
4
4
5
-
Specifically, Perseus achieves this by serializing the global state and the page state store, along with the route that the user's currently on. You can invoke this easily by running `.freeze()` on the render context, which you can access with `perseus::get_render_ctx!()`. Best of all, if state hasn't been used yet (e.g. a page hasn't been visited), it won't be cached, because it doesn't need to be. That also applies to global state, meaning the size of your frozen output is minimized.
5
+
Specifically, Perseus achieves this by serializing the global state and the page state store, along with the route that the user's currently on. You can invoke this easily by running `.freeze()` on the render context, which you can access with `perseus::get_render_ctx!()`. Best of all, if state hasn't been used yet (e.g. a page hasn't been visited), it won't be cached, because it doesn't need to be. That also applies to global state, meaning the size of your frozen output is minimized (note that this isn't property-level granular yet, but that *might* be investigated in future).
6
6
7
7
## Example
8
8
@@ -14,7 +14,7 @@ You can easily imperatively instruct your app to freeze itself like so (see [her
14
14
15
15
## Thawing
16
16
17
-
Recovering your app's state from a frozen state is called *thawing* in Perseus (basically like hydration for state, but remember that hydration is for views and thawing is for state!), and it can occur gradually and automatically once you provide Perseus a frozen state to use, which you can do by calling `.thaw()` on the render context (which you can get with `perseus::get_render_ctx!()`). How you store and retrieve frozen state is entirely up to you. For example, you could store the user's last state in a database and then fetch that when the user logs in, or you could store it in IndexedDB and have even logging in be covered by it (if authentication tokens are part of your global state).
17
+
Recovering your app's state from a frozen state is called *thawing* in Perseus (basically like hydration for state, but remember that hydration is for views and thawing is for state!), and it can occur gradually and automatically once you provide Perseus a frozen state to use, which you can do by calling `.thaw()` on the render context (which you can get with `perseus::get_render_ctx!()`). How you store and retrieve frozen state is entirely up to you. For example, you could store the user's last state in a database and then fetch that when the user logs in, or you could store it in IndexedDB and have even logging in be covered by it (if authentication tokens are part of your global state). Note that thawing will also return the user to the page they were on when the state was thawed.
18
18
19
19
One important thing to understand about thawing though is how Perseus decided what state to use for a template, because there can be up to three options. Every template that accepts state will have generated state that's provided to it from the generation proceses on the server, but there could also be a frozen state and an active state (some state that's already been made reactive). The server-generated state is always the lowest priority, and it will be used if no active or frozen state is available. However, deciding between frozen and active state is more complicated. If only one is available, it will of course be used, but it both are available, the choice is yours. You can represent this choice through the `ThawPrefs` `struct`, which must be provided to a call to `.thaw()` as the second argument. This has two fields, one for page state, and another for global state. For global state, you can set the `global_prefers_frozen` field to `true` if you want to override active global state with a frozen one. For page state, you'll use `PageThawPrefs`, which can be set to `IncludeAll` (all pages will prefer frozen state), `Include(Vec<String>)` (the listed pages will prefer frozen state, all others will prefer active state), or `Exclude(Vec<String>)` (the listed pages will prefer active state, all others will prefer frozen state). There's no `ExcludeAll` option because that would defeat the entire purpose of thawing.
20
20
@@ -32,8 +32,6 @@ In the first case, the reasoning is simple. Statw thawing is a gradual process,
32
32
33
33
In the second case, the reason is similar. When you get the global state directly in this way, you bypass the thawing process altogether, meaning thawed state won't show up. If you need to access the global state, you should do it by making it the second argument to your template function (as documented [here](:reference/state/global)).
34
34
35
-
*Note: in a future version of Perseus, thawing logic may be moved so that direct access does become possible, but it's currently not.*
35
+
*Note: in a future version of Perseus, thawing logic may be altered so that direct access does become possible, but it's currently not.*
36
36
37
37
</details>
38
-
39
-
*Note: currently, Perseus' thawing process requires navigating to another page to work due to problems in Sycamore's router. These will be resolved in some form before v0.3.3.*
Copy file name to clipboardExpand all lines: docs/next/en-US/reference/state/rx.md
+9-5
Original file line number
Diff line number
Diff line change
@@ -1,14 +1,18 @@
1
1
# Reactive State
2
2
3
-
Since v0.3.3, Perseus added support for *reactive state*. Up until now, all our templates have generated state to create one or more pages, and then they've simply used that state to render some stuff. However, in reality, we'll have much more complex data models that involve user interaction. For example, we might have inputs on a page that might change aspects of the view displayed to the user. This used to be done with Sycamore's reactivity system on its own, but Perseus now provides a mechanism to make the state you provide to templates automatically reactive. That means every single property becomes reactive.
3
+
In v0.3.3., Perseus added support for *reactive state*, which we talked about a bit in the tutorials at the beginning of the documentation. If you've come from a Perseus version before v0.3.3, this system will be quite new to you, as it adds a whole new platform on which templates can interact with their state. Originally, you could generate state, and then it would be done, and the template would receive it as is. Now, that state can be made *reactive* by wrapping all its fields inside `Signal`s, and it will then be added to a global store of page state. The platform this is built on allows a whole new level of state mechanics in Perseus, including [global state](:reference/state/global) and even [hot state reloading](:reference/state/hsr) (a world first to our knowledge)!
4
4
5
-
Just annotate a state `struct` with `#[perseus::make_rx(RxName)]`, where `RxName` is the name of the new reactive `struct` (e.g. `IndexState` might become `IndexStateRx`). This macro wraps every single property in your `struct` in a `Signal` and produces a new reactive version that way, implementing `perseus::state::MakeRx` on the original to provide a method `.make_rx()` that can be used to convert from the unreactive version to the reactive one (there's also the reverse through `perseus::state::MakeUnrx`, which is implemented on the new, reactive version). If you have fields on your `struct` that are themselves `struct`s, you'll need to nest that reactivity, which you can do by adding `#[rx::nested("field", FieldRxName)]` just underneath the `#[make_rx(...)]` macro, providing it the name of the field and the type of the reactive version (which you'd generated with `#[make_rx(...)]`). Notably, `#[make_rx(...)]` automatically derives `Serialize`, `Deserialize`, and `Clone` on your `struct` (so don't derive them yourself!).
5
+
In essence, Perseus now provides a way to make your state automatically reactive, which enables some *really* cool new features!
6
+
7
+
To use this new platform, just annotate a state `struct` with `#[perseus::make_rx(RxName)]`, where `RxName` is the name of the new reactive `struct` (e.g. `IndexState` might become `IndexStateRx`). This macro wraps every single property in your `struct` in a `Signal` and produces a new reactive version that way, implementing `perseus::state::MakeRx` on the original to provide a method `.make_rx()` that can be used to convert from the unreactive version to the reactive one (there's also the reverse through `perseus::state::MakeUnrx`, which is implemented on the new, reactive version). If you have fields on your `struct` that are themselves `struct`s, you'll need to nest that reactivity, which you can do by adding `#[rx::nested("field", FieldRxName)]` just underneath the `#[make_rx(...)]` macro, providing it the name of the field and the type of the reactive version (which you'd generated with `#[make_rx(...)]`). Notably, `#[make_rx(...)]` automatically derives `Serialize`, `Deserialize`, and `Clone` on your `struct` (so don't derive them yourself!).
6
8
7
9
*Note: Sycamore has a proposal to support fine-grained reactivity like this through observers, which will supersede this when they're released, and they'll make all this even faster! Right now, everything has to be cloned unfortunately.*
8
10
9
-
Once you've got some reactive versions of your state `struct`s ready, you should generate the unreactive versions as usual, but then set the first argument on your template function to the reactive version. This requires Perseus to convert between the unreactive and reactive versions in the background, which you can enable by changing `#[template(...)]` to `#[template_rx(...)]` and removing the Sycamore `#[component]` annotation (this is added automatically by `#[template_rx(...)]`). Behind the scenes, you've just enabled the world's most powerful state platform, and not only will your state be made reactive for you, it will be added to the *page state store*, a global store that enables Perseus to cache the state of a page. So, if your users start filling out forms on page 1 and then go to page 2, and then come back to page 1, their forms will be just how they left them. (Not sure about you, but it feels to us like it's about time this was the default on the web!)
11
+
Once you've got some reactive versions of your state `struct`s ready, you should generate the unreactive versions as usual in functions like `get_build_state()`, but then set the first argument on your template function to the reactive version (e.g. `IndexStateRx` rather than `IndexState`). This requires Perseus to convert between the unreactive and reactive versions in the background, which you can enable by changing the old `#[template(...)]` (used in the old documentation/tutorials) to `#[template_rx(...)]` and removing the Sycamore `#[component]` annotation (this is added automatically by `#[template_rx(...)]`). Behind the scenes, you've just enabled the world's most powerful state platform, and not only will your state be made reactive for you, it will be added to the *page state store*, a global store that enables Perseus to cache the state of a page. So, if your users start filling out forms on page 1 and then go to page 2, and then come back to page 1, their forms will be just how they left them. (Not sure about you, but it feels to us like it's about time this was the default on the web!)
12
+
13
+
*Side note: if you think this behavior is horrific, you can still use the old `#[template(...)] macro, and we have no plans to deprecate it. Perseus' original unreactive state system worked very well, and there are still plenty of use cases where you may not want all this newfangled reactive state nonsense (like completely static blogs).*
10
14
11
-
You may be wondering what the benefits of having a reactive state are though. Well, the intention is this: every possible state your page can be in should be representable in your state. That means that, whenever you'd usually declare a new variable in a `Signal` to handle some state, you can move it into your template's state and handle it there instead, making things cleaner and taking advantage of Perseus' state caching system.
15
+
You may be wondering what the benefits of having a reactive state are. Well, the intention is this: every possible state your page can be in should be representable in your state. That means that, whenever you'd usually declare a new variable in a `Signal` to handle some state, you can move it into your template's state and handle it there instead, making things cleaner and taking advantage of Perseus' state caching system. If your entire app doesn't use any of this though, you can still trivially use the old state platform if you want to.
12
16
13
17
## Example
14
18
@@ -18,7 +22,7 @@ This can all be a bit hard to imagine, so here's how it looks in practice with a
The only unergonomic thing here is that we have to `.clone()` the `username` so that we can both `bind:value` to it and display it. Note that this will be made unnecessary with Sycamore's new reactive primitives (which will be released soon).
25
+
The only particularly unergonomic thing here is that we have to `.clone()` the `username` so that we can both `bind:value` to it and display it. Note that this will be made unnecessary with Sycamore's new reactive primitives (which will be released soon).
This example illustrates a very simple amalgamation, taking the states of both strategies to produce a new state that combines the two. Note that this also uses `RenderFnWithCause` as a return type (see the section on the [_build state_](:reference/strategies/build-state) strategy for more information). It will be passed an instance of `States`, which you can learn more about in the [API docs](https://docs.rs/perseus). As usual, serialization of your returned state is done with the `#[perseus::autoserde(amalgamate_states)]` macro, though the components of `States` will **not** be deserialized, and you'll have to do that manually.
13
+
This example illustrates a very simple amalgamation, taking the states of both strategies to produce a new state that combines the two. Note that this also uses `RenderFnWithCause` as a return type (see the section on the [_build state_](:reference/strategies/build-state) strategy for more information). It will be passed an instance of `States`, which you can learn more about in the [API docs](https://docs.rs/perseus). As usual, serialization of your returned state is done with the `#[perseus::autoserde(amalgamate_states)]` macro, though the components of `States` will **not** be deserialized, and you'll have to do that manually. Note that the next major version of Perseus will deserialize the components of `States` automatically.
Copy file name to clipboardExpand all lines: docs/next/en-US/reference/strategies/build-paths.md
+1-1
Original file line number
Diff line number
Diff line change
@@ -16,4 +16,4 @@ Here's the same example as given in the previous section (taken from [here](http
16
16
17
17
Note the return type of the `get_build_paths` function, which returns a `RenderFnResult<Vec<String>>`, which is just an alias for `Result<T, Box<dyn std::error::Error>>`, which means that you can return any error you want. If you need to explicitly `return Err(..)`, then you should use `.into()` to perform the conversion from your error type to this type automatically. Perseus will then format your errors nicely for you using [`fmterr`](https://github.com/arctic-hen7/fmterr).
18
18
19
-
Also note how this page renders the page `/docs` by specifying an empty string as one of the paths exported from `get_build_paths`.
19
+
Also note how this page renders the page `/build_paths` by specifying an empty string as one of the paths exported from `get_build_paths`. It's a very common structure to have something like `/blog` and then `/blog/<post-name>` as a website structure, with `/blog` being a list of all posts or the like. However, Perseus would require this to all be done in the same template (since it's all under the same URL), so you'd need to use an `enum` as your state that would then render an alternative view for the root page. Alternatively, you could use a `/post/<post-name>` and `/posts` structure, which would let you use two different templates.
Copy file name to clipboardExpand all lines: docs/next/en-US/reference/strategies/build-state.md
+1-1
Original file line number
Diff line number
Diff line change
@@ -20,7 +20,7 @@ Note that Perseus passes around properties to pages as `String`s, so the functio
20
20
21
21
### With _Build Paths_ or _Incremental Generation_
22
22
23
-
You may have noticed in the above example that the build state function takes a `path` parameter. This becomes useful once you bring the _build paths_ or _incremental generation_ strategies into play, which allow you to render many paths for a single template. In the following example (taken from [here](https://github.com/arctic-hen7/perseus/blob/main/examples/showcase/src/templates/post.rs)), all three strategies are used together to pre-render some blog posts at build-time, and allow the rest to be requested and rendered if they exist (here, any post will exist except one called `tests`):
23
+
You may have noticed in the above example that the build state function takes a `path` parameter. This becomes useful once you bring the _build paths_ or _incremental generation_ strategies into play, which allow you to render many paths for a single template. In the following example (taken from [here](https://github.com/arctic-hen7/perseus/blob/main/examples/core/state_generation/src/templates/incremental_generation.rs)), all three strategies are used together to pre-render some blog posts at build-time, and allow the rest to be requested and rendered if they exist (here, any post will exist except one called `tests`):
0 commit comments