react-flip-move icon indicating copy to clipboard operation
react-flip-move copied to clipboard

Workarounds to prevent known issue of array change glitching?

Open zxol opened this issue 6 years ago • 5 comments

Thanks for a great library!

In my use case, I am using FlipMove to animate a list of items up to 100 items that can be filtered down to none instantly or filtered differently, in rapid succession . As documented, this causes some weird glitches with FlipMove.

Is there anything I can do avoid this? Perhaps updating the array batchwise, with a small delay between groups? Or even delay each array update? I'm guessing that will cause a ton more render calls, would that be right?

zxol avatar Nov 05 '17 03:11 zxol

Hey there, sorry for the delay!

Yeah, so interrupts (especially when interrupts involve items being added/removed) are a very hard problem that we haven't been able to solve yet. It's something I wish I had a better answer for, but I suspect a proper fix would involve a teardown/rebuild with WAAPI, which introduces a bunch of its own issues (like very poor browser support without a hefty polyfill).

The safest approach has some unpleasant UX side-effects: if you disable the filtering while a filter is in progress, you remove the complexity of interrupts. So if your transition length is 500ms, just disable the filter options for 500ms, grey them out in the UI.

You could try the approaches you listed (like updating in batches) but I'm not hopeful about that solution. I don't know if I understand what you mean by "delay each array update", maybe you're talking about the same thing I am, with disabling interrupts? If so, I don't believe it would cause unnecessary renders - FlipMove will render every time it receives new props.

Hope it helps! Let me know what you wound up doing :)

joshwcomeau avatar Nov 13 '17 13:11 joshwcomeau

Using leaveAnimation={null} fixed it for me.

Full code:

      ...
        <tbody>
          <FlipMove
            duration={300}
            easing="ease-out"
            enterAnimation="fade"
            leaveAnimation={null}
            typeName={null}
          >
            {sortBy(items, sortPredicate).map(item => (
              <tr key={item.id} id={item.id} onClick={this.handleClick}>
                ...

ggregoire avatar Dec 14 '17 05:12 ggregoire

@ggregoire How did you come up with that! I've been struggling with duplicate elements forever, this works like a charm.

mrlubos avatar Mar 04 '18 02:03 mrlubos

@ggregoire Typesafe equivalent is to use leaveAnimation='none'.

shaunsaker avatar Apr 30 '20 11:04 shaunsaker

For posterity: There is another way to guard against this if you do want a leaveAnimation. If you add an onStartAll callback to check if the number of domNodes is unexpected, you can remount the <FlipMove> component by updating a key prop on it.

Example:

function Foo({ someList }) {
  const [overloadCounter, setOverloadCounter] = useState(1)
  const overloadTimoutRef = useRef(-1)
  const duration = 500

  // clean up the timer
  useEffect(() => () => clearTimeout(overloadTimoutRef.current))

  return (
    <FlipMove
      key={overloadCounter}
      duration={duration}
      onStartAll={(childElements, domNodes) => {
        const numDomNodes = domNodes.length
        clearTimeout(overloadTimoutRef.current)

        // check after the duration has elapsed if we have an unexpected number of DOM nodes
        overloadTimoutRef.current = window.setTimeout(() => {
          if (numDomNodes > someList.length) {
            setOverloadCounter(overloadCounter + 1)
          }
        }, duration)
      }}
    >
      {someList.map(({ id, content }) => (
        <div key={id}>{content}</div>
      ))}
    </FlipMove>
  )
}

keegan-lillo avatar Jun 22 '20 06:06 keegan-lillo