Back to posts

Dragging files into the whole React application

Dragging files into the whole application

One of these days, I was helping a co-work and friend of mine, Chen Jin, to solve an interesting use-case for drag and drop files into the application we were building.

At Living Sky Technologies, the mission is to make content creation as easy as possible creating a great writing experience. That also involves collecting users' ideas that are going to be used (or not) in the writing document. So, in the product, we have a right pane (also called idea board) that is responsible for holding all the users' ideas. Everything that sparks light to the users goes there, including images, yeah, we have an Add images button to allow importing images.

Add image button

To increase the user experience when adding images to the idea pane, the design team made a design capturing when the user starts dragging a file anywhere on the page so, everything becomes blurred except for the idea pane. With this approach, it is clear to the user that after dropping the file it is going to be placed in that box (the idea pane) that is highlighted (you can have an idea by looking at this blog post cover image).

We jumped right into the implementation. It seemed simple, HTML has a very clear and robust API for drag and drop. The plan was simple, when the user is dragging over an upper-level div element, we add a className modifier (for example container--dragging) to style the blur and the drop-zone according to the design. When the event dragLeave was triggered we just remove the class name. Hands-on code, all done. Here is the React hook (yes, we love React Hooks) with this functionality:

Everything seemed to work fine except for the fact an annoying issue came up. When dragging over and moving the file around it started blinking the page, switching between the two states. After some debugging we noticed the onDragLeave and onDragOver were getting triggered several times.

Blinking issue

After some Googling around we noticed this behaviour happened because the user was dragging over and leaving out of the children elements, so, in order to avoid the children elements being targets for the drag and drop events we need to add the pointer-events: none; to them when the drag action is in place.

The issue still persisted! But, we noticed if adding that CSS hack to the block element (in the .container instead of .container--dragging) the issue was temporarily solved (nothing on the page was clickable anymore, it needs a better fix). The blinking page when dragging happens because, by the time the user starts dragging over, we call setIsDragging which will then re-render the page and add the .container--dragging class name (that has the CSS hack applied) but, the user is already dragging over children elements and the onDragLeave was already being triggered. Oh no, we found a racing condition 😱.

The solution is quite simple but it took us some time to figure it out. We just need to apply the onDragLeave if the user is leaving the container element and void it when leaving the children elements. We add an id property to the main element, drag-and-drop-container, and we added a check to the onDragLeave to only apply it if leaving the element that contains that id property.

With this approach, the drag-and-drop experience is smooth and feels good.