I've set up this JS fiddle to demonstrate the behavior I'm seeing.
Basically when I render this code:
<section>
{ phone && !App.isValidPhoneNumber(phone) ? <div>Invalid Phone Number</div> : null }
<input value={phone} onInput={this.linkState('phone')} />
{ /* Weird: if you remove the line below, there's no error... */ }
<div>Phone: {phone}</div>
</section>
As soon as the state changes such that the first line's condition becomes true, the input loses focus. Is this a bug or expected behavior?
Update: Added GIF to illustrate behavior.

Update 2: I've noticed a pattern now, that this only happens if there's two other elements of the same type. e.g. if there's two div tags (like in the example above), this doesn't happen if one of them is changes to a strong tag, but happens again if both are. Wondering if it's some sort of recycling issue...? Still not sure why it would affect the input.
Seems like it's moving the input, which the DOM treats as a remove+add.
Verified that by adding a key to the div before the input, the input no longer loses focus.
Is this something that will be "fixed", or just the nature of how it works?
Wow, this took me an eternity to get to, but I finally was able to reproduce (and fix) the related issue I was having in React-Select in a Plunker. I fixed it by adding a key property to the Select-placeholder element.
See both the broken & fixed versions here: Plunker
Interesting. I am happy keys fixes this, but I want to better understand why the diff is removing an element there. Will be looking into it :)
Thanks! Appreciate your help with this, btw :) And your work on Preact in general, which is 馃挴
@developit
but I want to better understand why the diff is removing an element there. Will be looking into it :)
It is because react uses positions relative to all other values as implicit keys. So when you normalizing children array and removing null/boolean values, their positions are changing.
It is a major bug, because code patterns like condition ? <A> : null are everywhere in react-like codebases, and right now preact doesn't preserve internal state in cases like this: http://codepen.io/localvoid/pen/EZBpoz
@localvoid Indeed, I think I need to remove the pruning from h() and instead handle that in the diff. Shouldn't be particularly difficult.
I am not sure how you do diffing right now, but I think that nested array flattening will also cause similar problems, and nested arrays are also quite common in react apps.
I've tried some experiments with moving all normalization stuff to diff algo, and everything just gets way much more complicated. I've solved this problem by assigning node positions as keys to all nodes that doesn't have any explicit keys. And to prevent collision of keys, I also assign a flag that indicates if node is using explicit key or positional key.
@localvoid seems reasonable, plus it's easier to assign those keys when flattening than it would be during diff.
Confirmed this is fixed in Preact 8! 馃槉

You rock! Can't wait for 8.0!
I can't wait for it to be done 馃槀
Fixed in 8.
This was happening to me because I was appending Math.random() to the key.
Using preact 8 this is still happening . Unless I am mistaken? Thanks for checking. The key= fix works.

Still happening to me too on preact 10.0.1
Hi @mfbx9da4. I upgraded your codesandbox to Preact 10.0.1 and it seems to work now (input keeps focus after submitting the form): https://codesandbox.io/s/vibrant-mclaren-n3sgr Any chance upgrading to Preact X is an option for you?
@stephanebisson I upgraded the original repro in the description of this issue to Preact X (https://codesandbox.io/s/loving-bird-yz8l3) and it seems to work for me. Do you have a repro of the problem you are seeing with 10.0.1?
And did you try using keys? It might mitigate the issue
This is still an issue for me with Preact 10.3.1.
Here's a minimal repro:
https://codesandbox.io/s/friendly-water-meywq
It seems that any component that returns an <input> as the top-level element triggers this problem - unwrap the <Input/> component in this example and everything is fine.
Adding a key to either the <input> element in the component, or to the <Input/> instance itself, does not solve the issue. (and shouldn't be necessary for an only child anyhow.)
@mindplay-dk creating components inside a render function is component-thrashing (/debug) warns for this. You effectively mount and remount said input all the time due to it being a new reference on every render
import { render } from "preact";
import { useState } from "preact/hooks";
const Input = ({ state, setState }) => (
<input
type="email"
placeholder="e-mail"
value={state.email}
onInput={e => setState({ email: e.target.value })}
/>
);
const App = () => {
const [state, setState] = useState({ email: "" });
return (
<div>
<p>Test: {state.email}</p>
<Input state={state} setState={setState} />
</div>
);
};
render(<App />, document.getElementById("root"));
This is the right solution, given your sandbox.
Most helpful comment
Confirmed this is fixed in Preact 8! 馃槉