React Gets Svelte Flavoured Drag and Drop (or Svelte Actions via React Hooks)

Isaac Hagoel - Oct 26 '20 - - Dev Community

TLDR: Check out the brand new library: react-dnd-action. It brings the leading drag and drop library for Svelte into React land, and mimics Svelte's "action" pattern using a custom hook. If you are interested in drag and drop or in how Svelte actions and React hooks relate to each other, continue reading.



Comparing and contrasting features in frontend frameworks (or any two solutions for the same problem) is always an interesting exercise. I still remember first learning Svelte, which happened shortly after I really got into React hooks.
Svelte has this incredible feature called "actions" (details in the official tutorial here and here).
It seems like a benign feature at first glance, but in fact it allows for amazingly elegant abstractions. For example, in the second link above you would see the longpress action. One could simply import it and write code that looks like this (a modified version of the official example for clarity):
<button use:longpress={duration}
    on:pressed={doSomethingInteresting}>
    Press and hold
</button>
Enter fullscreen mode Exit fullscreen mode

and magic... the logic for detecting long presses is fully encapsulated and abstracted away. How elegant and reusable is that?
Keen eyed React developers have probably noticed the prefix use: and recalled React hooks, which curiously use (pun intended) the same convention. Are Svelte actions and React hooks alike?

Svelte action vs. React hook

What is an action in Svelte?

The power of Svelte actions (and what makes them a unique pattern) comes from the fact that they operate outside of Svelte. You cannot use Svelte's reactivity or any of its normal syntax when implementing an action. You only have Javascript at your disposal. If you implement the API that Svelte defines for actions, you will get seamless integration with any Svelte application; but the framework doesn't impose any of its normal limitations on your code.
This may sound like a drawback or a strange design decision, but it is any library author/ tools maker's dream come true. The consumers of the library get to have all of the syntactic elegance and power that Svelte offers. The library author gets the thing they want and can leverage the most: full control and direct access to the underlying platform.
What is the API that Svelte defines for actions and that allows this win-win situation? I'm glad you asked.

  • An action is a vanilla Javascript function that takes two parameters: a DOM node to attach to, and optionally an object of options, which can include any set of properties.
  • The function can return an object with two other functions: update(options) and destroy(), which are invoked by Svelte when the options change (update) and when the host element is removed (destroy). Note that the action function is not re-run whenever the options change, only the update function gets called.
  • When the action wants to communicate with the host component, it is expected to dispatch custom events (in the code snippet above the event is called pressed). It is worth noting that even dispatching the events is done in a Vanilla way, not in the "Svelte way".

All of the above means that the action code does not depend on Svelte code whatsoever. It is just vanilla code that respects Svelte's contract for actions.

What is a hook in React?

Hooks are also functions, but unlike actions they live within the scope of React and its rendering cycle, and have access to its constructs (mostly other hooks).

  • A hook function can take any list of arguments.
  • A hook can return any value. When it returns a value, it triggers a reevaluation (re-render) of its parent component/hook. In other words, it communicates with its parent via re-running itself (which can be initiated in several ways) and returning a value.
  • When the parent element/hook re-evaluates, the entire hook function is re-executed with a fresh list of parameters. The same is true when the hook internal state is changed via setState.
  • If the hook needs to seperate initialisation logic from update logic or have different things happen in different times, built-in hooks like useRef, useMemo and useCallback are typically used.

The power of hooks comes from not being tied to the components tree. They are reusable pieces of potentially stateful and effect-full logic that work within the rendering cycle without committing to any hierarchy (unlike abstractions like higher order components for example).



We can see that a React hook is less specific in its API than a Svelte action. On the other hand, a Svelte action is less framework specific and imposes less limitations on its author.
To be fair, I am comparing apples and oranges. These are solutions to different problems: Hooks solve a general problem while actions are focused on augmenting DOM element with custom functionality.

Can A React Hook Behave Like a Svelte Action?

Since hooks are more generic than actions and assuming we are willing to give up some of Svelte's syntactic sugar, the answer would have to be yes. Let's define what a React Action might look like:

  • It is a custom hook with no return value.
  • It takes in a ref to a Dom node and an options object just like its Svelte counterpart.
  • Instead of dispatching events (which doesn't play well with React conventions or with its synthetic events system), it can accept the event-handlers as additional arguments.

This kind of hook might offer the same great developer experience that Svelte provides with its actions.
This sounds nice in theory but can it work in practice?
I wrote react-dnd-action in order to find out. All it is made of is:

  1. A custom hook that acts as an adapter for svelte-dnd-action.
  2. A flip utility to compensate for the lack of built-in flip in React.

Drag and Drop via a React "Action"

Here is what a generic "vertical or horizontal list with draggable items" looks like when using a "react action":

import React, { useRef } from "react";
import { useDndZone } from "react-dnd-action";

export function List({ items, onItemsChange, isHorizontal = false }) {
  const listRef = useRef();
  useDndZone(listRef, { items }, ({ items: newItems }) =>
    onItemsChange(newItems)
  );

  return (
    <ul className={isHorizontal ? "list horizontal" : "list"} ref={listRef}>
      {items.map((item) => (
        <li key={item.id}>{item.id}</li>
      ))}
    </ul>
  );
}

Enter fullscreen mode Exit fullscreen mode

The call to useDndZone is where the magic happens.
As we said above, it takes a ref to the container we would like to turn into a dnd zone, an object with options (in this case just the list of items data) and a callback that is updating the list of items every time a dnd event takes place.
You can play with it in codesandbox:

Not sure what you think, but I think it is quite expressive and nice.

How about a Trello-like board with draggable columns and items?

There are more examples in the README.



Even though it is a brand new library it is feature rich because it leverages everything that svelte-dnd-action has to offer.
Did I mention it supports touch and keyboard, scrolling containers and is accessible out of the box? Oh and it's only 8.5kb minified and gzipped.

Summary and Current Status

At the time of writing these lines, react-dnd-action is ready to be used but still experimental.
I have no intentions of making breaking changes to the API. There is bunch of work that still needs to be done (contributions are welcome btw): Adding examples, adding typescript type definitions, polishing out little quirks, cleaning up the logs etc.
The reason I am releasing it now is because this is an excellent time to get your feedback and thoughts.
Would you find this flavour of drag and drop useful?
What do you think about "actions" in React? Is it a pattern that makes sense to you?
Thanks for reading and happy dragging and dropping.

. . . . . . . . . . .