Standalone mjs script loses page state when using preact-router after route change
See original GitHub issueThe Problem
State within a <Route>
is lost when using htm/preact/standalone.mjs
and preact-router
after navigating.
Backstory
I was testing out htm/preact/standalone.mjs
with preact-router
and I noticed that the page state gets lost altogether after navigating. There is no error thrown to the console.
Bug Replication
I created a mini-app to replicate this bug. It’s a SPA with two routes, /
and /other
, corresponding to a “Toggle” page and an “Other” page. If you load /other
first, and then navigate to “Toggle”, the toggler is broken. The same thing occurs if you load Toggle first and navigate to Other and back to “Toggle”.
The only case when the “Toggle” state works is on initial page load.
Video:
Code (loaded in a <script>
tag with type="module"
):
import {
html,
render,
useReducer
} from '//unpkg.com/htm/preact/standalone.mjs'
import Router from '//unpkg.com/preact-router?module'
function App() {
return html`
<div>
<${Header} url=${this.state.url} />
<${Router} onChange=${e => this.setState(e)}>
<${Toggle} path="/" />
<${Other} path="/other" />
<//>
</div>
`
}
const Header = ({ url }) => html`
<header style="max-width: 400px">
<nav>
<a href="/">Toggle</a>
<a href="/other">Other Page</a>
</nav>
<section>URL:<input readonly value=${url} /></section>
</header>
`
const Toggle = () => {
const [on, toggle] = useReducer(v => !v, false)
return html`
<section>
<h1>Toggle</h1>
<strong>Value: ${on || 'un'}checked</strong>
<br />
<label>
<input type="checkbox" value=${on} onClick=${toggle} />
Check Me
</label>
<br />
<p>
Value toggles after initial pageload, but after nagivation it's
broken...
</p>
</section>
`
}
const Other = () =>
html`
<section>
<h1>Other</h1>
<p>If you navigate to the Toggle Page, it will break its page's state</p>
</section>
`
render(html`<${App} />`, document.body)
What I tried
To ensure that this was indeed an htm/preact/standalone.mjs
problem, I rebuilt this sample app with both transpiled JSX and with htm/preact import
Issue Analytics
- State:
- Created 4 years ago
- Comments:7 (3 by maintainers)
Top GitHub Comments
@n8jadams just to clarify - when you move away from a route, that component is completely unmounted and all state is dropped - that behavior isn’t a bug.
You’re right about the codesandbox though, and it’s actually still getting two copies of Preact. This happens because unpkg transforms import statements to refer back to itself as full URLs when the
?module
querystring parameter is passed. When doing that, it usespeerDependencies
to insert version numbers in the URLs, and since those version numbers are different in preact-router and htm, it ends up causing 2 copies of Preact to be loaded since they’re imported using different specifiers. This happens even though the final redirected URLs are the same, which is pretty annoying:Solutions
Unpkg, but using UMD
Unfortunately, there’s only really one way to solve this at the moment, and that’s to not use unpkg’s
?module
implementation. It’s still possible to use unpkg, just with the default UMD bundles. They’re a tiny bit larger, but not as much as you’d think (~100b):JSDelivr Combined UMD
You can also try jsdelivr, which is a CDN like unpkg but with a clever “combine” feature that lets you load all five dependencies in a single URL:
There used to be an amazing web service called
packd-es
that actually did this but using ES Modules. You gave it a list of npm dependencies, and it would run them through Rollup to produce a highly optimized “package” module with all of their exports combined. The service was likely too costly to maintain and has been unavailable for a while. It was a barely-modified fork of Rich Harris’s packd project.Snowpack
Another option is to use a tool like Snowpack, which assembles a simple
web_modules
directory containing the ES Modules files your app depends on. I went ahead and set up a Glitch demo that you can check out here: https://glitch.com/edit/#!/htm-preact-hooks-router?path=index.js:4:0This approach does use
npm
, but doesn’t use a bundler and still uses native ES Modules. npm is just used to download the packages.Pre-bundling
It’s relatively easy to pre-bundle a set of known dependencies using Rollup or some other tool. I threw this Gist together that does this using Microbundle, and was pleased to see that the combination of HTM+Preact+Hooks+Router is only 6kB over-the-wire. Here’s the demo ported over to a single ES Module import for that Gist: https://codesandbox.io/s/htm-preact-hooks-router-pre-bundled-p9615
Ack! I messed up the assignments -
html
is provided bywindow.htmPreact
.Here’s a codesandbox that actually gets rid of
htm/preact
, since that module really just does this:https://codesandbox.io/s/htm-preact-hooks-router-umd-c07qi