justin-schroeder / arrow-js Goto Github PK
View Code? Open in Web Editor NEWReactivity without the framework
Home Page: https://arrow-js.com
License: MIT License
Reactivity without the framework
Home Page: https://arrow-js.com
License: MIT License
Hey!
Really loving ArrowJS - super easy to work with :)
One issue I have found is no way to "un watch". Take for example:
watch
on some reactive variable.watch
keeps running.It would be really helpful if the watch
returned some sort of reference which we could then remove on unmount.
A common time this happens is with multi-page apps. When spanning between pages, elements are constantly getting added and removed from the dom. After a bit you just have a bunch of old watch's sitting out there.
Any suggestions are appreciated!
Thanks!
First off, thanks for making this toolkit, it is a breath of fresh air. The description of project scope immediately appealed to me. I wish the project well.
Related to #8 Any type of user generated data can contain XSS vulnerabilities. Using a sample from https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html <IMG SRC=/ onerror="alert(String.fromCharCode(88,83,83))"></img>
I think you should put a warning at minimum on the docs/readme until all known XSS issues are resolved. Thanks again for the project and its inspiring scope.
Hey, awesome work! Excuse my ignorance, I am a newbie with frameworks, proxies etc..! I've got a couple of questions..
const data = r({
price: 25,
quantity: 10,
logTotal: true
})
function total () {
if (data.logTotal) {
console.log(`Total: ${data.price * data.quantity}`);
}
}
w(total)
data.price = 35
data.price = 66
data.price = 150
I'm curious to know if ES6 class methods and other callback functions are officially supported by the r()
function, or if they only work by coincidence.
I'm asking because the ReactiveProxy<T>
type isn't callable, which means you get warnings/errors when using TS, but not when using JS. It also appears to work fine, so I'm not sure if it is intended behavior or not.
Here's an example of what I mean with an ES6 class:
class Foo {
public mutateState(): void {
// mutates internal class state here
}
}
// ...
const data = r(new Foo())
data.mutateState(); // <-- This will get a Typescript warning/error
It yields the following warning/error:
Type '
ReactiveProxy<() => void>
' has no call signatures.
The same thing happens if I pass a function definition into the r()
function instead of a new class (i.e. r({greet: () => alert('hi')})
)
Like I said, though, having it mutate its own data works as expected (and even causes re-renders when placed in a template) if I tell Typescript to ignore the issue.
If it's intended to work, then maybe the ReactiveProxy<T>
type needs a way to be callable?
If passing mutators into the r()
method isn't intended, it would be useful to have it added to the docs.
First off, thank you SO MUCH for your work on this project! I've gone back and forth on every reactivity framework I could find over the past few weeks, and all of them were either too limited or (WAY) too complex to set up. ArrowJS is a huge breath of fresh air! (Not to mention, this is a work project and I was really stressing that I was going to miss my deadline, either because I couldn't find an appropriate framework or because doing it all by hand was taking a very long time.)
So I'm trying to do this:
<fieldset>
${() => addressData.streets.map(
(streetData, index) => html`
<div style="margin-bottom: 5px;">
<input type="text" required value="${streetData}" @input="${e => addressData.street[index] = e.target.value}"/>
<button type="button" @click="${() => removeStreet(index)}">Remove Street</button>
</div>
`.key(index)
)}
<button type="button" @click="${() => addStreet()}">Add Street</button>
</fieldset>
The streets
array is a plain array of strings. I can't easily change the data, so I'm trying to find a way to work with it as-is. All my other arrays are arrays of objects that have database IDs that I can use as keys, but streets
is stored in a weird way in the DB.
If I don't do key()
, I have to re-focus the input box after typing each character. I assume this is because all children of <fieldset>
are being removed and recreated when the data changes. If I do key(index)
, the behaviour while typing is jank (first input needs to be refocused, others don't) and I assume removing streets will be broken (the remaining inputs won't get updated properly).
What's the workaround for this situation? Is there something I'm missing or do I just need to get creative?
arrow-js.com reads:
You can bind an event listener to a DOM element by using the @eventName short hand. This automatically performs a document.addEventListener('eventName') and registers your expression as the event handler.
When that element is removed from the DOM, are all references cleaned up so that the event listeners are GCed properly?
If you have a list of data elements and then change the order of them, the DOM gets out of sync and can render more elements than actually exist in the data.
I wrote a simple todo app with the ability to mark a todo as done. Done todos are sorted to the bottom.
I only had four total todos and marked them done in an odd order. Once I marked them all as done, I got duplicate DOM elements leading to 6 list items, even though the data list still only had 4 items.
This doesn't alway happen at the same time, but it does happen consistently.
I think a "key" is needed to track identical DOM elements across renders, like React.
import { r, t, w } from "@arrow-js/core";
type Todo = {
label: string;
isDone: boolean;
};
const App = () => {
const data = r({
todos: [] as Todo[],
});
w(() => console.log("todos changes", JSON.stringify(data.todos)));
const addTodo = (label: string) => {
console.log("add todo", label);
const newTodo: Todo = { label, isDone: false };
data.todos = [...data.todos, newTodo] as any;
};
const markDone = (todo: Todo) => () => {
console.log("mark done", todo);
data.todos = data.todos.map((t) =>
t === todo ? { ...t, isDone: true } : t
) as any;
};
const sortedTodos = () => {
return [...data.todos].sort((a, b) => {
if (a.isDone === b.isDone) {
return 0;
}
return a.isDone ? 1 : -1;
});
};
return t`
<h1>Todo List</h1>
<input type="text" @keyup="${(e: KeyboardEvent) => {
if (e.code === "Enter") {
const target = e.target as HTMLInputElement;
addTodo(target.value);
target.value = "";
}
}}">
<ul>
${() =>
sortedTodos().map((todo) =>
TodoItem({ todo, markDone: markDone(todo) })
)}
</ul>
`;
};
const TodoItem = ({ todo, markDone }: { todo: Todo; markDone: () => void }) => {
const style = () => {
if (todo.isDone) {
return `text-decoration: line-through`;
}
return "";
};
return t`
<li style=${style}><button @click=${markDone}>Done</button>${todo.label}</li>
`;
};
const appElement = document.getElementById("app");
App()(appElement);
Hey. The page with the arrow vs vue benchmark says that 500 elements in the array are being tested, but the page code says 400. It would be nice to fix this. Thanks.
Love the ideas and approach of arrow-js
. I believe this tiny thing is going to be the next big thing for many developers who are interested in committing to JS and away from JS frameworks.
I have a question if arrow-js
or html
templates have possible integration points with hyperscript's h
interface. Since h
is the underlying interface for many other JS frameworks e.g. React.createElement
, Vue.h
, Mithril.m
, and is also agnostic, being able to integrate/map with html
in some ways would open up adoption paths to arrow-js
from other frameworks.
If this should live outside of arrow-js
, is there a general direction how arrow-js.html
can be mapped to h
?
type H = <P extends object>(
tag: string,
props: P,
children: H | H[],
);
// how to map between "arrow.html <-> h"
// example html's `@click` and `@input` mappable to `props[click|onClick]` and `props[change|onChange]` based on some mapping object?
Thank you for your time.
Say I have this:
const selectedProductIds = reactive([]);
return html`
<div>
<span>Selected products: ${() => selectedProductIds.length}/${productCount}</span>
</div>
...
<input type="checkbox" ${selectedProductIds.includes(productData.internalId) ? 'checked' : ''} @input="${e => {
if (e.target.checked) {
if (!selectedProductIds.includes(productData.internalId)) {
selectedProductIds.push(productData.internalId);
}
} else {
if (selectedProductIds.includes(productData.internalId)) {
selectedProductIds.splice(selectedProductIds.indexOf(productData.internalId), 1);
}
}
selectedProductIds.length = selectedProductIds.length + 1; // <----- how can I not have to do this?
selectedProductIds.length = selectedProductIds.length - 1;
}}"/>
How can I avoid having to do the jankiness of those last two lines? If I only push to the array or splice from it, the reactive length binding doesn't update.
I have just tried the library and it's quite impressive for a such small code but I'm having an issue with list and map. We can use .key() to avoid creating new item but the problem happens when we use
return t`
<ul>
${() => state.todos.map((todo) => TodoItem(todo).key(todo.id) )}
</ul>
`;
The problem with this loop, it will rerender every list item even if we only edit one element todos[1].title
for instance.
The system could proxy the map méthod to "memo" the value and only rerun the method when the item in the array changed.
Example:
const data = reactive({
class:'css'
})
<li class="prefix-${() => data.class}-postfix" >Title</li>
renders to:
<li class="css" >Title</li>
instead of:
<li class="prefix-css-postfix" >Title</li>
notice "prefix/postfix" removed from the class attribute.
I understand that this can be refactored (to include all inside the function) but I'd expect static string before/after observable to be left unchanged.
This example is oversimplified but the idea is that template holds static values (to render even without dynamic part being set).
Hi there! From one lightweight reactive library creator to another, nice work!
Just wanted to flag a few XSS vectors not currently blocked by ArrowJS. The first two alerts show up. The third alert (in the svg) and fourth do not. The janky table format is rendered, as is the link which, if opened, runs an attack.
I'm not sure what your rendering and diffing engine looks like under-the-hood, or I'd share a fix, but if you want, feel free to peruse my source code if anything in there is useful to you.
let xss = [
`<p><img src="x" onerror="alert(1)"></p>`,
`<p>abc<iframe//src=jAva	script:alert(2)>def</p>`,
`<svg><g/onload=alert(3)//<p>`,
`<math><mi//xlink:href="data:x,<script>alert(4)</script>">`,
`<TABLE><tr><td>HELLO</tr></TABL>`,
`<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=">test</a>`
];
let template = html`${xss.map(function (elem) {
return html`${elem}`;
})}`;
template(document.querySelector('#app'));
Here is a test which shows the problem:
import { html } from '..'
describe('extressions in attribute', () => {
it('should render correctly with a single expression in an attribute', () => {
const parent = document.createElement('div')
html`<div class="${() => 'class'}" @click="${() => 'click'}">
${() => 'body'}
</div>`(parent)
expect(parent.innerHTML).toStrictEqual(`<div class="class">
body
</div>`)
})
it('should render correctly with multiple expressions in an attribute', () => {
const parent = document.createElement('div')
html`<div class="${() => 'class1'} ${() => 'class2'}">
${() => 'body'}
</div>`(parent)
expect(parent.innerHTML).toStrictEqual(
`<div class="<!--➳❍--> <!--➳❍-->">
class1
</div>`
)
})
})
Repro: https://codesandbox.io/s/blissful-napier-k5gvjy?file=/src/index.js
I often use ternary operators for conditional rendering, something like
condition ? html`option 1` : html`option 2`
I noticed that when I don't have an "option 2", I can't simply write
condition ? html`option 1` : html``
because that will throw an error when the condition changes. If the condition doesn't change, it renders fine, no matter if the condition is true
or false
.
My initial workaround was simply filling the second option with a <br>
; it fixed the issue and usually didn't cause any problems.
However, recently I also discovered that I can use null
as well, and the parent template string will render it without a hiccup, so that's probably the preferred way. You might wanna add that to the docs somewhere?
Anyway, hope the repro helps and you can fix the bug, not that the empty template string solution should be encouraged or anything, but it certainly shouldn't break the whole application either (which it currently does).
I absolutely love arrow, it's been a great experience so far and I've even built and launched a full appliation, fully powered by arrow!
Keep up the good work, and let me know if you would like to hear more about my experience with it and my suggestions for useful additions :)
I'm very excited about ArrowJS, but I've just came across benchmark, where ArrowJS is one of the slowest.
On the other hand, your performance demo clearly shows ArrowJS is much faster than Vue.
I'm kind of confused...
To Reproduce:
<div id=holder></div>
<script type="module">
import { reactive, html } from 'https://cdn.skypack.dev/@arrow-js/core';
let data = reactive({order: [1, 2, 3]});
const temp = html`${() => data.order.map(item => html`${item} `)}`;
temp(document.getElementById('holder'));
data.order[1] = data.order[1] + 100;
</script>
Expected Output
1 102 3
Actual Output
1 102 2 3
there's a split-second light mode flash before the dark mode is applied when visiting the https://www.arrow-js.com/ and also when navigating to Docs
one possible solution could be to set html's background-color
property based on @media(prefers-color-scheme: dark)
in CSS by default and then let JS take over the theming
What the title says, generally would be nice to know more about how it works
so that I can tell how it's intended to be used and what it's limitations are.
For example does data need to be "serializable" to json? Can I use Date
objects or
do I need to use a string repr for those?
Render the component on the server
I am using a library that produces an HTMLElement that is typically attached to the DOM with appendChild
, or similar. How can I include this element in a conditionally rendered template, similar to the tabs example?
I tried the following code, but the element appears to be missing when the watcher is called:
const element = thirdPartyLibrary.getElement();
function injectElement() {
if (state.selectedPage === 2) {
document.getElementById("my-element-div")!.appendChild(element as Node);
}
}
watch(injectElement);
When I change to tab 2, I get:
Uncaught TypeError: Cannot read properties of null (reading 'appendChild')
If I try to make a reactive style value, then the value of the CSS property in the style attribute is overwritten.
<!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>arrow-js</title>
</head>
<body>
<div id="app"></div>
<script type='module'>
import { reactive, html } from 'https://unpkg.com/@arrow-js/[email protected]/dist/index.min.mjs';
const state = reactive({
color: 'red'
});
html`
<button style="background-color: ${() => state.color}">click me</button>
`(document.getElementById('app'))
</script>
</body>
</html>
I tried doing this:
import { reactive, html } from 'https://esm.sh/@arrow-js/core';
const data = reactive({value: "123"})
html`
<textarea>${() => data.value}</textarea>
`(document.body);
As you may already know, it doesn't work:
However, the workaround (use the value
attribute) isn't documented in a way that would have pointed me at the correct behavior. I pieced it together from the changelog and an unrelated GitHub issue.
In templates, is there any way to distinguish plain text content from HTML content? Or does the caller need to always escape arbitrary text values?
As a contrived example, in the docs under the "Event" header, if I type <b>bold!
into the text box, bold text appears, so it seems I can insert arbitrary HTML. That's fine for hard-coded values, but seems like a security issue for anything derived from user input.
For what it's worth, I like the minimal, modern JS-based approach of ArrowJS, but this seems like a potential footgun :)
Perhaps there is an incomplete definition on ReactiveProxy... ?
import { r } from "@arrow-js/core";
type Data = {
payload: Uint8Array;
};
const data = r<Data>({
payload: new Uint8Array(),
});
data.payload = new Uint8Array();
// ^^^^^^^^^ Type 'Uint8Array' is not assignable to type 'ReactiveProxy<Uint8Array>'.
Let's say I make this reactive object:
interface State {
x?: { y: number };
}
const state = reactive<State>({x: { y: 0 }});
And then I want to observe the value of x.y
:
state.x?.$on('y', (val) => console.log(val));
I get a TypeScript error:
Property '$on' does not exist on type '{ y: number; }'
I recognize this example code won't work if the initial value of x
is undefined
, but if it's present, .$on
works fine, so it would be nice if the type system reflected this.
import { reactive } from "@arrow-js/core";
const data1 = reactive({
a: 123
});
const data2 = reactive({
b: 123
});
data2.$on('b', (value) => {
console.log(value);
})
data1.b = data2;
data1.b = 123;
Above code throw TypeError: Reflect.get called on non-object
error
Support hydration for SSR
Is it possible to enable syntax highlighting for html inside template literals in any popular editor? (VS Code, Webstorm, etc) Are there instructions on how to do so anywhere? Apologies if this is documented somewhere, I couldn't find it
It appears that IDL attribute handling in Arrow templates is broken in v1.0.0-alpha.9
. I expected the following code to call the setter for complexProp
on Test
and that logging the value in connectedCallback
would provide the correct value:
class Test extends HTMLElement {
#prop;
set complexProp(newValue) {
this.#prop = newValue;
}
connectedCallback() {
console.log(this.#prop.bar); // does not log
}
}
customElements.define("app-test", Test);
let template = html`
<app-test .complexProp="${{ foo: "something", bar: "something else" }}"></app-test>
`;
template(document.getElementById("app"));
Instead, the value is still undefined, and in the markup, it appears Arrow is treating .complexProp
as an attribute and attempting to serialize it as [Object object]
instead:
The same issue occurs if you use a class instance variable instead of a setter and if you use an arrow expression in the template instead of a static expression.
Please provide support for event helpers (à la alpine) in the template function.
Example: <div @click.outside="someHandler" @some_event.some_helper="some_handler"></div>
Thanks and keep up the good work!
Arrow will be (already is) a big deal! 🥇
Arrow looks promising. Is there any plan to build a client side router or an adapter for TankStack router TanStack/router ?
would be cool to see how arrow-js does in a keyed
implementation of krausest/js-framework-benchmark, and what that impl would look like.
The data.$off function is never explained in the documentation
Hello, I'm trying to use arrow-js (1.0.0 alpha 1) to display a table with the tagged templates, and I'm having some trouble on getting it to work, not sure if it's a bug or it's a wrong interpretation of the docs from my side. I have the following code:
import { t } from "https://cdn.jsdelivr.net/npm/@arrow-js/core";
import table from "./js/components/table.js";
let table_header = ["Name", "State"];
let table_data = [
["John", "Subscribed"],
["Ashley", "Unsubscribed"],
];
console.log(table(table_header, table_data)());
And then, in table.js
:
import { t } from "https://cdn.jsdelivr.net/npm/@arrow-js/core";
export default function table(header, rows) {
return t`
<table class="table table-hover">
<thead><tr>${header.map(column => t`<th>${column}</th>`)}</tr></thead>
<tbody>
${rows.map(columns => t`<tr>${columns.map(column => t`<td>${column}</td>`)}</tr>`)}
</tbody>
</table>`;
}
I would expect this to generate the following HTML:
<table class="table table-hover">
<thead>
<tr>
<th>Name</th>
<th>State</th>
</tr>
</thead>
<tbody>
<tr>
<td>John</td>
<td>Subscribed</td>
</tr>
<tr>
<td>Ashley</td>
<td>Unsubscribed</td>
</tr>
</tbody>
</table>
Instead, I'm getting the following, which is not correct. As you can see, the <th>
and <td>
elements show outside, and before the <table>
element:
<th>Name</th>
<th>State</th>
<td>John</td>
<td>Subscribed</td>
<td>Ashley</td>
<td>Unsubscribed</td>
<table class="table table-hover">
<thead>
<tr></tr>
</thead>
<tbody></tbody>
</table>
Is this a known issue? Am I doing something wrong?
Hi there!
I'm migrating a personal project from Vue to arrow-js, as I love the idea behind this project. I'm using webmidi and when trying to set an array of MIDIInputs to a reactive property, it throws an "Illegal Invocation" right away. The cause of that is that at some point, the reactivity makes the function "onmidimessage" of a MIDIInput to be called without the right context:
import {Message, WebMidi} from 'webmidi';
import {reactive, watch} from "@arrow-js/core";
const midi = reactive({ inputs: [] })
WebMidi.enable()
.then(() => {
midi.inputs = WebMidi.inputs
})
This doesn't happen when using Vue and its ref()
function. I think the problem here is that I don't want to make a deep reactive array, but a shallow reactive array, and only when the array is modified, it should call reactive dependencies and update the template.
This happens with native browser objects, like MIDIInput or any DOM object, because there are some functions that should not be modified or called, or because the context is lost.
A reproducible script using DOM, to make the life easy to anyone wanting to reproduce this would be:
<body>
<button>Hello</button>
<script>
const data = reactive({
buttons: []
})
data.buttons = document.querySelectorAll("button")
</script>
</body>
You can find here a reproducible stackblitz. I'd be happy to collaborate with the project in any way!
When mutating reactive data inside a watcher function, the mutation does not trigger a re-render.
Bug Reproduction: https://codepen.io/justin-schroeder/pen/zYagOQQ?editors=1111
A temporary workaround is to use a 0ms timeout to push the mutation to the next callstack:
https://codepen.io/justin-schroeder/pen/PoaErQZ?editors=1111
Consider the following modified example from arrow-js.com:
import { r, w } from "@arrow-js/core";
type Data = {
price: number | null;
quantity: number;
logTotal: boolean;
};
const data = r<Data>({
price: null,
quantity: 10,
logTotal: true,
});
function total() {
if (data.logTotal && data.price !== null) {
console.log(`Total: ${data.price * data.quantity}`);
}
}
w(total);
data.price = 35;
Executing the above yields:
TypeError: null is not an object (evaluating 'e.$on')
I see great possibilities with arrow-js. Just curious if anyone has tested using arrow-js existing js frameworks like react.
Darkmode would be nice for users reading docs.
From the docs:
However, the watcher also detects when a dependency is no longer being used by a function and turns $off tracking for those properties.
In our example, this means that when logTotal is false it will not call our total() function again until data.logTotal is set back to true.
The example function in the doc was:
function total () {
if (data.logTotal) {
console.log(`Total: ${data.price * data.quantity}`);
}
}
But what if the function was:
function total () {
if (data.logTotal == ifMathTheoremIsTrue()) {
console.log(`Total: ${data.price * data.quantity}`);
}
}
Isn't knowing when a dependency is used by an arbitrary function reducible to the Halting Problem?
So when can the watcher detect changes, and when can it not?
It switches back to "Getting Started" when scrolling past it
Maybe it has to do with how the docs contents are structured with Examples and Changelog <h1>
s being inside the <section>
instead of on the same level like in "Getting Started", assuming that's what intersection observer expects.
Boolean attributes such as the checked
attribute on a checkbox seem unable to be set by dynamic expressions.
Example:
This renders and behaves as expected when the checked
attribute is static:
html`<input type="checkbox"
${data.foo ? 'checked' : ''}
@input="${e => data.foo = e.target.checked}"
/>`
However, if we make the checked
attribute dynamic, two things go wrong:
html`<input type="checkbox"
${() => data.foo ? 'checked' : ''}
@input="${e => data.foo = e.target.checked}"
/>`
(1) It renders like this in the browser, with an unchecked checkbox followed by some mangled code as plain text. (The name of the attribute, checked
, seems to replace the value of the next attribute, @input
.)
☐ @input="checked" />
(2) The @input
event doesn't fire when the checkbox is clicked.
Workaround:
Checkboxes can be made to work by making the checked
attribute static and then manipulating the checkbox DOM element directly when the value changes, with $on
.
Here's a generalized implementation of the workaround that I've only tested with checkboxes:
function booleanHack(data, prop, attr) {
let hackAttr = `boolean-hack="${prop}"`;
data.$on(prop, () => {
document.querySelectorAll(`[${hackAttr}]`).forEach(el => {
el[attr] = data[prop];
});
});
return hackAttr;
}
html`<input type="checkbox"
${booleanHack(data, 'foo', 'checked')}
${data.foo ? 'checked' : ''}
@input="${e => data.foo = e.target.checked}"
/>`
I think 1.0.0-alpha.2
broke the click handlers. This code worked with 1.0.0-alpha.1
Now
import { t } from "@arrow-js/core";
t`
<button @click=${() => console.log('click')}>Click</button>
`(document.getElementById('app'));
renders as
<button @click="<!--➳❍--">>Click</button>
And clicking it does nothing
If this PR gets merged -> #87, the reactivity will not wrap Map, Set, WeakMap, WeakSet (which means they'll be returned without any interception)
Without it being merged I don't think they work properly inside of reactive()
, the following throws:
const data = reactive({
map: new Map()
})
data.map.size
Error in the browser (Firefox): Uncaught TypeError: get size method called on incompatible Proxy
Output in a test with the above code:
TypeError: Method get Map.prototype.size called on incompatible receiver #<Map>
❯ Object.get src/reactive.ts:208:29
206| if (Reflect.has(depProps, p)) return Reflect.get(depProps, p)
207|
208| const value = Reflect.get(...args)
| ^
209| // For any existing dependency collectors that are active, add this
210| // property to their observed properties.
Please consider adding a second parameter for the html
(template) function. This would be useful for all kinds of initial options, like, for instance,
action: append | prepend | replace | replaceInner | insertBefore | insertAfter | ...
- where append
is the (current) default action, but replaceInner
would probably be a better fit for default action (as most libs/frameworks work this way).
Example code:
...(the html initial structure)
<div class="some-container">
<span class="some-span">ABC</span>
</div>
... (the script)
html`<span class="some-span">XYZ</span>`(document.querySelector('.some-container'), {
action: 'replaceInner' // Replaces the innerHtml of .some-container with the resulting html code
});
...(should result in)
<div class="some-container">
<span class="some-span">XYZ</span>
</div>
...(instead of)
<div class="some-container">
<span class="some-span">ABC</span>
<span class="some-span">XYZ</span>
</div>
Cheers!
I have a conditionally-included template like this:
const state = reactive({ items: [], index: 0 });
...
${() => hasItem()
? html`
${() => currentItem().loaded !== false
? html`
<img src="${() => currentItem().url}"
@load="${e => onImageLoaded(e, true)}"
@error="${e => onImageLoaded(e, false)}">
${() => currentItem().loaded === null ? loadingAnimation : null}`
: html`<div style="color: red;">Item failed to load</div>`}
<div>Current rating: ${() => currentItem().rating}</div>
</div>`
: loadingAnimation}
...
function hasItem() { return state.items.length > state.index; }
function currentItem() { return hasItem() ? state.items[state.index] : null; }
state
is a reactive object with an array items
and an integer property index
. What surprises me is that sometimes I am hitting a javascript error on this line:
<div>Current rating: ${() => currentItem().rating}</div>
The problem is that currentItem()
is null. However, I would have expected this not to be called because of the hasItem()
conditional. Am I misunderstanding something about how Arrow is supposed to work with conditional templates?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.