react: Bug: Iterator as JSX children doesn't work right
React version: 17
Steps To Reproduce
function App() {
const x = [<h1>a</h1>, <h2>a</h2>].values()
return <div>{x}</div>
}
Link to code example:
https://codesandbox.io/s/react-playground-forked-jv4p5?fontsize=14&hidenavigation=1&theme=dark
The current behavior
Nothing rendered.
React consumes the iterator twice so the items are gone. The first consume is to validate the JSX, the second consume is collecting them as children.
The expected behavior
Render JSX items in the list.
Why I’m considering this case is because I’m investigating how React will work with the ES Number.range proposal.
It seems like they can work in this way:
function App() {
return <>{Number.range(0, 10).map(x => <h1>{x}</h1>)}</>
}
Without appending .toArray()(iterator helper proposal) to the end. It seems like React does support iterators but used it in the wrong way.
About this issue
- Original URL
- State: open
- Created 3 years ago
- Reactions: 1
- Comments: 19 (7 by maintainers)
Can we somehow detect a one-shot iterator? Maybe some check like
x[Symbol.iterator]() === xshould warn when true?I really think it was a mistake for
.values()to behave this way when it could’ve been an iterable. (.NET got it right as usual.)At the end of the day, with a reusable iterable you can always get a one-shot iterator out of it but you can’t get a reusable out of a one-iterator except by allocating the full memory at which point the whole point of iterables are defeated.
React is a good example of a use case where you can’t use a one-shot. It’s a very common paradigm in JS.
Since you can’t use a one-shot and toArray defeats the purpose, your only option will be to use a third party user space library. If this pattern is useful enough, it’ll eventually get added to the language as the reusable iterable form.
So it seems inevitable that either there’s two APIs or just reusable iterable.
React only accepts immutable input. A one-shot iterator wouldn’t be immutable and so is not supported. Only iterables that can produce multiple iterators over the same immutable input are supported.
A user space description of why this is important might be something like this:
This component itself might rerender multiple times without the parent doing so. Therefore if you pass a one-shot iterable in to this component - rerendering wouldn’t work.
It’d suggest you explore Number.range returning an iterable that can produce multiple iterators over the same range since the input range is immutable. (I think we’d probably want to object to a collection approach that is one shot and not compatible with an immutable approach. Like you shouldn’t need to use toArray for something like this since it’s inefficient.)
If we don’t have progress on this, at least we can make this first.
A lot of time it’s more efficient to toArray at some intermediate points because it’s worth materialize the memory over reexecuting it but not all. I don’t think it’s usually when it’s passed into React itself but to an intermediate:
In this case, it might be unnecessary to materialize the initial array and intermediate array. But it needs to be multi-shot since the inner component can rerender without rerendering a parent component.
But again, it’s probably mostly best practice to materialize to an array when it passes a component boundary just because memoization is better than recomputation for updates.