diff --git a/README.md b/README.md
index e45df6e..b6c6613 100644
--- a/README.md
+++ b/README.md
@@ -113,6 +113,34 @@ In short, this means that simply adding data points to a trace in `data` or chan
 
 ## API Reference
 
+### usePlotly Hook
+
+As an alternative to the `Plot` component, you may use the `usePlotly` react _hook_. This provides a more powerful API with full control over the plot element, compatibility with functional components, intuitive responsive behaviour and ability to use `extendTraces`. 
+Here is a simple example of creating a chart with `usePlotly`:
+
+```jsx
+function MyChart(props) {
+   const { ref, updates, appendData } = usePlotly();
+
+  // Here is a function that will change the data. You must pass a partial Figure object (plotly DSL object) which will be
+  // merged with all previous calls to `updates`
+  const changeData = () => updates({ data: [ { y: [Math.random() * 10], type: 'scatter' } ] })
+
+ // Here we start extending traces using the `appendData` stream
+ const extendData = setInterval(() => {
+      appendData({ data: { y: [[Math.random() * 10]]}, tracePos: [0] });
+   }, 500);
+  
+   return (
+   <div> 
+      <div ref={ref}  style={{ width: '500px', height: '300px' }}/> 
+      <button onClick={changeData}>React!</button>
+      <button onClick={extendData}>Extend!</button>
+   </div>);
+}
+```
+
+
 ### Basic Props
 
 **Warning**: for the time being, this component may _mutate_ its `layout` and `data` props in response to user input, going against React rules. This behaviour will change in the near future once https://github.com/plotly/plotly.js/issues/2389 is completed.
diff --git a/package.json b/package.json
index 945785a..5d0f853 100644
--- a/package.json
+++ b/package.json
@@ -70,7 +70,9 @@
   },
   "peerDependencies": {
     "plotly.js": ">1.34.0",
-    "react": ">0.13.0"
+    "react": ">0.13.0",
+    "flyd": ">=0.2.8",
+    "ramda": ">=0.28.0"
   },
   "browserify-global-shim": {
     "react": "React"
diff --git a/src/usePlotly.js b/src/usePlotly.js
new file mode 100644
index 0000000..b087988
--- /dev/null
+++ b/src/usePlotly.js
@@ -0,0 +1,50 @@
+import { useLayoutEffect, useState, useMemo } from 'react';
+import { head, prop, compose, pick, objOf, mergeDeepRight } from 'ramda';
+import { stream, scan } from 'flyd';
+
+/**
+* A simple debouncing function
+*/
+const debounce = (fn, delay) => {
+   let timeout;
+
+   return function (...args) {
+      const functionCall = () => fn.apply(this, args);
+
+      timeout && clearTimeout(timeout);
+      timeout = setTimeout(functionCall, delay);
+   };
+};
+
+const getSizeForLayout = compose(objOf('layout'), pick(['width', 'height']), prop('contentRect'), head);
+
+export default function usePlotly() {
+   const updates = useMemo(stream, []);
+   const appendData = useMemo(stream, []);
+   const plotlyState = useMemo(
+      () => scan(mergeDeepRight, { data: [], config: {}, layout: {} }, updates),
+      []
+   );
+
+   const observer = new ResizeObserver(debounce(compose(updates, getSizeForLayout), 100));
+   const [internalRef, setRef] = useState(null);
+   useLayoutEffect(() => {
+      if (internalRef) {
+         observer.observe(internalRef);
+         const endS = plotlyState.map(state => {
+            Plotly.react(internalRef, state);
+         });
+
+         const endAppend = appendData.map(({ data, tracePos }) => Plotly.extendTraces(internalRef, data, tracePos));
+
+         return () => {
+            Plotly.purge(internalRef);
+            observer.unobserve(internalRef);
+            endAppend.end(true);
+            endS.end(true);
+         };
+      }
+   }, [internalRef, plotlyState, updates, appendData]);
+
+   return { ref: setRef, updates, appendData };
+}