From d5d091793fc6319f720029e9fa2fd1641f4e3d7e Mon Sep 17 00:00:00 2001
From: Greg Johnston
Date: Thu, 30 Nov 2023 19:55:32 -0500
Subject: [PATCH] initial commit
---
.github/workflows/publish-book.yml | 36 ++
.gitignore | 1 +
README.md | 15 +
book.toml | 10 +
mdbook-admonish.css | 345 +++++++++++++++
src/01_introduction.md | 22 +
src/02_getting_started.md | 97 ++++
src/15_global_state.md | 402 +++++++++++++++++
src/SUMMARY.md | 52 +++
src/appendix_dx.md | 62 +++
src/appendix_reactive_graph.md | 243 ++++++++++
src/async/10_resources.md | 136 ++++++
src/async/11_suspense.md | 156 +++++++
src/async/12_transition.md | 83 ++++
src/async/13_actions.md | 176 ++++++++
src/async/README.md | 9 +
src/deployment/README.md | 74 ++++
src/deployment/binary_size.md | 72 +++
src/interlude_projecting_children.md | 175 ++++++++
src/interlude_styling.md | 112 +++++
src/islands.md | 489 +++++++++++++++++++++
src/metadata.md | 49 +++
src/progressive_enhancement/README.md | 36 ++
src/progressive_enhancement/action_form.md | 100 +++++
src/reactivity/14_create_effect.md | 329 ++++++++++++++
src/reactivity/README.md | 5 +
src/reactivity/interlude_functions.md | 76 ++++
src/reactivity/working_with_signals.md | 140 ++++++
src/router/16_routes.md | 144 ++++++
src/router/17_nested_routing.md | 324 ++++++++++++++
src/router/18_params_and_queries.md | 202 +++++++++
src/router/19_a.md | 155 +++++++
src/router/20_form.md | 180 ++++++++
src/router/README.md | 23 +
src/server/25_server_functions.md | 150 +++++++
src/server/26_extractors.md | 87 ++++
src/server/27_response.md | 73 +++
src/server/README.md | 11 +
src/ssr/21_cargo_leptos.md | 37 ++
src/ssr/22_life_cycle.md | 43 ++
src/ssr/23_ssr_modes.md | 156 +++++++
src/ssr/24_hydration_bugs.md | 161 +++++++
src/ssr/README.md | 21 +
src/testing.md | 219 +++++++++
src/view/01_basic_component.md | 225 ++++++++++
src/view/02_dynamic_attributes.md | 262 +++++++++++
src/view/03_components.md | 481 ++++++++++++++++++++
src/view/04_iteration.md | 265 +++++++++++
src/view/04b_iteration.md | 278 ++++++++++++
src/view/05_forms.md | 250 +++++++++++
src/view/06_control_flow.md | 384 ++++++++++++++++
src/view/07_errors.md | 175 ++++++++
src/view/08_parent_child.md | 463 +++++++++++++++++++
src/view/09_component_children.md | 229 ++++++++++
src/view/README.md | 5 +
src/view/builder.md | 98 +++++
video/async.mov | Bin 0 -> 210450 bytes
video/in-order.mov | Bin 0 -> 269735 bytes
video/out-of-order.mov | Bin 0 -> 267043 bytes
59 files changed, 8603 insertions(+)
create mode 100644 .github/workflows/publish-book.yml
create mode 100644 .gitignore
create mode 100644 README.md
create mode 100644 book.toml
create mode 100644 mdbook-admonish.css
create mode 100644 src/01_introduction.md
create mode 100644 src/02_getting_started.md
create mode 100644 src/15_global_state.md
create mode 100644 src/SUMMARY.md
create mode 100644 src/appendix_dx.md
create mode 100644 src/appendix_reactive_graph.md
create mode 100644 src/async/10_resources.md
create mode 100644 src/async/11_suspense.md
create mode 100644 src/async/12_transition.md
create mode 100644 src/async/13_actions.md
create mode 100644 src/async/README.md
create mode 100644 src/deployment/README.md
create mode 100644 src/deployment/binary_size.md
create mode 100644 src/interlude_projecting_children.md
create mode 100644 src/interlude_styling.md
create mode 100644 src/islands.md
create mode 100644 src/metadata.md
create mode 100644 src/progressive_enhancement/README.md
create mode 100644 src/progressive_enhancement/action_form.md
create mode 100644 src/reactivity/14_create_effect.md
create mode 100644 src/reactivity/README.md
create mode 100644 src/reactivity/interlude_functions.md
create mode 100644 src/reactivity/working_with_signals.md
create mode 100644 src/router/16_routes.md
create mode 100644 src/router/17_nested_routing.md
create mode 100644 src/router/18_params_and_queries.md
create mode 100644 src/router/19_a.md
create mode 100644 src/router/20_form.md
create mode 100644 src/router/README.md
create mode 100644 src/server/25_server_functions.md
create mode 100644 src/server/26_extractors.md
create mode 100644 src/server/27_response.md
create mode 100644 src/server/README.md
create mode 100644 src/ssr/21_cargo_leptos.md
create mode 100644 src/ssr/22_life_cycle.md
create mode 100644 src/ssr/23_ssr_modes.md
create mode 100644 src/ssr/24_hydration_bugs.md
create mode 100644 src/ssr/README.md
create mode 100644 src/testing.md
create mode 100644 src/view/01_basic_component.md
create mode 100644 src/view/02_dynamic_attributes.md
create mode 100644 src/view/03_components.md
create mode 100644 src/view/04_iteration.md
create mode 100644 src/view/04b_iteration.md
create mode 100644 src/view/05_forms.md
create mode 100644 src/view/06_control_flow.md
create mode 100644 src/view/07_errors.md
create mode 100644 src/view/08_parent_child.md
create mode 100644 src/view/09_component_children.md
create mode 100644 src/view/README.md
create mode 100644 src/view/builder.md
create mode 100644 video/async.mov
create mode 100644 video/in-order.mov
create mode 100644 video/out-of-order.mov
diff --git a/.github/workflows/publish-book.yml b/.github/workflows/publish-book.yml
new file mode 100644
index 0000000..dee5550
--- /dev/null
+++ b/.github/workflows/publish-book.yml
@@ -0,0 +1,36 @@
+name: Deploy book
+on:
+ push:
+ paths: ["**"]
+ branches:
+ - main
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write # To push a branch
+ pull-requests: write # To create a PR from that branch
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Install mdbook
+ run: |
+ mkdir mdbook
+ curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.27/mdbook-v0.4.27-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook
+ echo `pwd`/mdbook >> $GITHUB_PATH
+ - name: Deploy GitHub Pages
+ run: |
+ mdbook build
+ git worktree add gh-pages
+ git config user.name "Deploy book from CI"
+ git config user.email ""
+ cd gh-pages
+ # Delete the ref to avoid keeping history.
+ git update-ref -d refs/heads/gh-pages
+ rm -rf *
+ mv book/* .
+ git add .
+ git commit -m "Deploy book $GITHUB_SHA to gh-pages"
+ git push --force --set-upstream origin gh-pages
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e9c0728
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+book
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8570b25
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+This project contains the code for the introductory guide/book for the [Leptos](https://leptos.dev) web framework.
+
+It is built using `mdbook`. You can view a local copy by installing `mdbook`
+
+```bash
+cargo install mdbook
+```
+
+and run the book with
+
+```
+mdbook serve
+```
+
+It should be available at `http://localhost:3000`.
diff --git a/book.toml b/book.toml
new file mode 100644
index 0000000..cd7ca14
--- /dev/null
+++ b/book.toml
@@ -0,0 +1,10 @@
+[output.html]
+additional-css = ["./mdbook-admonish.css"]
+[output.html.playground]
+runnable = false
+
+[preprocessor]
+
+[preprocessor.admonish]
+command = "mdbook-admonish"
+assets_version = "3.0.1" # do not edit: managed by `mdbook-admonish install`
diff --git a/mdbook-admonish.css b/mdbook-admonish.css
new file mode 100644
index 0000000..a0a566a
--- /dev/null
+++ b/mdbook-admonish.css
@@ -0,0 +1,345 @@
+@charset "UTF-8";
+:root {
+ --md-admonition-icon--admonish-note: url("data:image/svg+xml;charset=utf-8,");
+ --md-admonition-icon--admonish-abstract: url("data:image/svg+xml;charset=utf-8,");
+ --md-admonition-icon--admonish-info: url("data:image/svg+xml;charset=utf-8,");
+ --md-admonition-icon--admonish-tip: url("data:image/svg+xml;charset=utf-8,");
+ --md-admonition-icon--admonish-success: url("data:image/svg+xml;charset=utf-8,");
+ --md-admonition-icon--admonish-question: url("data:image/svg+xml;charset=utf-8,");
+ --md-admonition-icon--admonish-warning: url("data:image/svg+xml;charset=utf-8,");
+ --md-admonition-icon--admonish-failure: url("data:image/svg+xml;charset=utf-8,");
+ --md-admonition-icon--admonish-danger: url("data:image/svg+xml;charset=utf-8,");
+ --md-admonition-icon--admonish-bug: url("data:image/svg+xml;charset=utf-8,");
+ --md-admonition-icon--admonish-example: url("data:image/svg+xml;charset=utf-8,");
+ --md-admonition-icon--admonish-quote: url("data:image/svg+xml;charset=utf-8,");
+ --md-details-icon: url("data:image/svg+xml;charset=utf-8,");
+}
+
+:is(.admonition) {
+ display: flow-root;
+ margin: 1.5625em 0;
+ padding: 0 1.2rem;
+ color: var(--fg);
+ page-break-inside: avoid;
+ background-color: var(--bg);
+ border: 0 solid black;
+ border-inline-start-width: 0.4rem;
+ border-radius: 0.2rem;
+ box-shadow: 0 0.2rem 1rem rgba(0, 0, 0, 0.05), 0 0 0.1rem rgba(0, 0, 0, 0.1);
+}
+@media print {
+ :is(.admonition) {
+ box-shadow: none;
+ }
+}
+:is(.admonition) > * {
+ box-sizing: border-box;
+}
+:is(.admonition) :is(.admonition) {
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+:is(.admonition) > .tabbed-set:only-child {
+ margin-top: 0;
+}
+html :is(.admonition) > :last-child {
+ margin-bottom: 1.2rem;
+}
+
+a.admonition-anchor-link {
+ display: none;
+ position: absolute;
+ left: -1.2rem;
+ padding-right: 1rem;
+}
+a.admonition-anchor-link:link, a.admonition-anchor-link:visited {
+ color: var(--fg);
+}
+a.admonition-anchor-link:link:hover, a.admonition-anchor-link:visited:hover {
+ text-decoration: none;
+}
+a.admonition-anchor-link::before {
+ content: "§";
+}
+
+:is(.admonition-title, summary.admonition-title) {
+ position: relative;
+ min-height: 4rem;
+ margin-block: 0;
+ margin-inline: -1.6rem -1.2rem;
+ padding-block: 0.8rem;
+ padding-inline: 4.4rem 1.2rem;
+ font-weight: 700;
+ background-color: rgba(68, 138, 255, 0.1);
+ print-color-adjust: exact;
+ -webkit-print-color-adjust: exact;
+ display: flex;
+}
+:is(.admonition-title, summary.admonition-title) p {
+ margin: 0;
+}
+html :is(.admonition-title, summary.admonition-title):last-child {
+ margin-bottom: 0;
+}
+:is(.admonition-title, summary.admonition-title)::before {
+ position: absolute;
+ top: 0.625em;
+ inset-inline-start: 1.6rem;
+ width: 2rem;
+ height: 2rem;
+ background-color: #448aff;
+ print-color-adjust: exact;
+ -webkit-print-color-adjust: exact;
+ mask-image: url('data:image/svg+xml;charset=utf-8,');
+ -webkit-mask-image: url('data:image/svg+xml;charset=utf-8,');
+ mask-repeat: no-repeat;
+ -webkit-mask-repeat: no-repeat;
+ mask-size: contain;
+ -webkit-mask-size: contain;
+ content: "";
+}
+:is(.admonition-title, summary.admonition-title):hover a.admonition-anchor-link {
+ display: initial;
+}
+
+details.admonition > summary.admonition-title::after {
+ position: absolute;
+ top: 0.625em;
+ inset-inline-end: 1.6rem;
+ height: 2rem;
+ width: 2rem;
+ background-color: currentcolor;
+ mask-image: var(--md-details-icon);
+ -webkit-mask-image: var(--md-details-icon);
+ mask-repeat: no-repeat;
+ -webkit-mask-repeat: no-repeat;
+ mask-size: contain;
+ -webkit-mask-size: contain;
+ content: "";
+ transform: rotate(0deg);
+ transition: transform 0.25s;
+}
+details[open].admonition > summary.admonition-title::after {
+ transform: rotate(90deg);
+}
+
+:is(.admonition):is(.admonish-note) {
+ border-color: #448aff;
+}
+
+:is(.admonish-note) > :is(.admonition-title, summary.admonition-title) {
+ background-color: rgba(68, 138, 255, 0.1);
+}
+:is(.admonish-note) > :is(.admonition-title, summary.admonition-title)::before {
+ background-color: #448aff;
+ mask-image: var(--md-admonition-icon--admonish-note);
+ -webkit-mask-image: var(--md-admonition-icon--admonish-note);
+ mask-repeat: no-repeat;
+ -webkit-mask-repeat: no-repeat;
+ mask-size: contain;
+ -webkit-mask-repeat: no-repeat;
+}
+
+:is(.admonition):is(.admonish-abstract, .admonish-summary, .admonish-tldr) {
+ border-color: #00b0ff;
+}
+
+:is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title) {
+ background-color: rgba(0, 176, 255, 0.1);
+}
+:is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title)::before {
+ background-color: #00b0ff;
+ mask-image: var(--md-admonition-icon--admonish-abstract);
+ -webkit-mask-image: var(--md-admonition-icon--admonish-abstract);
+ mask-repeat: no-repeat;
+ -webkit-mask-repeat: no-repeat;
+ mask-size: contain;
+ -webkit-mask-repeat: no-repeat;
+}
+
+:is(.admonition):is(.admonish-info, .admonish-todo) {
+ border-color: #00b8d4;
+}
+
+:is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title) {
+ background-color: rgba(0, 184, 212, 0.1);
+}
+:is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title)::before {
+ background-color: #00b8d4;
+ mask-image: var(--md-admonition-icon--admonish-info);
+ -webkit-mask-image: var(--md-admonition-icon--admonish-info);
+ mask-repeat: no-repeat;
+ -webkit-mask-repeat: no-repeat;
+ mask-size: contain;
+ -webkit-mask-repeat: no-repeat;
+}
+
+:is(.admonition):is(.admonish-tip, .admonish-hint, .admonish-important) {
+ border-color: #00bfa5;
+}
+
+:is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title) {
+ background-color: rgba(0, 191, 165, 0.1);
+}
+:is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title)::before {
+ background-color: #00bfa5;
+ mask-image: var(--md-admonition-icon--admonish-tip);
+ -webkit-mask-image: var(--md-admonition-icon--admonish-tip);
+ mask-repeat: no-repeat;
+ -webkit-mask-repeat: no-repeat;
+ mask-size: contain;
+ -webkit-mask-repeat: no-repeat;
+}
+
+:is(.admonition):is(.admonish-success, .admonish-check, .admonish-done) {
+ border-color: #00c853;
+}
+
+:is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title) {
+ background-color: rgba(0, 200, 83, 0.1);
+}
+:is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title)::before {
+ background-color: #00c853;
+ mask-image: var(--md-admonition-icon--admonish-success);
+ -webkit-mask-image: var(--md-admonition-icon--admonish-success);
+ mask-repeat: no-repeat;
+ -webkit-mask-repeat: no-repeat;
+ mask-size: contain;
+ -webkit-mask-repeat: no-repeat;
+}
+
+:is(.admonition):is(.admonish-question, .admonish-help, .admonish-faq) {
+ border-color: #64dd17;
+}
+
+:is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title) {
+ background-color: rgba(100, 221, 23, 0.1);
+}
+:is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title)::before {
+ background-color: #64dd17;
+ mask-image: var(--md-admonition-icon--admonish-question);
+ -webkit-mask-image: var(--md-admonition-icon--admonish-question);
+ mask-repeat: no-repeat;
+ -webkit-mask-repeat: no-repeat;
+ mask-size: contain;
+ -webkit-mask-repeat: no-repeat;
+}
+
+:is(.admonition):is(.admonish-warning, .admonish-caution, .admonish-attention) {
+ border-color: #ff9100;
+}
+
+:is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title) {
+ background-color: rgba(255, 145, 0, 0.1);
+}
+:is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title)::before {
+ background-color: #ff9100;
+ mask-image: var(--md-admonition-icon--admonish-warning);
+ -webkit-mask-image: var(--md-admonition-icon--admonish-warning);
+ mask-repeat: no-repeat;
+ -webkit-mask-repeat: no-repeat;
+ mask-size: contain;
+ -webkit-mask-repeat: no-repeat;
+}
+
+:is(.admonition):is(.admonish-failure, .admonish-fail, .admonish-missing) {
+ border-color: #ff5252;
+}
+
+:is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title) {
+ background-color: rgba(255, 82, 82, 0.1);
+}
+:is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title)::before {
+ background-color: #ff5252;
+ mask-image: var(--md-admonition-icon--admonish-failure);
+ -webkit-mask-image: var(--md-admonition-icon--admonish-failure);
+ mask-repeat: no-repeat;
+ -webkit-mask-repeat: no-repeat;
+ mask-size: contain;
+ -webkit-mask-repeat: no-repeat;
+}
+
+:is(.admonition):is(.admonish-danger, .admonish-error) {
+ border-color: #ff1744;
+}
+
+:is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title) {
+ background-color: rgba(255, 23, 68, 0.1);
+}
+:is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title)::before {
+ background-color: #ff1744;
+ mask-image: var(--md-admonition-icon--admonish-danger);
+ -webkit-mask-image: var(--md-admonition-icon--admonish-danger);
+ mask-repeat: no-repeat;
+ -webkit-mask-repeat: no-repeat;
+ mask-size: contain;
+ -webkit-mask-repeat: no-repeat;
+}
+
+:is(.admonition):is(.admonish-bug) {
+ border-color: #f50057;
+}
+
+:is(.admonish-bug) > :is(.admonition-title, summary.admonition-title) {
+ background-color: rgba(245, 0, 87, 0.1);
+}
+:is(.admonish-bug) > :is(.admonition-title, summary.admonition-title)::before {
+ background-color: #f50057;
+ mask-image: var(--md-admonition-icon--admonish-bug);
+ -webkit-mask-image: var(--md-admonition-icon--admonish-bug);
+ mask-repeat: no-repeat;
+ -webkit-mask-repeat: no-repeat;
+ mask-size: contain;
+ -webkit-mask-repeat: no-repeat;
+}
+
+:is(.admonition):is(.admonish-example) {
+ border-color: #7c4dff;
+}
+
+:is(.admonish-example) > :is(.admonition-title, summary.admonition-title) {
+ background-color: rgba(124, 77, 255, 0.1);
+}
+:is(.admonish-example) > :is(.admonition-title, summary.admonition-title)::before {
+ background-color: #7c4dff;
+ mask-image: var(--md-admonition-icon--admonish-example);
+ -webkit-mask-image: var(--md-admonition-icon--admonish-example);
+ mask-repeat: no-repeat;
+ -webkit-mask-repeat: no-repeat;
+ mask-size: contain;
+ -webkit-mask-repeat: no-repeat;
+}
+
+:is(.admonition):is(.admonish-quote, .admonish-cite) {
+ border-color: #9e9e9e;
+}
+
+:is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title) {
+ background-color: rgba(158, 158, 158, 0.1);
+}
+:is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title)::before {
+ background-color: #9e9e9e;
+ mask-image: var(--md-admonition-icon--admonish-quote);
+ -webkit-mask-image: var(--md-admonition-icon--admonish-quote);
+ mask-repeat: no-repeat;
+ -webkit-mask-repeat: no-repeat;
+ mask-size: contain;
+ -webkit-mask-repeat: no-repeat;
+}
+
+.navy :is(.admonition) {
+ background-color: var(--sidebar-bg);
+}
+
+.ayu :is(.admonition),
+.coal :is(.admonition) {
+ background-color: var(--theme-hover);
+}
+
+.rust :is(.admonition) {
+ background-color: var(--sidebar-bg);
+ color: var(--sidebar-fg);
+}
+.rust .admonition-anchor-link:link, .rust .admonition-anchor-link:visited {
+ color: var(--sidebar-fg);
+}
diff --git a/src/01_introduction.md b/src/01_introduction.md
new file mode 100644
index 0000000..35d85d1
--- /dev/null
+++ b/src/01_introduction.md
@@ -0,0 +1,22 @@
+# Introduction
+
+This book is intended as an introduction to the [Leptos](https://github.com/leptos-rs/leptos) Web framework.
+It will walk through the fundamental concepts you need to build applications,
+beginning with a simple application rendered in the browser, and building toward a
+full-stack application with server-side rendering and hydration.
+
+The guide doesn’t assume you know anything about fine-grained reactivity or the
+details of modern Web frameworks. It does assume you are familiar with the Rust
+programming language, HTML, CSS, and the DOM and basic Web APIs.
+
+Leptos is most similar to frameworks like [Solid](https://www.solidjs.com) (JavaScript)
+and [Sycamore](https://sycamore-rs.netlify.app/) (Rust). There are some similarities
+to other frameworks like React (JavaScript), Svelte (JavaScript), Yew (Rust), and
+Dioxus (Rust), so knowledge of one of those frameworks may also make it easier to
+understand Leptos.
+
+You can find more detailed docs for each part of the API at [Docs.rs](https://docs.rs/leptos/latest/leptos/).
+
+**Important Note**: This current version of the book reflects the `0.5.1` release. The CodeSandbox versions of the examples still reflect `0.4` and earlier APIs and are in the process of being updated.
+
+> The source code for the book is available [here](https://github.com/leptos-rs/book). PRs for typos or clarification are always welcome.
diff --git a/src/02_getting_started.md b/src/02_getting_started.md
new file mode 100644
index 0000000..9512dd1
--- /dev/null
+++ b/src/02_getting_started.md
@@ -0,0 +1,97 @@
+# Getting Started
+
+There are two basic paths to getting started with Leptos:
+
+1. Client-side rendering with [Trunk](https://trunkrs.dev/)
+2. Full-stack rendering with [`cargo-leptos`](https://github.com/leptos-rs/cargo-leptos)
+
+For the early examples, it will be easiest to begin with Trunk. We’ll introduce
+`cargo-leptos` a little later in this series.
+
+If you don’t already have it installed, you can install Trunk by running
+
+```bash
+cargo install trunk
+```
+
+Create a basic Rust project
+
+```bash
+cargo init leptos-tutorial
+```
+
+`cd` into your new `leptos-tutorial` project and add `leptos` as a dependency
+
+```bash
+cargo add leptos --features=csr,nightly
+```
+
+> **Note**: This version of the book reflects the Leptos 0.5 release. The CodeSandbox examples have not yet been updated from 0.4 and earlier versions.
+
+Or you can leave off `nightly` if you're using stable Rust
+
+```bash
+cargo add leptos --features=csr
+```
+
+> Using `nightly` Rust, and the `nightly` feature in Leptos enables the function-call syntax for signal getters and setters that is used in most of this book.
+>
+> To use nightly Rust, you can either opt into nightly for all your Rust projects by running
+>
+> ```bash
+> rustup toolchain install nightly
+> rustup default nightly
+> ```
+>
+> or only for this project
+>
+> ```bash
+> rustup toolchain install nightly
+> cd
+> rustup override set nightly
+> ```
+>
+> [See here for more details.](https://doc.rust-lang.org/book/appendix-07-nightly-rust.html)
+>
+> If you’d rather use stable Rust with Leptos, you can do that too. In the guide and examples, you’ll just use the [`ReadSignal::get()`](https://docs.rs/leptos/latest/leptos/struct.ReadSignal.html#impl-SignalGet%3CT%3E-for-ReadSignal%3CT%3E) and [`WriteSignal::set()`](https://docs.rs/leptos/latest/leptos/struct.WriteSignal.html#impl-SignalGet%3CT%3E-for-ReadSignal%3CT%3E) methods instead of calling signal getters and setters as functions.
+
+Make sure you've added the `wasm32-unknown-unknown` target so that Rust can compile your code to WebAssembly to run in the browser.
+
+```bash
+rustup target add wasm32-unknown-unknown
+```
+
+Create a simple `index.html` in the root of the `leptos-tutorial` directory
+
+```html
+
+
+
+
+
+```
+
+And add a simple “Hello, world!” to your `main.rs`
+
+```rust
+use leptos::*;
+
+fn main() {
+ mount_to_body(|| view! {
"Hello, world!"
})
+}
+```
+
+Your directory structure should now look something like this
+
+```
+leptos_tutorial
+├── src
+│ └── main.rs
+├── Cargo.toml
+├── index.html
+```
+
+Now run `trunk serve --open` from the root of the `leptos-tutorial` directory.
+Trunk should automatically compile your app and open it in your default browser.
+If you make edits to `main.rs`, Trunk will recompile your source code and
+live-reload the page.
diff --git a/src/15_global_state.md b/src/15_global_state.md
new file mode 100644
index 0000000..57873be
--- /dev/null
+++ b/src/15_global_state.md
@@ -0,0 +1,402 @@
+# Global State Management
+
+So far, we've only been working with local state in components, and we’ve seen how to coordinate state between parent and child components. On occasion, there are times where people look for a more general solution for global state management that can work throughout an application.
+
+In general, **you do not need this chapter.** The typical pattern is to compose your application out of components, each of which manages its own local state, not to store all state in a global structure. However, there are some cases (like theming, saving user settings, or sharing data between components in different parts of your UI) in which you may want to use some kind of global state management.
+
+The three best approaches to global state are
+
+1. Using the router to drive global state via the URL
+2. Passing signals through context
+3. Creating a global state struct and creating lenses into it with `create_slice`
+
+## Option #1: URL as Global State
+
+In many ways, the URL is actually the best way to store global state. It can be accessed from any component, anywhere in your tree. There are native HTML elements like `
}
+ >
+ // the children will be rendered once initially,
+ // and then whenever any resources has been resolved
+
+ "Your shouting name is "
+ {move || async_data.get()}
+
+
+ }
+}
+
+fn main() {
+ leptos::mount_to_body(App)
+}
+```
+
+
+
diff --git a/src/async/12_transition.md b/src/async/12_transition.md
new file mode 100644
index 0000000..85db85b
--- /dev/null
+++ b/src/async/12_transition.md
@@ -0,0 +1,83 @@
+# ``
+
+You’ll notice in the `` example that if you keep reloading the data, it keeps flickering back to `"Loading..."`. Sometimes this is fine. For other times, there’s [``](https://docs.rs/leptos/latest/leptos/fn.Transition.html).
+
+`` behaves exactly the same as ``, but instead of falling back every time, it only shows the fallback the first time. On all subsequent loads, it continues showing the old data until the new data are ready. This can be really handy to prevent the flickering effect, and to allow users to continue interacting with your application.
+
+This example shows how you can create a simple tabbed contact list with ``. When you select a new tab, it continues showing the current contact until the new data loads. This can be a much better user experience than constantly falling back to a loading message.
+
+[Click to open CodeSandbox.](https://codesandbox.io/p/sandbox/12-transition-0-5-2jg5lz?file=%2Fsrc%2Fmain.rs%3A1%2C1)
+
+
+
+
+CodeSandbox Source
+
+```rust
+use gloo_timers::future::TimeoutFuture;
+use leptos::*;
+
+async fn important_api_call(id: usize) -> String {
+ TimeoutFuture::new(1_000).await;
+ match id {
+ 0 => "Alice",
+ 1 => "Bob",
+ 2 => "Carol",
+ _ => "User not found",
+ }
+ .to_string()
+}
+
+#[component]
+fn App() -> impl IntoView {
+ let (tab, set_tab) = create_signal(0);
+
+ // this will reload every time `tab` changes
+ let user_data = create_resource(tab, |tab| async move { important_api_call(tab).await });
+
+ view! {
+
+
+ }
+}
+
+fn main() {
+ leptos::mount_to_body(App)
+}
+```
+
+
+
diff --git a/src/async/13_actions.md b/src/async/13_actions.md
new file mode 100644
index 0000000..625dc33
--- /dev/null
+++ b/src/async/13_actions.md
@@ -0,0 +1,176 @@
+# Mutating Data with Actions
+
+We’ve talked about how to load `async` data with resources. Resources immediately load data and work closely with `` and `` components to show whether data is loading in your app. But what if you just want to call some arbitrary `async` function and keep track of what it’s doing?
+
+Well, you could always use [`spawn_local`](https://docs.rs/leptos/latest/leptos/fn.spawn_local.html). This allows you to just spawn an `async` task in a synchronous environment by handing the `Future` off to the browser (or, on the server, Tokio or whatever other runtime you’re using). But how do you know if it’s still pending? Well, you could just set a signal to show whether it’s loading, and another one to show the result...
+
+All of this is true. Or you could use the final `async` primitive: [`create_action`](https://docs.rs/leptos/latest/leptos/fn.create_action.html).
+
+Actions and resources seem similar, but they represent fundamentally different things. If you’re trying to load data by running an `async` function, either once or when some other value changes, you probably want to use `create_resource`. If you’re trying to occasionally run an `async` function in response to something like a user clicking a button, you probably want to use `create_action`.
+
+Say we have some `async` function we want to run.
+
+```rust
+async fn add_todo_request(new_title: &str) -> Uuid {
+ /* do some stuff on the server to add a new todo */
+}
+```
+
+`create_action` takes an `async` function that takes a reference to a single argument, which you could think of as its “input type.”
+
+> The input is always a single type. If you want to pass in multiple arguments, you can do it with a struct or tuple.
+>
+> ```rust
+> // if there's a single argument, just use that
+> let action1 = create_action(|input: &String| {
+> let input = input.clone();
+> async move { todo!() }
+> });
+>
+> // if there are no arguments, use the unit type `()`
+> let action2 = create_action(|input: &()| async { todo!() });
+>
+> // if there are multiple arguments, use a tuple
+> let action3 = create_action(
+> |input: &(usize, String)| async { todo!() }
+> );
+> ```
+>
+> Because the action function takes a reference but the `Future` needs to have a `'static` lifetime, you’ll usually need to clone the value to pass it into the `Future`. This is admittedly awkward but it unlocks some powerful features like optimistic UI. We’ll see a little more about that in future chapters.
+
+So in this case, all we need to do to create an action is
+
+```rust
+let add_todo_action = create_action(|input: &String| {
+ let input = input.to_owned();
+ async move { add_todo_request(&input).await }
+});
+```
+
+Rather than calling `add_todo_action` directly, we’ll call it with `.dispatch()`, as in
+
+```rust
+add_todo_action.dispatch("Some value".to_string());
+```
+
+You can do this from an event listener, a timeout, or anywhere; because `.dispatch()` isn’t an `async` function, it can be called from a synchronous context.
+
+Actions provide access to a few signals that synchronize between the asynchronous action you’re calling and the synchronous reactive system:
+
+```rust
+let submitted = add_todo_action.input(); // RwSignal