Migrating from State Pages
Move one page at a time: build the web page at a new URL next to the old one, compare the two, then move the URL over. State pages and UI components keep running, so nothing has to happen at once. The tables below show which web block replaces which, and what has no direct replacement.
What actually changes
| State page | Web page | |
|---|---|---|
| What the browser gets | A React application that builds the page | Finished HTML and a script under 4 KB compressed |
| The markup comes from | A tree of blocks, one per element | A template, usually one block |
| Interaction runs over | A WebSocket, with state held between requests | A plain POST. The server holds nothing between requests |
| Typing into a form | Goes through state | Stays in the browser |
| An answer holds | State updates the page reacts to | Pieces of HTML to put in place |
Which web block replaces which
Pages and rendering
| State or UI component block | Becomes |
|---|---|
state_page, uicomponent_page | web_page. The same event, scopes and l10n, and the same connection, params and query pins. |
state_render_page, uicomponent_page_render | web_render, with a template where the content pin was. |
state_div, state_h1, state_button and every other element block | The plain tag in the template: <div>, <h1>, <button>. |
state_html | Plain markup in the template, or {{{value}}} for HTML a flow built. |
| State templates in Manage Templates | A stored web template used as a layout, with {{{body}}} and {{{head}}}. |
uicomponent_update_meta_tags | The title, description, image and meta pins. See SEO and Sharing. |
Lists and tables
| State or UI component block | Becomes |
|---|---|
state_foreach_div, state_foreach_ul, state_foreach_ol, state_foreach_tr, state_foreach_tbody, state_foreach_thead, uicomponent_foreach | web_foreach when an action changes the rows later, or {{for item in items}} in the template when they only show. See Lists and Tables. |
state_datatable, state_advanced_datatable | web_foreach into a <table>, plus an action for each thing the table does. See below. |
state_set_datatable_refresh, state_datatable_push | web_replace and web_insert of just that row. |
Events and state
| State or UI component block | Becomes |
|---|---|
stateevents_onclick, stateevents_onchange, stateevents_onenter | data-action="name" on the element, and a web_action with that name. A button or link fires on click, a field on change, and a form on submit, Enter included. |
state_on_state_change | A field with data-action, adding data-trigger="input" to run while typing. |
state_trigger_custom_function and its state_inline_function_execute | A web_action. A shared piece of logic becomes an ordinary function that the action calls. |
state_backend_request | Nothing: the action is already on the server. To fill a part of the page after it loads, use data-trigger="load". |
state_update_state, state_update_state_fields, state_return_state, state_increment_state, global state | A value on the server, usually in a storage, and a web_update or web_replace of the place that shows it. Read the section below. |
state_is_true, state_is_empty, state_branch | {{if}} in the template, or a branch in the flow when the server decides. |
Forms, navigation and the rest
| State or UI component block | Becomes |
|---|---|
state_form, state_input, state_submit_button | A plain <form data-action="name"> with plain inputs. See Forms and Validation. |
state_form_get | The form out-pin of web_action. |
| Field validation shown in the form | required, pattern and the like in the markup, and web_errors for what only the server knows. |
state_flow_modal, state_return_modal, state_close_flow_modal, uicomponent_modal | Markup that is on the page already, opened with data-toggle="#dialog" with no request. For content the server must build, either a page of its own, or an action that answers web_replace of a hidden placeholder with the visible dialog. |
state_translate_key, state_translate_dynamic | {{t "key"}} and {{t key}}. Your translation keys stay as they are. See Languages. |
state_redirect, state_redirect_to_page, uicomponent_go_to_page | web_redirect, with a path you write out. |
state_refresh_page | A web_redirect to the page's own path, or a web_render in the action. |
state_script, state_custom_function_execution | Your own <script nonce="{{nonce}}">, or a file the layout links. |
state_set_cookie | A Set-Cookie in the headers pin of web_render, or a line in your script. Reading stays httpconnection_get_cookie, which reads the ui- prefix for you. |
state_input_file, state_get_uploaded_file | The upload endpoints, with the result passed to the action. A web action refuses files. |
State becomes a value on the server and a piece of HTML
This is the one change in thinking. On a state page the browser holds state and the page reacts to it. On a web page the page shows what the server rendered, and an action changes something on the server and sends back what now looks different.
State page
A click updates cart_count in global state, and every component that reads it renders again.
Web page
A click runs an action that writes the cart to a storage and answers web_update(target "#cart-count", html: count). If two places show the count, the action answers two updates, or the places share one element.
Purely visual state needs no server at all: data-toggle for show and hide, and a few lines of your own script for the rest.
What has no one-to-one replacement
| On a state page | On a web page |
|---|---|
Live updates pushed over a WebSocket, such as state_broadcast_subscribe | A web page has no WebSocket of its own. Poll with data-trigger="every 10s", which pauses while the page is not visible. For true push, open a socket in your own script and call RUALWeb.apply(ops). A chat or a live dashboard may be better off staying a state page for now. |
| The built-in datatable: sorting, filtering, paging and selection out of the box | You write what you need: a search with a sort from query or a data-param-*, links or an action for paging, and checkboxes in a form for selection. It is more work up front, and every part is plain HTML you control. A heavy admin table is a fair thing to migrate last. |
| Client state shared between components | Rendered by the server, or held in your own script. A page that behaves like a spreadsheet or an editor is mostly client state, and suits this model least. |
| Custom React components | Your own script with a nonce, mounted on an element in the template. There is no hydration step. You bring and load the library yourself, from your own files or over https:. |
| Modals as flows of their own, opened by name from anywhere | No registry of modals. A dialog is markup on the page, a page, or the answer of an action in this blueprint. |
| Actions across blueprints | An action belongs to its page's blueprint. Put shared logic in a function and call it from an action in each. |
A safe order to move one page in
Write down what the page does
List what it reads, what each click and form does, who may open it, and what it shows search engines. This list is your test later. Start with a page that mostly shows things, and leave the most interactive ones for last.
Build the web page at a new URL
Make a new blueprint with a
web_pageat a URL nobody uses yet, such asnew/pricing, with the same scopes as the old page. Set both in the code view, as in Your First Page, since the canvas has no URL button on this block yet. Keep this URL out of search with themetapin:{"robots": "noindex"}. The old page is untouched, so nothing can break yet.Move what it shows first
Turn the shell into a stored layout and the elements into a template, and call the same functions and storages. Compare the rendered result with the old page, in each language and both signed in and signed out.
Then its actions, one at a time
One
web_actionper intent. Test each with real data in a test document. If the new blueprint has pages with different scopes, give each action its own.Compare, side by side
Go through your list on both URLs. Check
view sourcefor the title, description and canonical, and test a form with bad input and with JavaScript off if that matters for this page.Move the URL over
In one sitting: give the old page another URL or deactivate its blueprint, set the web page's
eventandl10nto the old URLs, remove thenoindex, save and activate. Do not leave two active pages on one URL, because which one answers is not something to rely on. The address stays the same, so links and search results keep working.Watch, and keep the way back open
Keep the old blueprint for a while. To go back, swap the URLs again and save: saving is what publishes. Remove the old blueprint only when you are sure.
Next steps
Your First Page Get a feel for the blocks on a small page first. Tutorial: A Small Webshop Lists, actions and forms working together. Actions Where your state handlers end up.Frequently asked
Are RUAL state pages being removed?
They are deprecated, not removed: existing state pages and UI components keep running. New work should use the web blocks, and the migration guide gives a safe order to move a page across.
What replaces state_page and state_render_page?
web_page and web_render. Where a state page keeps state in the browser and re-renders it, a web page renders on the server and answers an action with only the parts that changed: web_foreach for lists, web_action for buttons and forms, and web_replace, web_remove, web_insert and web_update for the updates.