preactjs / preact

⚛️ Fast 3kB React alternative with the same modern API. Components & Virtual DOM.
https://preactjs.com
MIT License
36.78k stars 1.96k forks source link

Diff doesn't render anything. #2496

Closed GordonCode25 closed 2 years ago

GordonCode25 commented 4 years ago

I'm trying out the latest version of preact UMD but I'm struggling with an issue I didn't seem to have before where components don't render every other time they are replaced. Here is an example?

Component

class P extends Component {

    constructor(props) {
        super(props);
    }

    render() {
        var test = this.props.test;
        var list=[];
        list.push(<p key={test}>{test}</p>);
        return <div>{list}</div>;
    }

}

This script runs client side (Simulating loading a new page)

var root = document.getElementById("root");
preact.render(preact.createElement(P,{test:"A"}), root,root.firstChild);
preact.render(preact.createElement(P,{test:"B"}), root,root.firstChild);

The rendered content gets loaded into this root div.

<div id="root"><div className="seo">SEO text...</div></div>

Note: I'm aware of using => for loops, it's just a coding pattern I use for complex lists.

andrewiggins commented 4 years ago

Hi 👋

Try returning list directly from P.render() instead of {list}. Currently the code above is returning an object with a single list property. A Component's render() method should return a preact VNode (the value returned from calling createElement) or a list of VNodes (like you are doing here).

See if that helps :)

GordonCode25 commented 4 years ago

Sorry, thought I'd updated that example. I'm testing with <div>{list}</div>

and it's the same issue. It just removed the divs from the example markup for some reason.

GordonCode25 commented 4 years ago

Also note, if I add multiple items to the list, it still doesn't work.

JoviDeCroock commented 4 years ago

This works for me https://codesandbox.io/s/headless-dew-s44bm?file=/src/index.js

GordonCode25 commented 4 years ago

So the problem I'm having is the diff. It works first time, but second time it's blank.

preact.render(preact.createElement(P,{test:"A"}), root,root.firstChild); 
preact.render(preact.createElement(P,{test:"B"}), root,root.firstChild);
JoviDeCroock commented 4 years ago

Oh yes, This is a current limitation of replaceNode a workaround you can use is wrapping the <p>{test}</p> in a <Fragment key={test} or just make it into it's own component. In the replaceNode process it's hard to reconcile keyed native nodes.

GordonCode25 commented 4 years ago

ok, sounds like you've seen this issue before. Can you explain that a little more clearly or point me at some documentation?

I need to wrap something in a fragment with a key? What do I need to wrap, why?

GordonCode25 commented 4 years ago

A little background, I'm maintaining a pretty huge app, I've just updated the version of preact and I'm seeing blank bits appearing all over the place. This example is just for demonstration.

marvinhagemeister commented 4 years ago

@GordonCode25 If you're just updating from Preact 8.x there is a high chance you can just remove the third argument to render(), see this page in our docs.

GordonCode25 commented 4 years ago

I think removing the last argument works but wouldn't that re-render my entire page?

Also, would be nice to understand where I'm going wrong.

JoviDeCroock commented 4 years ago

So the issue presents itself because we use a key on the p tag, we enter the render cycle with the tag inside of excessDomChildren and in oldChildren our diffElementNodes sees that we can reuse this paragraph but does not remove it from oldChildren meaning we'll unmount it later in the cycle.

This means that if we wrap this in a function like

const Text = ({ content }) => <p>{content}</p>
const App = ({ test }) => {
  const list = [<Text key={test} content={test} />]
  return <div>{test}</div>
}

// OR

const App = ({ test }) => {
  const list = [<Fragment key={test}><p>{test}</p></Fragment>]
  return <div>{test}</div>
}
GordonCode25 commented 4 years ago

ok, thanks guys. Appreciate the help.

GordonCode25 commented 4 years ago

Further note on this, still couldn't get it to work but I eventually managed to find a workaround.

I created a Root component and used an event to update the the state on the Root component rather than calling render again.

developit commented 4 years ago

This seems similar to the issue we were having with replaceNode in htm/preact. Removing that parameter fixed the issue, but it makes me wonder if something is awry still.

JoviDeCroock commented 2 years ago

Closing as inactive