question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Standalone mjs script loses page state when using preact-router after route change

See original GitHub issue

The 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: bug-movie

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:closed
  • Created 4 years ago
  • Comments:7 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
developitcommented, Jan 29, 2020

@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 uses peerDependencies 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:

Screen Shot 2020-01-27 at 1 34 59 PM

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):

<script src="//unpkg.com/preact"></script>
<script src="//unpkg.com/preact/hooks/dist/hooks.umd.js"></script>
<script src="//unpkg.com/htm"></script>
<script src="//unpkg.com/htm/preact/index.umd.js"></script>
<script src="//unpkg.com/preact-router"></script>
<script type="module">
  const { html, render } = htmPreact;
  const { useReducer } = preactHooks;
  const { Router } = preactRouter;
</script>

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:

<script src="https://cdn.jsdelivr.net/combine/npm/preact/dist/preact.umd.js,npm/preact/hooks/dist/hooks.umd.js,npm/htm,npm/htm/preact/index.umd.js,npm/preact-router">
<script type="module">
  const { html, render } = htmPreact;
  const { useReducer } = preactHooks;
  const { Router } = preactRouter;
</script>

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:0

This 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

(edit 2020-01-29: fixed UMD examples)

1reaction
developitcommented, Jan 29, 2020

Ack! I messed up the assignments - html is provided by window.htmPreact.

Here’s a codesandbox that actually gets rid of htm/preact, since that module really just does this:

const { h, render } = preact;
const html = htm.bind(h);

https://codesandbox.io/s/htm-preact-hooks-router-umd-c07qi

Read more comments on GitHub >

github_iconTop Results From Across the Web

route function from Preact-Router does not update window ...
I am trying to use the route function from preact-router in order to sanitize a hash prefix that I add. However, the route...
Read more >
Gatsby Changelog | 5.3.0
After the files are built, Gatsby will then stitch the resulting markup and JavaScript to the pages that include that shared component. This...
Read more >
Go Preact! ❤️ - fettblog.eu
After using Preact for quite a while I come to the conclusion that Preact is ... Client-side routing; State management ( vuex );...
Read more >
Nuxt js vs nextjs - Caritas Castellaneta
Astro Performance; Case Study: Building a Documentation Website; Nuxt vs. ... That said, following situations warrant the use of Client Routing: Simple ...
Read more >
Untitled
Mirzapur cadet school and college, Mariee sioux friendboats lyrics, Lpn to rn nursing programs in mn, East florida state basketball, Armagh industrial ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found