+      
+        {ITEMS.map(n => (
+          
+        ))}
+      
+    
+  );
+}
+```
 
 ### Easing Example
 
@@ -92,7 +119,7 @@ export default App(props) {
   );
 }
 ```
-Note: You should replace the original `div` you would like to make scrollable with the `ScrollingComponent`. 
+Note: You should replace the original `div` you would like to make scrollable with the `ScrollingComponent`.
 
 ### Virtualized Example
 
diff --git a/package-lock.json b/package-lock.json
index 25de415..ec58da7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
       "version": "1.2.4",
       "license": "MIT",
       "dependencies": {
+        "defaults": "^1.0.4",
         "hoist-non-react-statics": "3.x",
         "lodash.throttle": "^4.1.1",
         "prop-types": "15.x",
@@ -2585,6 +2586,14 @@
         "wrap-ansi": "^7.0.0"
       }
     },
+    "node_modules/clone": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+      "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/clone-deep": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
@@ -2768,6 +2777,17 @@
       "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
       "dev": true
     },
+    "node_modules/defaults": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
+      "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
+      "dependencies": {
+        "clone": "^1.0.2"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/define-properties": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
@@ -7619,6 +7639,11 @@
         "wrap-ansi": "^7.0.0"
       }
     },
+    "clone": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+      "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="
+    },
     "clone-deep": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
@@ -7765,6 +7790,14 @@
       "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
       "dev": true
     },
+    "defaults": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
+      "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
+      "requires": {
+        "clone": "^1.0.2"
+      }
+    },
     "define-properties": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
diff --git a/package.json b/package.json
index 5368d4f..2f2b33d 100644
--- a/package.json
+++ b/package.json
@@ -43,19 +43,20 @@
   ],
   "license": "MIT",
   "dependencies": {
+    "defaults": "^1.0.4",
     "hoist-non-react-statics": "3.x",
     "lodash.throttle": "^4.1.1",
     "prop-types": "15.x",
     "raf": "^3.4.1"
   },
   "devDependencies": {
-    "@node-loader/babel": "^2.0.1",
     "@babel/cli": "^7.17.6",
     "@babel/core": "^7.17.8",
     "@babel/eslint-parser": "^7.17.0",
     "@babel/preset-env": "^7.16.11",
     "@babel/preset-react": "^7.16.7",
     "@babel/register": "^7.17.7",
+    "@node-loader/babel": "^2.0.1",
     "chai": "^4.3.6",
     "eslint": "^8.12.0",
     "eslint-config-airbnb": "^19.0.4",
diff --git a/src/index.js b/src/index.js
index bffe4dc..d8fe51a 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,7 +1,7 @@
-import React, { Component } from 'react';
+import React, { useEffect, useRef, useCallback, useContext } from 'react';
 import PropTypes from 'prop-types';
+import defaults from 'defaults';
 import { DndContext } from 'react-dnd';
-import { findDOMNode } from 'react-dom';
 import throttle from 'lodash.throttle';
 import raf from 'raf';
 import hoist from 'hoist-non-react-statics';
@@ -56,194 +56,211 @@ export const defaultHorizontalStrength = createHorizontalStrength(DEFAULT_BUFFER
 
 export const defaultVerticalStrength = createVerticalStrength(DEFAULT_BUFFER);
 
-export default function createScrollingComponent(WrappedComponent) {
-  class ScrollingComponent extends Component {
-    static displayName = `Scrolling(${getDisplayName(WrappedComponent)})`;
-
-    static propTypes = {
-      onScrollChange: PropTypes.func,
-      verticalStrength: PropTypes.func,
-      horizontalStrength: PropTypes.func,
-      strengthMultiplier: PropTypes.number
-    };
-
-    static defaultProps = {
-      onScrollChange: noop,
-      verticalStrength: defaultVerticalStrength,
-      horizontalStrength: defaultHorizontalStrength,
-      strengthMultiplier: 30
-    };
-
-    static contextType = DndContext;
-
-    constructor(props, ctx) {
-      super(props, ctx);
-
-      this.scaleX = 0;
-      this.scaleY = 0;
-      this.frame = null;
-
-      this.attached = false;
-      this.dragging = false;
-    }
-
-    componentDidMount() {
-      this.container = findDOMNode(this.wrappedInstance);
-      this.container.addEventListener('dragover', this.handleEvent);
-      // touchmove events don't seem to work across siblings, so we unfortunately
-      // have to attach the listeners to the body
-      window.document.body.addEventListener('touchmove', this.handleEvent);
+const defaultProps = {
+  onScrollChange: noop,
+  verticalStrength: defaultVerticalStrength,
+  horizontalStrength: defaultHorizontalStrength,
+  strengthMultiplier: 30,
+  dragDropManager: null
+};
+
+export function useDndScrolling(componentRef, passedOptions) {
+  const props = defaults(passedOptions, defaultProps);
+  const scaleX = useRef(0);
+  const scaleY = useRef(0);
+  const frame = useRef(0);
+  const attached = useRef(false);
+  const dragging = useRef(false);
+
+  const { dragDropManager: dragDropManagerCtx } = useContext(DndContext);
+  const dragDropManager = props.dragDropManager ?? dragDropManagerCtx;
+
+  if (!dragDropManager) {
+    throw new Error(
+      'Unable to get dragDropManager from context. Provide DnDContext or pass dragDropManager via options to useDndScrolling.'
+    );
+  }
 
-      this.clearMonitorSubscription = this.context.dragDropManager
-        .getMonitor()
-        .subscribeToStateChange(() => this.handleMonitorChange());
-    }
+  const monitor = React.useMemo(() => dragDropManager.getMonitor(), [dragDropManager]);
 
-    componentWillUnmount() {
-      this.container.removeEventListener('dragover', this.handleEvent);
-      window.document.body.removeEventListener('touchmove', this.handleEvent);
-      this.clearMonitorSubscription();
-      this.stopScrolling();
-    }
+  const startScrolling = React.useCallback(() => {
+    let i = 0;
+    const tick = () => {
+      const { strengthMultiplier, onScrollChange } = props;
 
-    handleEvent = evt => {
-      if (this.dragging && !this.attached) {
-        this.attach();
-        this.updateScrolling(evt);
+      // stop scrolling if there's nothing to do
+      if (strengthMultiplier === 0 || scaleX.current + scaleY.current === 0) {
+        // eslint-disable-next-line no-use-before-define
+        stopScrolling();
+        return;
       }
-    };
-
-    handleMonitorChange() {
-      const isDragging = this.context.dragDropManager.getMonitor().isDragging();
 
-      if (!this.dragging && isDragging) {
-        this.dragging = true;
-      } else if (this.dragging && !isDragging) {
-        this.dragging = false;
-        this.stopScrolling();
+      // there's a bug in safari where it seems like we can't get
+      // mousemove events from a container that also emits a scroll
+      // event that same frame. So we double the strengthMultiplier and only adjust
+      // the scroll position at 30fps
+      if (i++ % 2) {
+        const {
+          scrollLeft,
+          scrollTop,
+          scrollWidth,
+          scrollHeight,
+          clientWidth,
+          clientHeight
+        } = componentRef.current;
+
+        const newLeft = scaleX.current
+          ? // eslint-disable-next-line no-param-reassign
+            (componentRef.current.scrollLeft = intBetween(
+              0,
+              scrollWidth - clientWidth,
+              scrollLeft + scaleX.current * strengthMultiplier
+            ))
+          : scrollLeft;
+
+        const newTop = scaleY.current
+          ? // eslint-disable-next-line no-param-reassign
+            (componentRef.current.scrollTop = intBetween(
+              0,
+              scrollHeight - clientHeight,
+              scrollTop + scaleY.current * strengthMultiplier
+            ))
+          : scrollTop;
+
+        onScrollChange(newLeft, newTop);
       }
-    }
-
-    attach() {
-      this.attached = true;
-      window.document.body.addEventListener('dragover', this.updateScrolling);
-      window.document.body.addEventListener('touchmove', this.updateScrolling);
-    }
+      frame.current = raf(tick);
+    };
 
-    detach() {
-      this.attached = false;
-      window.document.body.removeEventListener('dragover', this.updateScrolling);
-      window.document.body.removeEventListener('touchmove', this.updateScrolling);
-    }
+    tick();
+  }, []);
 
-    // Update scaleX and scaleY every 100ms or so
-    // and start scrolling if necessary
-    updateScrolling = throttle(
-      evt => {
-        const {
-          left: x,
-          top: y,
-          width: w,
-          height: h
-        } = this.container.getBoundingClientRect();
-        const box = { x, y, w, h };
-        const coords = getCoords(evt);
-
-        // calculate strength
-        this.scaleX = this.props.horizontalStrength(box, coords);
-        this.scaleY = this.props.verticalStrength(box, coords);
-
-        // start scrolling if we need to
-        if (!this.frame && (this.scaleX || this.scaleY)) {
-          this.startScrolling();
-        }
-      },
-      100,
-      { trailing: false }
-    );
+  // Update scaleX and scaleY every 100ms or so
+  // and start scrolling if necessary
+  const updateScrolling = React.useMemo(
+    () =>
+      throttle(
+        evt => {
+          if (!componentRef.current) {
+            return;
+          }
 
-    startScrolling() {
-      let i = 0;
-      const tick = () => {
-        const { scaleX, scaleY, container } = this;
-        const { strengthMultiplier, onScrollChange } = this.props;
-
-        // stop scrolling if there's nothing to do
-        if (strengthMultiplier === 0 || scaleX + scaleY === 0) {
-          this.stopScrolling();
-          return;
-        }
-
-        // there's a bug in safari where it seems like we can't get
-        // mousemove events from a container that also emits a scroll
-        // event that same frame. So we double the strengthMultiplier and only adjust
-        // the scroll position at 30fps
-        if (i++ % 2) {
           const {
-            scrollLeft,
-            scrollTop,
-            scrollWidth,
-            scrollHeight,
-            clientWidth,
-            clientHeight
-          } = container;
-
-          const newLeft = scaleX
-            ? (container.scrollLeft = intBetween(
-                0,
-                scrollWidth - clientWidth,
-                scrollLeft + scaleX * strengthMultiplier
-              ))
-            : scrollLeft;
-
-          const newTop = scaleY
-            ? (container.scrollTop = intBetween(
-                0,
-                scrollHeight - clientHeight,
-                scrollTop + scaleY * strengthMultiplier
-              ))
-            : scrollTop;
-
-          onScrollChange(newLeft, newTop);
-        }
-        this.frame = raf(tick);
-      };
-
-      tick();
+            left: x,
+            top: y,
+            width: w,
+            height: h
+          } = componentRef.current.getBoundingClientRect();
+          const box = { x, y, w, h };
+          const coords = getCoords(evt);
+
+          // calculate strength
+          scaleX.current = props.horizontalStrength(box, coords);
+          scaleY.current = props.verticalStrength(box, coords);
+
+          // start scrolling if we need to
+          if (!frame.current && (scaleX.current || scaleY.current)) {
+            startScrolling();
+          }
+        },
+        100,
+        { trailing: false }
+      ),
+    []
+  );
+
+  const attach = useCallback(() => {
+    attached.current = true;
+    window.document.body.addEventListener('dragover', updateScrolling);
+    window.document.body.addEventListener('touchmove', updateScrolling);
+  }, []);
+
+  const detach = useCallback(() => {
+    attached.current = false;
+    window.document.body.removeEventListener('dragover', updateScrolling);
+    window.document.body.removeEventListener('touchmove', updateScrolling);
+  }, []);
+
+  const stopScrolling = React.useCallback(() => {
+    detach();
+    scaleX.current = 0;
+    scaleY.current = 0;
+
+    if (frame.current) {
+      raf.cancel(frame.current);
+      frame.current = null;
     }
+  }, []);
 
-    stopScrolling() {
-      this.detach();
-      this.scaleX = 0;
-      this.scaleY = 0;
-
-      if (this.frame) {
-        raf.cancel(this.frame);
-        this.frame = null;
+  const handleEvent = useCallback(
+    evt => {
+      if (dragging.current && !attached.current) {
+        attach();
+        updateScrolling(evt);
       }
+    },
+    [dragDropManager]
+  );
+
+  useEffect(() => {
+    if (!dragging.current && monitor.isDragging()) {
+      dragging.current = true;
+    } else if (dragging.current && !monitor.isDragging()) {
+      dragging.current = false;
+      stopScrolling();
     }
+  }, [monitor.isDragging()]);
 
-    render() {
-      const {
-        // not passing down these props
-        strengthMultiplier,
-        verticalStrength,
-        horizontalStrength,
-        onScrollChange,
-
-        ...props
-      } = this.props;
-
-      return (
-