Preact: vdom manipulation, confusion ..

Created on 13 Sep 2018  路  6Comments  路  Source: preactjs/preact

https://stackblitz.com/edit/js-o4semn
Code and inspected result would tell the problem.
But I'll try to explain.
I'm trying to achieve , a table component, which creates rows and cols automatically with given children like:

<MTable>
    <th>x</th>
    <th>Test</th>
    <th>Test</th>

    <input snr type="text" />
    <input type="text" />
    <input type="text" />
</MTable>

As you might see in the code in the link above, in render of the MTable I'm traversing children, if there isn't a row ( tr ) or a "snr -> short for start new row" prop adding it, and continue.
The thing that confuses me is , some extra -unwanted- rows in inputs ( you could see them with react dev tools or chrome dev tools ) appear , this breaks code in more complex scenarios .
bgxx

I may made a mistake in render code - if so forgive me - but if code is correct there must be something wrong about vnodes.

For completeness I'm adding component code :

const T = (p) => {
  const FieldTypes = ['input',];
  let currentrow = null;
  let thcurrentrow = null;
  let thead = null;

  let els = p.children.reduce(
    (res, child) => {
      let nodeName = getNodeName(child);
      let ca = child.attributes || {};
      if (FieldTypes.includes(nodeName)) {
        if (!currentrow || ca.snr) {
          currentrow = <tr ><td>{child}</td></tr>
          res.push(currentrow);
        }
        else if (currentrow) {
          currentrow.children.push(<td>{child}</td>);
        }
        return res;
      }
      else if (nodeName == "th") {
        if (!thead) {
          thead = <thead></thead>;
          res.push(thead);
        }
        if (!thcurrentrow) {
          thcurrentrow = <tr >{child}</tr>
          thead.children.push(thcurrentrow);
        }
        else if (thcurrentrow) {
          thcurrentrow.children.push(child);
        }
        return res; // added hope to fix, probably unnecessary ..
      }
      return res;
    }, []);

  return (

    <table>{els}</table>
  );
};

const getNodeName = c => c.nodeName ? (c.nodeName.name ? c.nodeName.name.toLowerCase() : c.nodeName.toLowerCase()) : "";

I've tried same -almost ( had to change some thing because of reacts dictation ) - code with react, and it worked. So I can conclude that the logic in render code is correct and this is a bug about vdom.

https://stackblitz.com/edit/react-73igvk?file=MTable.js

rct

question

All 6 comments

I did some debugging , in MTable's children inputs don't have any children at the start of reduce function,
after th related ops ( pushing them to new array ), inputs started to have children ( first row ), during reduce somehow inputs -children array- are mutated ( couldn't follow into preact's source -don't have proper debug env for that-, ) but when I clone children array , before reduce function , code works as expected.
(At line 11 updated code)
What mutates child in reduce func. I'm not sure, it probably happens inside h func. , ..
Again, I would love to check preact's code but it's not an easy task for me at the moment.

debug session video:
https://www.youtube.com/watch?v=QIKC-7sT4xQ&feature=youtu.be

another test about reduce logic:
https://stackblitz.com/edit/js-mldm9c

React is just ignoring children of <input> because the DOM doesn't accept them.

It seems like you're inadvertently mutating VNodes in-place, which is going to produce unexpected results.

If you need to replace the children or props/attributes of an element (child), use cloneElement():

import { cloneElement } from 'preact';

child = cloneElement(child, null, newChildren);

It's best to do so with a fully constructed array of children, rather than mutating the element's children in-place.

@developit
Don't mean to hijack the thread, but I was not aware that Preact offered cloneElement. Is that API documented anywhere? Or is it the same as React's? What's particularly interesting to me is how ref is handled if a child is wrapped in an HOC that also needs a ref.

@lewischa It's the same api like for react: https://reactjs.org/docs/react-api.html#cloneelement

@developit ,
I don't know how to explain myself at the moment ( about the issue ).
The reason I wanted to use preact is I don't like the reacts opinion enforcement.
I'm pretty sure, something not right about the code (preacts) but can't go any further to find out.
Thanks for checking.

@developit ,
I also would like to note: There aren't any in-place modifications. ( You probably haven't checked things carefully which I provide ) - debug session, etc.-. Reducer traverses the component's children, and returns a new array of children. In reducer, there is not any modification real children.

If you check debug session, when I push a child into el. , real children (component I traverse) vdom getting mutated (which I did not intend to do so), it pushes (and its children) as child to every input (input is one sample, there may be a span, etc which could have children).

Could we say adding children into th is an in-place modification, since it added to another array, not sure ?
Also, I did not want to add extra comments, issues, but if you try attached React code with preact - I mean mods. I made to make test work with react-, another issue appears as stack overflows ( probably same line ( push th to children ).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Zashy picture Zashy  路  3Comments

marcosguti picture marcosguti  路  3Comments

matthewmueller picture matthewmueller  路  3Comments

youngwind picture youngwind  路  3Comments

jasongerbes picture jasongerbes  路  3Comments