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

Unexpected flickering only of some children in draggable #2470

Closed
felixmpaulus opened this issue Jan 3, 2023 · 4 comments
Closed

Unexpected flickering only of some children in draggable #2470

felixmpaulus opened this issue Jan 3, 2023 · 4 comments

Comments

@felixmpaulus
Copy link

Check your console

Console logs nothing regarding this package.

Similar issues that did not help me:

#2305
#304
#1060

What version of React are you using?

"react": "^17.0.2"

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

"react-beautiful-dnd": "^13.1.0"

What browser are you using?

Version 108.0.5359.124 (Official Build) (arm64)

Demo

ezgif-4-bd9d8ec129

I spend 3 hours on this now and am getting nowhere...
Any help would be greatly appreciated!! :)

  • What really confuses me is how only the 2 checkboxes flicker, but not the text or the icon.
  • The list rerenders only once after reorder (asynchronously when the state is updated)
  • No external requests happen in the background, the app stores the change only in the local state (it needs to be manually saved by the user to be pushed to the database).
  • I suspect the asynchronous nature of useState to be the issue. But the fact that only those 2 checkboxes flicker contradicts the suspicion in my opinion.
  • The draggableId is updated after each reorder to the index of each element.

Here is the code:

My reorder function that is called onDragEnd:

function reorderElements({ source, destination }: any) {
    if (!destination) {
      return;
    }
    setAppState((oldAppState: AppState | null) => {
      if (oldAppState) {
        const { content } = oldAppState;
        const copy = JSON.parse(JSON.stringify(content));
        const [elementToMove] = copy.splice(source.index, 1);
        copy.splice(destination.index, 0, elementToMove);
        const elementsWithNewIndex = copy.map(
          (e: BannerElement, i: number) => ({
            ...e,
            id: "" + i,
          })
        );
        return { ...oldAppState, content: elementsWithNewIndex };
      }
      return null;
    });
  }
The list component
<DragDropContextContainer onDragEnd={onDragEnd}>
        <DroppableContainer direction="horizontal" droppableId="root">
          {(provided: DroppableProvided) => (
            <div
              style={{ display: "flex" }}
              ref={provided.innerRef}
              {...provided.droppableProps}
              className="draggable-container-custom"
            >
              <>
                {content.map((element, index) => (
                  <Element
                    key={index}
                    index={index}
                    element={element}
                    updateElement={updateElement}
                    deleteElement={deleteElement}
                    provided={provided}
                    nElements={nElements}
                  />
                ))}
                {provided.placeholder}
              </>
            </div> 
          )}
        </DroppableContainer>
      </DragDropContextContainer>
DragDropContextContainer
export function DragDropContextContainer({ children, ...props }: Props) {
  return <DragDropContext {...props}>{children}</DragDropContext>;
}
DroppableContainer
export function DroppableContainer({ children, ...props }: Props) {
  return <Droppable {...props}>{children}</Droppable>;
}
Each Element
<DraggableContainer draggableId={id} index={index} key={index}>
      {(provided: DraggableProvided, snapshot: DraggableStateSnapshot) => {
        const draggableStyle = snapshot.isDragging
          ? { ...customDraggableStyle, ...provided.draggableProps.style }
          : provided.draggableProps.style;
        return (
          <div
            ref={provided.innerRef}
            {...provided.draggableProps}
            style={draggableStyle}
            className="draggable-custom"
          >
            <div
              {...provided.dragHandleProps}
              className="element-drag-icon-container"
            >
              <div className="element-drag-icon">
                <PolarisIcon source={DragHandleMinor} color={"base"} />
              </div>
            </div>
            <div className="element-container">
              <div className="element-icon-container">
                <Checkbox
                  label="Text has Icon Prefix"
                  checked={hasIcon}
                  onChange={(newValue) => {
                    updateElement({ id, hasIcon: newValue });
                  }}
                />
                <Icon
                  disabled={!hasIcon}
                  key={JSON.stringify(icon)}
                  id={id}
                  elementIcon={icon}
                  updateElement={updateElement}
                />
              </div>
              <div className="element-text-container">
                <TextField
                  label="Set your Text:"
                  multiline={3}
                  value={text}
                  onChange={(text: ElementText) => {
                    updateElement({ id, text });
                  }}
                  autoComplete="off"
                />
              </div>
              <div className="element-bottom-row">
                <Checkbox
                  label="Show on mobile"
                  checked={showMobile}
                  onChange={() => {
                    updateElement({ id, showMobile: true });
                  }}
                />
                  <Button
                    plain
                    destructive
                    onClick={() => {
                      deleteElement(id);
                    }}
                  >
                    delete
                  </Button>
              </div>
            </div>
          </div>
        );
      }}
    </DraggableContainer>
  );
DraggableContainer
export function DraggableContainer({ children, ...props }: Props) {
  return <Draggable {...props}>{children}</Draggable>;
}

Those container-Components exist to get rid of some ESLint errors.

@felixmpaulus
Copy link
Author

Anyone? This issue is keeping me from the next release. Would really appreciate any input!

@maximilianorly
Copy link

maximilianorly commented Jan 5, 2023

@felixmpaulus Where is hasIcon defined? Can you show me this code? Is it a state variable like useState?

It seems to be only the cards that have been reordered which have the flicker bug, possibly due to re-render of children below Draggable. I think it's more likely to be the behaviour of MUI Checkbox, maybe their defaultChecked state, than a DnD issue. Could you Memoize hasIcon, or the checkbox itself, so that it persists even on component reload?

@felixmpaulus
Copy link
Author

@maximilianorly Thank you!
hasIcon is nested deep inside the state for each element, so it is updated like the other values (showMobile, text, icon).

You gave me the idea to replace one of the checkboxes with a classic html <input type="checkbox" checked={hasIcon}/> and the flickering is gone.

I'll go from here and post my findings once I find a sufficient solution! :)

@felixmpaulus
Copy link
Author

felixmpaulus commented Jan 24, 2023

This can be closed.
I think the issue was with the checkboxes, I just replaced them with <input/> tags and did not invest more time into it.

FYI I was not using MUI but Shopify's Polaris.

Thanks for any help ❤️

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

No branches or pull requests

3 participants
@felixmpaulus @maximilianorly and others