'How to use useState hook with Preact and no build tools?
Currently I need to get a Preact application working without any build tools, just with an index.html with the import statements to get preact from the CDN. I'm able to import the useState
hook from the CDN no problem, and even able to console.log() the value of the function useState
, but whenever I try to use it, I get an error saying:
'Uncaught TypeError: u is undefined'
Do ya'll happen to know why this is the case? I have tried to use the useState
function inside of the functional component, and outside and it doesn't work either way. Am I missing something here? Can anyone help point me in the right direction?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="module">
import { h, Component, render } from 'https://unpkg.com/preact?module';
import { useState } from 'https://unpkg.com/preact@latest/hooks/dist/hooks.module.js?module'
import htm from 'https://unpkg.com/htm?module';
// Initialize htm with Preact
const html = htm.bind(h);
const App = (props) => {
const [testVar, setTestVar] = useState(0);
var countVariable = 0;
const incrementButtonHandler = () => {
countVariable = countVariable + 1;
}
const logMethod = () => {
console.log(countVariable);
countVariable = countVariable + 1;
}
return html`<div>
<h1>Test ${props.name}!: ${countVariable}</h1>
<button onClick=${logMethod}>Increment</button>
</div>`;
}
render(html`<${App} name="World" />`, document.body);
</script>
</head>
<body>
</body>
</html>
Solution 1:[1]
This is a known bug and limitation in what unpkg can do, see: https://github.com/preactjs/preact/issues/2571
There's a couple easy fixes though:
- Use already resolved URLs for each module (notice the
@latest
in the preact import)
import { h, render } from 'https://unpkg.com/preact@latest?module'
import { useState } from 'https://unpkg.com/preact@latest/hooks/dist/hooks.module.js?module'
import { html } from 'https://unpkg.com/htm/preact/index.module.js?module'
- Use a CDN without this issue, like skypack, instead:
import { h, render } from 'https://cdn.skypack.dev/preact';
import { useState } from 'https://cdn.skypack.dev/preact/hooks';
import { html } from 'https://cdn.skypack.dev/htm/preact';
Either should work.
Solution 2:[2]
rchristian's answer is useful, but the module-imported hooks appear to fail when using a router. Here's a minimal repro*.
(If you have trouble navigating to the /about
route in the snippets, you may need to run them locally since the iframe doesn't play well with routing on all browsers, notably Chrome at the time of writing.)
<script type="module">
import {h, render, Component} from "https://cdn.skypack.dev/preact";
import {useState} from "https://cdn.skypack.dev/preact/hooks";
import {Router} from "https://cdn.skypack.dev/preact-router";
import htm from "https://cdn.skypack.dev/htm";
const html = htm.bind(h);
const Counter = () => {
const [count, setCount] = useState(0);
const increment = () => {
setCount(prevState => prevState + 1);
};
return html`
<button onClick=${increment}>
Count: ${count}
</button>
`;
};
const Nav = () => html`
<nav>
<a href="/">home</a> | <a href="/about">about</a>
</nav>
`;
const Home = () => html`
<p>Home</p>
<${Counter} />
`;
const About = () => html`
<p>About</p>
`;
const App = () => html`
<header>
<${Nav} />
</header>
<main>
<${Router}>
<${Home} path="/" />
<${About} path="/about" />
<${Home} default />
<//>
</main>
`;
render(html`<${App} />`, document.querySelector("#app"));
</script>
<div id="app"></div>
Starting at the home route, you'll see the counter component works fine. Navigate to the about route, then back to home and you'll see Uncaught (in promise) TypeError: u is null
from the hooks script.
I'd much prefer the module imports so I'd be curious to hear how to fix the above code, but I did manage to work around this with the following UMD approach:
<script type="module">
const {h, render, Component} = preact;
const {useState, useEffect} = preactHooks;
const {Router} = preactRouter;
import htm from "https://cdnjs.cloudflare.com/ajax/libs/htm/3.1.1/htm.module.js";
const html = htm.bind(h);
const Counter = () => {
const [count, setCount] = useState(0);
const increment = () => {
setCount(prevState => prevState + 1);
};
return html`
<button onClick=${increment}>
Count: ${count}
</button>
`;
};
const Nav = () => html`
<nav>
<a href="/">home</a> | <a href="/about">about</a>
</nav>
`;
const Home = () => html`
<p>Home</p>
<${Counter} />
`;
const About = () => html`
<p>About</p>
`;
const App = () => html`
<header>
<${Nav} />
</header>
<main>
<${Router}>
<${Home} path="/" />
<${About} path="/about" />
<${Home} default />
<//>
</main>
`;
render(html`<${App} />`, document.querySelector("#app"));
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/preact/10.7.2/preact.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/preact/10.7.2/hooks.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/preact-router/4.0.1/preact-router.umd.min.js"></script>
<div id="app"></div>
* This is a known issue as developit has a couple of code sandbox repros:
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 |