I came to realize that I had confused the reconciler. The fix is basically to just use the key= attribute everywhere, not just in arrays.
https://codepen.io/recursive/pen/XWMLWBZ
If you could point to anything in the docs that I'm missing, I'd genuinely appreciate it.
This is definitely not idiomatic react and I can't ever think of seeing this in the wild. Just render two instances. If you want the value synced then it should be a controlled component, where the locked state is passed in as a prop.
This is the kind of stuff that's difficult for me to wrap my mind around. If I have to remember to "do it this way, but not that way", that's mental overhead. Especially when it's difficult to articulate exactly under what circumstances this problem occurs.
Obviously, there are plenty of people that have no problem with it. However, my first inclinations seem to be more likely to be those that React has problems with. I'm having a difficult time "thinking in React".
As for rendering a single instance in two places, you'd see weird behaviour trying to do the same with raw DOM element instance (well not so weird as not being able to render in two places).
I don't know what to tell you, just don't do this?
I've been working professionally for years in React and have never encountered this particular issue. Once I've gotten to to it this morning with fresh eyes it's taken me like 5min to understand - React sees this as a list of the same element, which requires keys - this on the other hand is extremely common and well documented, so even a junior could intuit it.
This is a contrived example and as a moderately experienced developer you would almost certainly reach for the idiom, which would be rendering the input as a list with a map, in which case you would see clearly what's going on, get a warning, and a clear mapping to the official docs [1]
As far as footguns go, I think this is a weak and contrived example. I get why one would not want to work with React - maybe they don't like JSX, or the lack of baked-in state management, that there are a million ways to do the same thing, the general philosophy of the framework, etc.
But I've worked with many other technologies on the front and the back end and not only have I seen infinitely worse than this, I can't think of a technology where if you try really hard to break it, you won't find ways to do so.
I eventually did figure out what's going on. I did figure out that using key= solves it. I also found I could use class components and make the three labeled instances in the constructor. That gives them a long enough lifetime to stay consistent also.
I'm not trying to tell anyone else they shouldn't use react. It's great that so many find it to be so productive.
You might say I dislike the general philosophy of react. I would rather choose whether I'm using 1-way or 2-way binding than being told. I actually have come around on JSX. I think it's pretty good now.
As for being weak and contrived, I don't know what to tell you. This honestly seems like a natural way to write this. I'm a react beginner. When I ran into this, it took me some time to figure it out, but I did eventually. I suppose another part of the philosophy I don't like is entire premise of virtual DOM and reconciliation. It creates another layer of concerns the developer needs to think about. This kind of stuff should be an implementation detail. Instead, you have to remember to use an array rather than unrolling. Or just use keys everywhere. More precisely, maybe you don't need to. But I would need to.
It seems like an identity confusion issue where the VDOM diff is ambiguous, and React resolves it in the "wrong" way. Adding keys to each `LabeledInput` resolves the issue, but I'm surprised that the runtime doesn't complain when you create the inputs without keys.
I wonder if this is why the checkbox that's checked moves, but stays in the same relative position (the second checkbox in the list): https://medium.com/@ryardley/react-hooks-not-magic-just-arra... or if it's just the ambiguous VDOM diff causing that.
const inputs = isBob ? [name, confirmation, request] : [name, request]
then it complains that there's no key prop and the issue persists. This shows it's because of confused identity. In the example, it doesn't complain because it doesn't understand that {name}{confirmation}{request} is essentially an unrolled loop. {name}{confirmation}{request}
is basically a hidden render loop. If you had something like const inputs = [name, confirmation, request]
and rendered that via inputs.map() (or just {inputs}), it would have complained about the missing key prop. React can't seem identify which state belongs to which item in the "iteration".Using a conditional render like
{nameInput}
{isBob ? confirmationInput : null}
{requestInput}
seems to avoid the issue as well, without using a key prop. So yeah, beware of hidden loops.I'll grant the particular example or edge case is not available in the public docs, but it's extremely easy to intuit what is happening and relate it to the requirement of list components needing keys.
Like I replied to OP, you would be in all likelihood using arrays anyway, instead of this contrived pattern.
class App {
constructor() {
this.message = 'Hello'
this.foo = new Foo
this.show_foo = true
}
bye() {
this.message = 'Bye'
this.show_foo = false
}
}
class AppView {
view({attrs:{app}}) {
const attrs = {
onclick: (e) => app.bye(),
}
return m('div.container', [
m('h1', attrs, 'Hello!'),
(app.show_foo
? m(FooView, {foo:app.foo}),
: null),
this.render_footer(app),
])
}
render_footer(app) {
return m(…)
}
}
window.app = new App
m.mount(document.all.root, {
view: () => m(AppView, {app}))
})Not saying that Angular's banana box is ideologically superior, just that all JS frameworks have idiosyncrasies.
Neither HTML or JS on its own will do what you're describing. HTML doesn't allow binding to a data source. JS doesn't allow you to write HTML declaratively. So, people build abstractions.
If an abstraction ever became popular enough and futureproof enough, there could be a case for supporting it natively. But I don't know of anything that currently exists and does what you're describing.
<BulletedList [(DataSource)]="MyCollectionProperty" />
If someone gave me a language that looks like Lisp but with car and cdr renamed to something else, is that really making my life easier?
JSX is not HTML because it need to blend logic and HTML seamlessly. React is not static site builder, it's an app builder. If your project doesn't need this sure go ahead. Otherwise are there much better choices?
Yes, but its a template DSL for JavaScript (and TSX is one for TypeScript), not a template DSL for HTML.
Building JSX outputs JS, not HTML.