prefresh icon indicating copy to clipboard operation
prefresh copied to clipboard

fast refresh duplicates HOC wrapped list items

Open dashzt opened this issue 3 years ago • 4 comments

Dependancies:

 "@prefresh/babel-plugin": "^0.4.1"
 "@prefresh/webpack": "^3.2.0"
 "webpack": "^4.46.0",   
 "webpack-dev-server": "^3.11.2",
 "webpack-hot-middleware": "^2.25.0",
 "preact": "^10.5.12"

webpack is set up to use preact/compat

There are following files: HOC.jsx // This just represents generic HOC. Originally I faced this issue when using useIntl from 'react-intl' and withTheme from 'styled-components' and it can be reproduced using them as well

import React from 'react'

export const applyHOC = (Component) => (props) => <Component {...props} HOCApplied={true} />

List.jsx

import React from 'react'
import Item from './ListItem'

const items = [0, 1, 2, 3]

export const List = () => {
  return <div>{items.map(item => <Item key={`item-${item}`} index={item}/>)}</div>
}

ListItem.jsx

import React, { Component } from 'react'
import { applyHOC } from './HOC'

class ListItem extends Component {
  render() {
    return <div>item {this.props.index}</div>
  }
}

const WrappedListItem = applyHOC(ListItem)

export default WrappedListItem

With this set up whenever I try to change ListItem.jsx all items in List are duplicated. (For example I've added word 'updated' to ListItem content) image

I've also tried playing with displayName in different ways, but for me it gave no result at all

I've found that if I move wrapping ListItem with HOC to separate file this flow works fine and list items are never duplicated.

So, if I change files to be: List.jsx

import React from 'react'
import Item from './WrappedListItem'

const items = [0, 1, 2, 3]

export const List = () => {
  return <div>{items.map(item => <Item key={`item-${item}`} index={item}/>)}</div>
}

ListItem.jsx

import React, { Component } from 'react'

class ListItem extends Component {
  render() {
    return <div>item {this.props.index}</div>
  }
}

export default ListItem

WrappedListItem.jsx

import { applyHOC } from './HOC'
import Item from './ListItem'

export default applyHOC(Item)

Then I have following result doing same steps as before image

dashzt avatar Mar 22 '21 16:03 dashzt

Hey,

I've been thinking about what could be the issue here, I keep coming back to the referential identity part and this being a possible Preact child-diffing bug.

If I understand this correctly you can't HMR WrappedListItem but you can HMR ListItem with your workaround? This means that we might need to account for the case where the identity of a component changes between module-injections. Because the HOC injects a series of unnamed components.

export const applyHOC = (Component) => function Wrapper(props) { return <Component {...props} HOCApplied={true} /> }

would for instance name the returning component from applyHOC to Wrapper, no clue if this is sufficient?

JoviDeCroock avatar Mar 23 '21 10:03 JoviDeCroock

Hey.

I don't face any issues with my workaround, all components seems to hot replace fine, even if returning unnamed function from HOC. I've just added this as possible solution.

The only issue I see is duplicating components, which i've described in the first part of this issue

dashzt avatar Mar 24 '21 12:03 dashzt

So I've just tried this setup in the test/fixture/next folder and don't seem to be running into this.

Changed return <div>item {this.props.index}</div> to return <div>i{this.props.index}</div> and it worked correcly, am I missing smth?

EDIT: oh I figured it out it only happens on the second save

JoviDeCroock avatar Mar 24 '21 19:03 JoviDeCroock

I'm not entirely sure if we can build a guard for this but this seems to be because of how the update propagates through the tree, the children get duplicated after a while. Trying to figure out if I can guard for this somehow or if this is something that will be for the restructure release of Preact

JoviDeCroock avatar Mar 29 '21 20:03 JoviDeCroock