From 05ec2e77cefdee73064edf1114f2157cbdc4f8c3 Mon Sep 17 00:00:00 2001 From: "Nate Hopkins (hopsoft)" Date: Fri, 14 Jun 2024 11:50:12 -0600 Subject: [PATCH] Update docs for State --- README.md | 151 +++++++++++++++---- test/dummy/app/helpers/application_helper.rb | 2 +- 2 files changed, 119 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index c7fe4e99..bf318f0d 100644 --- a/README.md +++ b/README.md @@ -77,9 +77,9 @@ - [Prevent Controller Action](#prevent-controller-action) - [Broadcasting Turbo Streams](#broadcasting-turbo-streams) - [State](#state) - - [Server Side State](#server-side-state) - - [Client Side State](#client-side-state) - - [Data Binding](#data-binding) + - [Server State](#server-state) + - [Now State](#now-state) + - [Client State](#client-state) - [State Resolution](#state-resolution) - [Page State](#page-state) - [Community](#community) @@ -536,57 +536,142 @@ _Learn more about Turbo Stream broadcasting by reading through the ## State -### Server Side State +TurboBoost Commands manage various forms of state to provide a terrific reactive user experience. -TODO: document... +Here’s a breakdown of each type: -### Client Side State +### Server State -TODO: document... +Server state is the persistent state that the server used for the most recent render. +This state is signed, ensuring data integrity and security. -### Data Binding +The client includes this signed state along with its own optimistic changes whenever a Command is invoked. +The server can then compute the difference between the client-side state and the signed state, +allowing you to accept or reject the client's optimistic changes. -TODO: document... +This ensures the server remains the single source of truth. -### State Resolution +Server state can be accessed within your Commands like so. -TODO: document... +```ruby +state[:key] = "value" +state[:key] +#=> "value" +``` -### Page State +Server state is also accessible your controllers and views. -You can opt-in to remember transient page state when using Rails tag helpers with `turbo_boost[:remember]` to track -element attribute values between requests. +```ruby +# controller +turbo_boost.state[:key] = "value" +turbo_boost.state[:key] #=> "value" +``` ```erb -<%= tag.details id: "page-state-example", open: "open", turbo_boost: { remember: [:open] } do %> - Page State Example - Content... -<% end %> +<% + turbo_boost.state[:key] = "value" + turbo_boost.state[:key] #=> "value" +%> ``` -The code above will be expanded to this HTML. +### Now State -```html -
- Page State Example - Content... -
+Now state is ephemeral server-side state that only exists for the current render cycle. +Similar to `flash.now` in Rails, this state is discarded after rendering. + +It’s useful for managing temporary data that doesn’t need to persist beyond the current request. + +Now state can be accessed within your Commands like so. + +```ruby +state.now[:key] = "value" +state.now[:key] +#=> "value" +``` + +Server state is also accessible your controllers and views. + +```ruby +# controller +turbo_boost.state.now[:key] = "value" +turbo_boost.state.now[:key] #=> "value" +``` + +```erb +<% + turbo_boost.state.now[:key] = "value" + turbo_boost.state.now[:key] #=> "value" +%> ``` -If the user closes the details element and invokes a command or performs a request, -the server will pre-render the markup with the current page state preserving the `open` attribute value. -_The client also ensures that remembered attributes are restored after DOM mutations._ +### Client State -Several things happen when you use `turbo_boost[:remember]` to track page state. +Client state is a mutable version of the signed server state, wrapped in an observable JavaScript proxy. +This allows for sophisticated techniques like data binding with custom JavaScript, Stimulus controllers, and web components. + +Client-side state enables immediate UI updates, providing a fast and smooth user experience while the server resolves state differences whenever commands are invoked. + +Client state can be accessed on the client like so. + +```js +TurboBoost.State.current['key'] = 'value' +TurboBoost.State.current['key'] //=> 'value' +``` + +### State Resolution -1. The client builds the current page state before emitting requests to the server. -1. The server uses the page state when rendering the response. -1. The client client verifies the page state and restores attribute values _(if necessary)_ after the DOM updates. +Commands can perform state resolution by implementing the `resolve_state` method. + +The Command has access to all forms of state, so you should use explicit access during resolution. + +You can access both the signed state and the optimistc client state from within the Command like so. + +```ruby +class ExampleCommand < TurboBoost::Commands::Command + + def resolve_state + state.signed #=> the server state used to perform the last render + state.unsigned #=> the client optimistc state + # compare and resolve the delta + end +end +``` + +> [!TIP] +> State resolution can involve data lookups, updates to persistent data stores, interactions with third-party APIs, etc. + +You can opt-in to state resolution with the following config option. + +```ruby +# config/initializers/turbo_boost.rb +TurboBoost::Commands.config.tap do |config| + config.resolve_state = true +end +``` + +### Page State + +Page State is managed by the client and used to remember element attribute values between server renders. +It’s best used to track transient user interactions, like which elements are open or closed, visible, or their positions. + +This enhances the user experience by maintaining the state of UI elements between renders. +When invoking commands, the client sends the page state to the server, allowing it to preserve element attributes during rendering. +_The client also checks and restores page state whenever the DOM changes if needed._ + +You can opt-in to remember transient page state with Rails tag helpers with the `turbo_boost[:remember]` option. + +```erb +<%= tag.details id: "page-state-example", open: "open", turbo_boost: { remember: [:open] } do %> + Page State Example + Content... +<% end %> +``` -This feature works with all attributes, including aria, data, and custom attributes. +__That's it!__ You're done. > [!NOTE] -> Elements must have a unique `id` assigned to participate in page state tracking. +> This feature works with all attributes, including `aria`, `data`, and custom attributes, +> but elements must have a unique `id` to participate in page state tracking. ## Community diff --git a/test/dummy/app/helpers/application_helper.rb b/test/dummy/app/helpers/application_helper.rb index 2a74b3da..72ac8807 100644 --- a/test/dummy/app/helpers/application_helper.rb +++ b/test/dummy/app/helpers/application_helper.rb @@ -36,6 +36,6 @@ def element_id(*args) end def attribute(id, name) - turbo_boost.command_state[id][name] if turbo_boost.command_state[id] + turbo_boost.state[id][name] if turbo_boost.state[id] end end