Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stuttering/Flickering issue #1060

Closed
cooperfrench95 opened this issue Jan 19, 2019 · 13 comments
Closed

Stuttering/Flickering issue #1060

cooperfrench95 opened this issue Jan 19, 2019 · 13 comments

Comments

@cooperfrench95
Copy link

Bug or feature request?

Bug

Expected behavior

Draggables slot in to their new positions smoothly after dropping, without any flickering or stuttering.

Actual behavior

When a draggable is dropped, there is a split second where it seems like perhaps the state is still updating, and during that split second, if the item is an image, the old item in that position is displayed. If the item is a block of text, then during that split second it just kind of moves slightly (and I notice that the styles change in the console). I notice that this is worse the more draggables are in the list, and particularly the more image draggables that are in the list. I have some images and some text blocks in my list of draggables.

I am using redux elsewhere in my application, but I'm not using it in this file. I'm not firing off any API requests or anything like that either.

The console shows no errors or warnings.

Steps to reproduce

Here is my js file:

import React from 'react';
import { Droppable, Draggable, DragDropContext } from 'react-beautiful-dnd';
import { baseURL } from '../shared/baseURL';
import Markdown from 'markdown-to-jsx';

export default class DragContext extends React.Component {

    constructor(props) {
        super(props)

        this.alterState = this.alterState.bind(this);        
        this.onDragEnd = this.onDragEnd.bind(this);
        this.state = {
            content: this.props.draft.data.content
        }
    }

    alterState (item, originalLocation, desiredLocation) {
        var newState = this.state.content.slice(0); // Get a copy of the state
        newState.splice(originalLocation, 1);
        newState.splice(desiredLocation, 0, item)
        return newState;
    }

    onDragEnd (result) {
        if (!result.destination) {
            return;
        }
        else if (result.destination.droppableId === result.source.droppableId && result.destination.index === result.source.index) {
            return;
        }
        else {
            // reorder the list
            this.setState({ content: this.alterState(this.state.content[result.source.index], result.source.index, result.destination.index) });
        }
    }

    render() {
        return(

            <DragDropContext onDragEnd={this.onDragEnd}>
                <Droppable droppableId={'droppable1'}>
                    {(provided) => 
                        <div 
                            ref={provided.innerRef}
                            {...provided.droppableProps}
                        >   
                            {this.state.content.map((item, index) => {

                                if (item.type === 'img') {
                                    return(
                                        <Draggable draggableId={(index + 10000).toString()} index={index} key={index + 20000}>
                                        {(provided) =>
                                            <div style={{ backgroundColor: "white", ...provided.draggableProps.style}} ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} >
                                                <img className="img-fluid" style={{ height: "200px", position: "relative", left: "50%", transform: "translate(-50%)"}} src={baseURL + 'images/' + item.image} alt={item.image.slice(0, -4)}></img>
                                            </div>
                                        }
                                        </Draggable>
                                    );
                                }
                                else if (['pell', 'p', 'em', 'strong'].includes(item.type)) {
                                    return(
                                        <Draggable draggableId={(index + 10000).toString()} index={index} key={index + 20000}>
                                        {(provided) => 
                                            <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
                                                <Markdown>{item.content}</Markdown>
                                            </div>
                                        }
                                        </Draggable>
                                    );
                                }
                                else {
                                    return(
                                        <Draggable draggableId={(index + 10000).toString()} index={index} key={index + 20000}>
                                        {(provided) => 
                                            <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
                                            </div>
                                        }
                                        </Draggable>
                                    );
                                }
                            })
                            
                            }
                        
                            {provided.placeholder}

                        </div>
                    }
                </Droppable>
            </DragDropContext>

        );
    }
}

What version of React are you using?

16.6.3

What version of react-beautiful-dnd are you running?

10.0.3

What browser are you using?

Firefox 64

Demo

peek 2019-01-19 21-09

@cooperfrench95
Copy link
Author

I have noticed that this issue is non existent on Chromium 71. I have not tried regular Google Chrome but I assume it is the same.

@cooperfrench95
Copy link
Author

cooperfrench95 commented Jan 19, 2019

Okay, so, edit number 2. Strangely, I have discovered that when I close my development tab, then re-open it and try to reproduce the issue, I am unable to. But if I subsequently refresh the page, the issue is there again. To make that more clear:

Open firefox -> go to localhost:3000/ -> issue is not present -> refresh page -> issue is present

The same is true for Chrome, only it's not refreshing the tab that causes the issue to start happening, it is entering responsive mode. Refreshing on chrome seems to fix the issue.

@cooperfrench95
Copy link
Author

cooperfrench95 commented Jan 19, 2019

Looking futher into this I have realised that the reason this is happening is that after refreshing the firefox tab, each time the state is updated, a GET request for the image(s) that had their positions moved in the list is fired off, and whilst the GET request is waiting to be fulfilled, I think the images display their alts.

get requests

@alexreardon
Copy link
Collaborator

Okay sure. So it sounds like an image loading issue. Is there anything you would like me to look into? It sounds like an image preload issue

@wrsx
Copy link

wrsx commented Apr 26, 2019

@cooperfrench95 Did you end up finding a workaround for this? I'm getting this issue as well, causing my images to jump exactly like in your example. It's happening for me in Safari/Firefox, and works fine in Chrome.

@cooperfrench95
Copy link
Author

cooperfrench95 commented Apr 26, 2019 via email

@alexreardon
Copy link
Collaborator

I would be happy to add a guide if somebody creates a good solution for this! I will stay away from fiddling myself for now

@devias-io
Copy link

Hey guys! I would suggest using React.memo to stop the re-rendering of the image. It did the job in my case.

@alexreardon
Copy link
Collaborator

See #1311

@maximilianorly
Copy link

maximilianorly commented Jan 5, 2023

Hey guys! I would suggest using React.memo to stop the re-rendering of the image. It did the job in my case.

I second this! In my use-case, I want my ui to be indicative of server state. My items where flashing in their new positions onDragEnd, reverting whilst API processes, then the component would reload with the updated server state. Removing my content from view whilst changes are applying improved the UX.

NOTE: I'm using RTKQuery and on successful mutation, in this case useMyUpdateItemsMutation, the cache is invalidated and my reliant component reloads, causing data to change, thus the code inside my useMemo is executed.

const myComponent = () => {
  const [isApplyingRearrangement, setIsApplyingRearrangement] = useState(true);
  const { data, isLoading, isSuccess, isError etc... } = useMyItemsQuery();
  const [updateItemsOrder, { isSuccess: isUpdating, isError: isUpdateError etc... }] = useMyUpdateItemsMutation();

  // Withold active spinner until "data" changes, check success of request and, if successful, dis-enable spinner
  useMemo(() => {
      if (isSuccess) {
	  setIsApplyingRearrangement(false);
      }
  }, [data]);


  // onDragEnd method to rearrange and updateItemsOrder()
  const myOnDragEndMethod = () => {
    // enabling spinner
    setIsApplyingRearrangement(true);


    // My rearrangement code here


    // useMyUpdateItemsMutation hook call, which will invalidate RTKQuery tags and update the cache, causing my component to reload
    updateItemsOrder(myRearrangedItems)
  }

  return (
    { 
      isLoading || isApplyingRearrangment && <MySpinner />
    }
    {
      !isApplyingRearrangment && isSuccess && data
      <DragDropContext onDragEnd={myOnDragEndMethod} >
        _droppable and draggable items here_
      <DragDropContext />
    }
  )
}

Thanks for pointing me in the right direction @devias-io

@prionkor
Copy link

I am having the same issue but it is not with images. So I think this is an issue that persists still. This is what probably happening:

  1. User drags and drops.
  2. The animation starts and ends.
  3. After end of animation handleDragEnd is called.
  4. At the initial dragend function state is still not changed so previous list is shown.
  5. After changing state new state is reflected.

The flickering happens between 4 & 5.

@MarkLovesCoding
Copy link

I am having the same issue but it is not with images. So I think this is an issue that persists still. This is what probably happening:

  1. User drags and drops.
  2. The animation starts and ends.
  3. After end of animation handleDragEnd is called.
  4. At the initial dragend function state is still not changed so previous list is shown.
  5. After changing state new state is reflected.

The flickering happens between 4 & 5.

Hi, have you had any luck discovering a solution?

@makaratzisiordanis
Copy link

makaratzisiordanis commented Jul 1, 2024

Hello. I had the same issue.

Drop the item and it would look normal
Trigger the state to change on the onDrag function
The Dropable would do a rerender with the old state
The state would change and the items would be in the correct order.
So the reason the items would flicker is because the Dropable would do this rerender

The Solution i did was to create a variable on this js file to hold the state immediately so that on step 3 it will have the latest changes. So instead of rendering the state do this instead
1)use a varialbe ouside of the component
let stateHelper= [];

2)inside the component add
stateHelper= state;

  1. onDragEnd when you set the state do this as well
    setState(newArray)
    stateHelper=newArray //set the final value immediately

  2. then inside the Droppable use stateHelper and not state like this.
    stateHelper.map(()=>{
    return <Draggable...>})
    //here is your code

I'm not sure if this will work if this component is multiple times in the same page since the stateHelper will be common for all the components.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants