sharing_state.html 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. <!DOCTYPE HTML>
  2. <html lang="en" class="sidebar-visible no-js light">
  3. <head>
  4. <!-- Book generated using mdBook -->
  5. <meta charset="UTF-8">
  6. <title>Sharing State</title>
  7. <!-- Custom HTML head -->
  8. <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  9. <meta name="description" content="">
  10. <meta name="viewport" content="width=device-width, initial-scale=1">
  11. <meta name="theme-color" content="#ffffff" />
  12. <link rel="icon" href="../favicon.svg">
  13. <link rel="shortcut icon" href="../favicon.png">
  14. <link rel="stylesheet" href="../css/variables.css">
  15. <link rel="stylesheet" href="../css/general.css">
  16. <link rel="stylesheet" href="../css/chrome.css">
  17. <link rel="stylesheet" href="../css/print.css" media="print">
  18. <!-- Fonts -->
  19. <link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
  20. <link rel="stylesheet" href="../fonts/fonts.css">
  21. <!-- Highlight.js Stylesheets -->
  22. <link rel="stylesheet" href="../highlight.css">
  23. <link rel="stylesheet" href="../tomorrow-night.css">
  24. <link rel="stylesheet" href="../ayu-highlight.css">
  25. <!-- Custom theme stylesheets -->
  26. <!-- MathJax -->
  27. <script async type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
  28. </head>
  29. <body>
  30. <!-- Provide site root to javascript -->
  31. <script type="text/javascript">
  32. var path_to_root = "../";
  33. var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
  34. </script>
  35. <!-- Work around some values being stored in localStorage wrapped in quotes -->
  36. <script type="text/javascript">
  37. try {
  38. var theme = localStorage.getItem('mdbook-theme');
  39. var sidebar = localStorage.getItem('mdbook-sidebar');
  40. if (theme.startsWith('"') && theme.endsWith('"')) {
  41. localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
  42. }
  43. if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
  44. localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
  45. }
  46. } catch (e) { }
  47. </script>
  48. <!-- Set the theme before any content is loaded, prevents flash -->
  49. <script type="text/javascript">
  50. var theme;
  51. try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
  52. if (theme === null || theme === undefined) { theme = default_theme; }
  53. var html = document.querySelector('html');
  54. html.classList.remove('no-js')
  55. html.classList.remove('light')
  56. html.classList.add(theme);
  57. html.classList.add('js');
  58. </script>
  59. <!-- Hide / unhide sidebar before it is displayed -->
  60. <script type="text/javascript">
  61. var html = document.querySelector('html');
  62. var sidebar = 'hidden';
  63. if (document.body.clientWidth >= 1080) {
  64. try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
  65. sidebar = sidebar || 'visible';
  66. }
  67. html.classList.remove('sidebar-visible');
  68. html.classList.add("sidebar-" + sidebar);
  69. </script>
  70. <nav id="sidebar" class="sidebar" aria-label="Table of contents">
  71. <div class="sidebar-scrollbox">
  72. <ol class="chapter"><li class="chapter-item expanded affix "><a href="../index.html">Introduction</a></li><li class="chapter-item expanded "><a href="../getting_started/index.html"><strong aria-hidden="true">1.</strong> Getting Started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../getting_started/desktop.html"><strong aria-hidden="true">1.1.</strong> Desktop</a></li><li class="chapter-item expanded "><a href="../getting_started/web.html"><strong aria-hidden="true">1.2.</strong> Web</a></li><li class="chapter-item expanded "><a href="../getting_started/ssr.html"><strong aria-hidden="true">1.3.</strong> Server-Side Rendering</a></li><li class="chapter-item expanded "><a href="../getting_started/fullstack.html"><strong aria-hidden="true">1.4.</strong> Fullstack</a></li><li class="chapter-item expanded "><a href="../getting_started/liveview.html"><strong aria-hidden="true">1.5.</strong> Liveview</a></li><li class="chapter-item expanded "><a href="../getting_started/tui.html"><strong aria-hidden="true">1.6.</strong> Terminal UI</a></li><li class="chapter-item expanded "><a href="../getting_started/mobile.html"><strong aria-hidden="true">1.7.</strong> Mobile</a></li><li class="chapter-item expanded "><a href="../getting_started/hot_reload.html"><strong aria-hidden="true">1.8.</strong> Hot Reloading</a></li></ol></li><li class="chapter-item expanded "><a href="../describing_ui/index.html"><strong aria-hidden="true">2.</strong> Describing the UI</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../describing_ui/special_attributes.html"><strong aria-hidden="true">2.1.</strong> Special Attributes</a></li><li class="chapter-item expanded "><a href="../describing_ui/components.html"><strong aria-hidden="true">2.2.</strong> Components</a></li><li class="chapter-item expanded "><a href="../describing_ui/component_props.html"><strong aria-hidden="true">2.3.</strong> Props</a></li><li class="chapter-item expanded "><a href="../describing_ui/component_children.html"><strong aria-hidden="true">2.4.</strong> Component Children</a></li></ol></li><li class="chapter-item expanded "><a href="../interactivity/index.html"><strong aria-hidden="true">3.</strong> Interactivity</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../interactivity/event_handlers.html"><strong aria-hidden="true">3.1.</strong> Event Listeners</a></li><li class="chapter-item expanded "><a href="../interactivity/hooks.html"><strong aria-hidden="true">3.2.</strong> Hooks &amp; Component State</a></li><li class="chapter-item expanded "><a href="../interactivity/user_input.html"><strong aria-hidden="true">3.3.</strong> User Input</a></li><li class="chapter-item expanded "><a href="../interactivity/sharing_state.html" class="active"><strong aria-hidden="true">3.4.</strong> Sharing State</a></li><li class="chapter-item expanded "><a href="../interactivity/custom_hooks.html"><strong aria-hidden="true">3.5.</strong> Custom Hooks</a></li><li class="chapter-item expanded "><a href="../interactivity/dynamic_rendering.html"><strong aria-hidden="true">3.6.</strong> Dynamic Rendering</a></li><li class="chapter-item expanded "><a href="../interactivity/router.html"><strong aria-hidden="true">3.7.</strong> Routing</a></li></ol></li><li class="chapter-item expanded "><a href="../async/index.html"><strong aria-hidden="true">4.</strong> Async</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../async/use_future.html"><strong aria-hidden="true">4.1.</strong> UseFuture</a></li><li class="chapter-item expanded "><a href="../async/use_coroutine.html"><strong aria-hidden="true">4.2.</strong> UseCoroutine</a></li><li class="chapter-item expanded "><a href="../async/spawn.html"><strong aria-hidden="true">4.3.</strong> Spawning Futures</a></li></ol></li><li class="chapter-item expanded "><a href="../best_practices/index.html"><strong aria-hidden="true">5.</strong> Best Practices</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../best_practices/error_handling.html"><strong aria-hidden="true">5.1.</strong> Error Handling</a></li><li class="chapter-item expanded "><a href="../best_practices/antipatterns.html"><strong aria-hidden="true">5.2.</strong> Antipatterns</a></li></ol></li><li class="chapter-item expanded "><a href="../publishing/index.html"><strong aria-hidden="true">6.</strong> Publishing</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../publishing/desktop.html"><strong aria-hidden="true">6.1.</strong> Desktop</a></li><li class="chapter-item expanded "><a href="../publishing/web.html"><strong aria-hidden="true">6.2.</strong> Web</a></li><li class="spacer"></li></ol></li><li class="chapter-item expanded "><a href="../fullstack/index.html"><strong aria-hidden="true">7.</strong> Fullstack</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../fullstack/getting_started.html"><strong aria-hidden="true">7.1.</strong> Getting Started</a></li><li class="chapter-item expanded "><a href="../fullstack/server_functions.html"><strong aria-hidden="true">7.2.</strong> Communicating with the Server</a></li><li class="spacer"></li></ol></li><li class="chapter-item expanded "><a href="../custom_renderer/index.html"><strong aria-hidden="true">8.</strong> Custom Renderer</a></li><li class="spacer"></li><li class="chapter-item expanded "><a href="../contributing/index.html"><strong aria-hidden="true">9.</strong> Contributing</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../contributing/project_structure.html"><strong aria-hidden="true">9.1.</strong> Project Structure</a></li><li class="chapter-item expanded "><a href="../contributing/walkthrough_readme.html"><strong aria-hidden="true">9.2.</strong> Walkthrough of Internals</a></li><li class="chapter-item expanded "><a href="../contributing/guiding_principles.html"><strong aria-hidden="true">9.3.</strong> Guiding Principles</a></li><li class="chapter-item expanded "><a href="../contributing/roadmap.html"><strong aria-hidden="true">9.4.</strong> Roadmap</a></li></ol></li></ol>
  73. </div>
  74. <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
  75. </nav>
  76. <div id="page-wrapper" class="page-wrapper">
  77. <div class="page">
  78. <div id="menu-bar-hover-placeholder"></div>
  79. <div id="menu-bar" class="menu-bar sticky bordered">
  80. <div class="left-buttons">
  81. <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
  82. <i class="fa fa-bars"></i>
  83. </button>
  84. <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
  85. <i class="fa fa-paint-brush"></i>
  86. </button>
  87. <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
  88. <li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
  89. <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
  90. <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
  91. <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
  92. <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
  93. </ul>
  94. <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
  95. <i class="fa fa-search"></i>
  96. </button>
  97. <button id="language-toggle" class="icon-button" type="button" title="Select language" aria-label="Select language" aria-haspopup="true" aria-expanded="false" aria-controls="language-list">
  98. <i class="fa fa-globe"></i>
  99. </button>
  100. <ul id="language-list" class="language-popup" aria-label="Languages" role="menu">
  101. <li role="none"><a href="../../en/interactivity/sharing_state.html"><button role="menuitem" class="language" id="light">English</button></a></li>
  102. <li role="none"><a href="../../pt-br/interactivity/sharing_state.html"><button role="menuitem" class="language" id="light">Português Brasileiro</button></a></li>
  103. </ul>
  104. </div>
  105. <h1 class="menu-title"></h1>
  106. <div class="right-buttons">
  107. <a href="../print.html" title="Print this book" aria-label="Print this book">
  108. <i id="print-button" class="fa fa-print"></i>
  109. </a>
  110. <a href="https://github.com/DioxusLabs/dioxus/edit/master/docs/guide" title="Git repository" aria-label="Git repository">
  111. <i id="git-repository-button" class="fa fa-github"></i>
  112. </a>
  113. <a href="https://github.com/DioxusLabs/dioxus/edit/master/docs/guide/src/interactivity/sharing_state.md" title="Suggest an edit" aria-label="Suggest an edit">
  114. <i id="git-edit-button" class="fa fa-edit"></i>
  115. </a>
  116. </div>
  117. </div>
  118. <div id="search-wrapper" class="hidden">
  119. <form id="searchbar-outer" class="searchbar-outer">
  120. <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
  121. </form>
  122. <div id="searchresults-outer" class="searchresults-outer hidden">
  123. <div id="searchresults-header" class="searchresults-header"></div>
  124. <ul id="searchresults">
  125. </ul>
  126. </div>
  127. </div>
  128. <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
  129. <script type="text/javascript">
  130. document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
  131. document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
  132. Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
  133. link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
  134. });
  135. </script>
  136. <div id="content" class="content">
  137. <main>
  138. <h1 id="sharing-state"><a class="header" href="#sharing-state">Sharing State</a></h1>
  139. <p>Often, multiple components need to access the same state. Depending on your needs, there are several ways to implement this.</p>
  140. <h2 id="lifting-state"><a class="header" href="#lifting-state">Lifting State</a></h2>
  141. <p>One approach to share state between components is to &quot;lift&quot; it up to the nearest common ancestor. This means putting the <code>use_state</code> hook in a parent component, and passing the needed values down as props.</p>
  142. <p>Suppose we want to build a meme editor. We want to have an input to edit the meme caption, but also a preview of the meme with the caption. Logically, the meme and the input are 2 separate components, but they need access to the same state (the current caption).</p>
  143. <blockquote>
  144. <p>Of course, in this simple example, we could write everything in one component – but it is better to split everything out in smaller components to make the code more reusable, maintainable, and performant (this is even more important for larger, complex apps).</p>
  145. </blockquote>
  146. <p>We start with a <code>Meme</code> component, responsible for rendering a meme with a given caption:</p>
  147. <pre><pre class="playground"><code class="language-rust edition2018">
  148. <span class="boring">#![allow(unused)]
  149. </span><span class="boring">fn main() {
  150. </span>#[inline_props]
  151. fn Meme&lt;'a&gt;(cx: Scope&lt;'a&gt;, caption: &amp;'a str) -&gt; Element&lt;'a&gt; {
  152. let container_style = r#&quot;
  153. position: relative;
  154. width: fit-content;
  155. &quot;#;
  156. let caption_container_style = r#&quot;
  157. position: absolute;
  158. bottom: 0;
  159. left: 0;
  160. right: 0;
  161. padding: 16px 8px;
  162. &quot;#;
  163. let caption_style = r&quot;
  164. font-size: 32px;
  165. margin: 0;
  166. color: white;
  167. text-align: center;
  168. &quot;;
  169. cx.render(rsx!(
  170. div {
  171. style: &quot;{container_style}&quot;,
  172. img {
  173. src: &quot;https://i.imgflip.com/2zh47r.jpg&quot;,
  174. height: &quot;500px&quot;,
  175. },
  176. div {
  177. style: &quot;{caption_container_style}&quot;,
  178. p {
  179. style: &quot;{caption_style}&quot;,
  180. &quot;{caption}&quot;
  181. }
  182. }
  183. }
  184. ))
  185. }
  186. <span class="boring">}
  187. </span></code></pre></pre>
  188. <blockquote>
  189. <p>Note that the <code>Meme</code> component is unaware where the caption is coming from – it could be stored in <code>use_state</code>, <code>use_ref</code>, or a constant. This ensures that it is very reusable – the same component can be used for a meme gallery without any changes!</p>
  190. </blockquote>
  191. <p>We also create a caption editor, completely decoupled from the meme. The caption editor must not store the caption itself – otherwise, how will we provide it to the <code>Meme</code> component? Instead, it should accept the current caption as a prop, as well as an event handler to delegate input events to:</p>
  192. <pre><pre class="playground"><code class="language-rust edition2018">
  193. <span class="boring">#![allow(unused)]
  194. </span><span class="boring">fn main() {
  195. </span>#[inline_props]
  196. fn CaptionEditor&lt;'a&gt;(
  197. cx: Scope&lt;'a&gt;,
  198. caption: &amp;'a str,
  199. on_input: EventHandler&lt;'a, FormEvent&gt;,
  200. ) -&gt; Element&lt;'a&gt; {
  201. let input_style = r&quot;
  202. border: none;
  203. background: cornflowerblue;
  204. padding: 8px 16px;
  205. margin: 0;
  206. border-radius: 4px;
  207. color: white;
  208. &quot;;
  209. cx.render(rsx!(input {
  210. style: &quot;{input_style}&quot;,
  211. value: &quot;{caption}&quot;,
  212. oninput: move |event| on_input.call(event),
  213. }))
  214. }
  215. <span class="boring">}
  216. </span></code></pre></pre>
  217. <p>Finally, a third component will render the other two as children. It will be responsible for keeping the state and passing down the relevant props.</p>
  218. <pre><pre class="playground"><code class="language-rust edition2018">
  219. <span class="boring">#![allow(unused)]
  220. </span><span class="boring">fn main() {
  221. </span>fn MemeEditor(cx: Scope) -&gt; Element {
  222. let container_style = r&quot;
  223. display: flex;
  224. flex-direction: column;
  225. gap: 16px;
  226. margin: 0 auto;
  227. width: fit-content;
  228. &quot;;
  229. let caption = use_state(cx, || &quot;me waiting for my rust code to compile&quot;.to_string());
  230. cx.render(rsx! {
  231. div {
  232. style: &quot;{container_style}&quot;,
  233. h1 { &quot;Meme Editor&quot; },
  234. Meme {
  235. caption: caption,
  236. },
  237. CaptionEditor {
  238. caption: caption,
  239. on_input: move |event: FormEvent| {caption.set(event.value.clone());},
  240. },
  241. }
  242. })
  243. }
  244. <span class="boring">}
  245. </span></code></pre></pre>
  246. <p><img src="./images/meme_editor_screenshot.png" alt="Meme Editor Screenshot: An old plastic skeleton sitting on a park bench. Caption: &quot;me waiting for a language feature&quot;" /></p>
  247. <h2 id="using-context"><a class="header" href="#using-context">Using Context</a></h2>
  248. <p>Sometimes, some state needs to be shared between multiple components far down the tree, and passing it down through props is very inconvenient.</p>
  249. <p>Suppose now that we want to implement a dark mode toggle for our app. To achieve this, we will make every component select styling depending on whether dark mode is enabled or not.</p>
  250. <blockquote>
  251. <p>Note: we're choosing this approach for the sake of an example. There are better ways to implement dark mode (e.g. using CSS variables). Let's pretend CSS variables don't exist – welcome to 2013!</p>
  252. </blockquote>
  253. <p>Now, we could write another <code>use_state</code> in the top component, and pass <code>is_dark_mode</code> down to every component through props. But think about what will happen as the app grows in complexity – almost every component that renders any CSS is going to need to know if dark mode is enabled or not – so they'll all need the same dark mode prop. And every parent component will need to pass it down to them. Imagine how messy and verbose that would get, especially if we had components several levels deep!</p>
  254. <p>Dioxus offers a better solution than this &quot;prop drilling&quot; – providing context. The <a href="https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context_provider.html"><code>use_context_provider</code></a> hook is similar to <code>use_ref</code>, but it makes it available through <a href="https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context.html"><code>use_context</code></a> for all children components.</p>
  255. <p>First, we have to create a struct for our dark mode configuration:</p>
  256. <pre><pre class="playground"><code class="language-rust edition2018">
  257. <span class="boring">#![allow(unused)]
  258. </span><span class="boring">fn main() {
  259. </span>struct DarkMode(bool);
  260. <span class="boring">}
  261. </span></code></pre></pre>
  262. <p>Now, in a top-level component (like <code>App</code>), we can provide the <code>DarkMode</code> context to all children components:</p>
  263. <pre><pre class="playground"><code class="language-rust edition2018">
  264. <span class="boring">#![allow(unused)]
  265. </span><span class="boring">fn main() {
  266. </span>use_shared_state_provider(cx, || DarkMode(false));
  267. <span class="boring">}
  268. </span></code></pre></pre>
  269. <p>As a result, any child component of <code>App</code> (direct or not), can access the <code>DarkMode</code> context.</p>
  270. <pre><pre class="playground"><code class="language-rust edition2018">
  271. <span class="boring">#![allow(unused)]
  272. </span><span class="boring">fn main() {
  273. </span>let dark_mode_context = use_shared_state::&lt;DarkMode&gt;(cx);
  274. <span class="boring">}
  275. </span></code></pre></pre>
  276. <blockquote>
  277. <p><code>use_context</code> returns <code>Option&lt;UseSharedState&lt;DarkMode&gt;&gt;</code> here. If the context has been provided, the value is <code>Some(UseSharedState&lt;DarkMode&gt;)</code>, which you can call <code>.read</code> or <code>.write</code> on, similarly to <code>UseRef</code>. Otherwise, the value is <code>None</code>.</p>
  278. </blockquote>
  279. <p>For example, here's how we would implement the dark mode toggle, which both reads the context (to determine what color it should render) and writes to it (to toggle dark mode):</p>
  280. <pre><pre class="playground"><code class="language-rust edition2018">
  281. <span class="boring">#![allow(unused)]
  282. </span><span class="boring">fn main() {
  283. </span>pub fn DarkModeToggle(cx: Scope) -&gt; Element {
  284. let dark_mode = use_shared_state::&lt;DarkMode&gt;(cx).unwrap();
  285. let style = if dark_mode.read().0 {
  286. &quot;color:white&quot;
  287. } else {
  288. &quot;&quot;
  289. };
  290. cx.render(rsx!(label {
  291. style: &quot;{style}&quot;,
  292. &quot;Dark Mode&quot;,
  293. input {
  294. r#type: &quot;checkbox&quot;,
  295. oninput: move |event| {
  296. let is_enabled = event.value == &quot;true&quot;;
  297. dark_mode.write().0 = is_enabled;
  298. },
  299. },
  300. }))
  301. }
  302. <span class="boring">}
  303. </span></code></pre></pre>
  304. </main>
  305. <nav class="nav-wrapper" aria-label="Page navigation">
  306. <!-- Mobile navigation buttons -->
  307. <a rel="prev" href="../interactivity/user_input.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
  308. <i class="fa fa-angle-left"></i>
  309. </a>
  310. <a rel="next" href="../interactivity/custom_hooks.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
  311. <i class="fa fa-angle-right"></i>
  312. </a>
  313. <div style="clear: both"></div>
  314. </nav>
  315. </div>
  316. </div>
  317. <nav class="nav-wide-wrapper" aria-label="Page navigation">
  318. <a rel="prev" href="../interactivity/user_input.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
  319. <i class="fa fa-angle-left"></i>
  320. </a>
  321. <a rel="next" href="../interactivity/custom_hooks.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
  322. <i class="fa fa-angle-right"></i>
  323. </a>
  324. </nav>
  325. </div>
  326. <script type="text/javascript">
  327. window.playground_line_numbers = true;
  328. </script>
  329. <script type="text/javascript">
  330. window.playground_copyable = true;
  331. </script>
  332. <script src="../ace.js" type="text/javascript" charset="utf-8"></script>
  333. <script src="../editor.js" type="text/javascript" charset="utf-8"></script>
  334. <script src="../mode-rust.js" type="text/javascript" charset="utf-8"></script>
  335. <script src="../theme-dawn.js" type="text/javascript" charset="utf-8"></script>
  336. <script src="../theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
  337. <script src="../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
  338. <script src="../mark.min.js" type="text/javascript" charset="utf-8"></script>
  339. <script src="../searcher.js" type="text/javascript" charset="utf-8"></script>
  340. <script src="../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
  341. <script src="../highlight.js" type="text/javascript" charset="utf-8"></script>
  342. <script src="../book.js" type="text/javascript" charset="utf-8"></script>
  343. <!-- Custom JS scripts -->
  344. </body>
  345. </html>