My app is a tree structure that expands/collapses, or a row can be highlighted when clicked on.
Let's the say the tree looks like this (just four rows)

...and I click on the top item. This sets that to 'current' and a render cycle is triggered. 95% of the time, the item will just be selected. But very rarely it will clone the whole tree!

At this point the app is broken. Clicking on the bottom tree will fire a click event, but select the item in the top version of the tree.
I'm guessing this is a reconciliation issue; if I look at the two DOM trees, I notice that the order of attributes is different in each.

It's not just the table, sometimes if I click on the top item the title of the site gets cloned
Preact: 7.1.0
Node: 7.1.0
Windows 10
Chrome 57
Any ideas? The site source is here if that helps.
wondering if this is a key issue? Might be worth trying it with this line removed. I'll try to look into this as soon as I can.
I have seen this same issue with stateless components. (Note axes below).
Note that I have react aliased in production build and do not experience this issue when using React in development.

import React from 'react';
import * as d3 from 'd3';
import { Motion, spring } from 'react-motion';
const XAxis = ({ xScale, yScale, label }) => {
const xRange = xScale.range();
const yRange = yScale.range();
return (
<Motion
style={{
x1: spring(xRange[0]),
x2: spring(xRange[1]),
y1: spring(yRange[0]),
// y2: spring(yRange[1]),
}}
>
{({ x1, x2, y1 }) => {
// Baseline
const baseline = (
<line
x1={x1}
x2={x2}
y1={y1}
y2={y1}
style={{ stroke: 'black' }}
/>
);
// Calculate tick values and generate line elements
const tickValues = d3.ticks(xScale.invert(x1), xScale.invert(x2), 5);
const ticks = tickValues.map((tickValue) => {
return (
<line
x1={xScale(tickValue)}
x2={xScale(tickValue)}
y1={y1 + 10}
y2={y1}
style={{ stroke: 'black' }}
key={tickValue}
/>
);
});
// Tick labels
const tickLabels = tickValues.map((tickValue) => {
return (
<text
x={xScale(tickValue)}
y={y1 + 25}
textAnchor={'middle'}
key={tickValue}
style={{ userSelect: 'none', pointerEvents: 'none' }}
>
{tickValue}
</text>
);
});
// Axis Label
const axisLabel = (
<text
x={(x2 + x1) / 2}
y={y1 + 40}
textAnchor={'middle'}
style={{ userSelect: 'none', pointerEvents: 'none' }}
>
{label}
</text>
);
return (
<g className={'axis axis--x'} >
{baseline}
{ticks}
{tickLabels}
{axisLabel}
</g>
);
}}
</Motion>
);
};
XAxis.propTypes = {
xScale: React.PropTypes.func,
yScale: React.PropTypes.func,
label: React.PropTypes.string,
};
export default XAxis;
@dyst5422 what version of preact? The values you're supplying for key seem odd - they will trigger a recycle on every diff.
I'm using version 7.1.0 of preact.
I rearranged some of my rendering and it cleared up the problem (changed keys about a bit too).
My understanding is that it should still be garbage collecting those elements which dont exist on a rerender (those whose keys dont match the previous set). So while I may have resolved the particular issue I was having, it seems to still have some issues in that regard. Mind you, I know very little of the internals of how all that works.
I'll include the new code which resolved this in case it is of any help.
import React from 'react';
import * as d3 from 'd3';
import { Motion, TransitionMotion, spring } from 'react-motion';
const NUM_TICKS = 5;
const XAxis = ({ xScale, yScale, label }) => {
const xRange = xScale.range();
const yRange = yScale.range();
const x1 = xRange[0];
const x2 = xRange[1];
const y1 = yRange[0];
const tickValues = d3.ticks(xScale.invert(x1), xScale.invert(x2), NUM_TICKS);
const styles = tickValues.map((tickValue, i) => {
return ({
key: (i + 1).toString(),
data: tickValue,
style: {
xVal: spring(xScale(tickValue)),
},
});
});
return (
<g className={'axis axis--x'} >
<Motion
style={{
springx1: spring(x1),
springx2: spring(x2),
springy1: spring(y1),
// y2: spring(yRange[1]),
}}
>{({ springx1, springx2, springy1 }) => {
return (
<g className={'react-motion-transition-group'}>
<line
x1={springx1}
x2={springx2}
y1={springy1}
y2={springy1}
style={{ stroke: 'black' }}
/>
<text
x={(springx2 + springx1) / 2}
y={springy1 + 40}
textAnchor={'middle'}
style={{ userSelect: 'none', pointerEvents: 'none' }}
>
{label}
</text>
</g>
);
}}
</Motion>
<TransitionMotion
styles={styles}
>{(interpolatedStyles) => {
return (
<g className={'react-motion-transition-group'}>
{interpolatedStyles.map(({ key, data, style }) => {
return (
<g className={'tick'} key={key}>
<line
x1={style.xVal}
x2={style.xVal}
y1={y1 + 10}
y2={y1}
style={{ stroke: 'black' }}
key={-key}
/>
<text
x={style.xVal}
y={y1 + 25}
textAnchor={'middle'}
key={key}
style={{ userSelect: 'none', pointerEvents: 'none' }}
>
{data}
</text>
</g>
);
})}
</g>
);
}}
</TransitionMotion>
</g>
);
};
XAxis.propTypes = {
xScale: React.PropTypes.func,
yScale: React.PropTypes.func,
label: React.PropTypes.string,
};
export default XAxis;
@dyst5422 hmm - interesting, so it seemed like Motion being a child of XAxis was causing a problem. That gives me somewhere to start!
Update: the rewritten Pure Functional Components support in Preact should fix this!
Fixed in 8.
Nice one!
Most helpful comment
Update: the rewritten Pure Functional Components support in Preact should fix this!