How Can I convert React.createclass to Class Component?

Tags: , , , ,



I have taken an example of dragging certain div in react js from here http://jsfiddle.net/Af9Jt/2/

Now it is in createClass and I need to convert it into class Draggable extends React.Component in order to export it into another component. Here is code

APP.JS

import React from 'react';
import './App.css';
import Draggable from './Draggable.js';

function App() {
  return (
    <React.Fragment>
        <Draggable />
    </React.Fragment>
  );
}

export default App;

Draggable.js

import React from 'react';

export class Draggable extends React.Component{
    constructor(props) {
        super(props);
        this.state = {
            pos: {x: 0, y: 0},
            dragging: false,
            rel: null
        };

        this.onMouseMove = this.onMouseMove.bind(this);
        this.onMouseDown = this.onMouseDown.bind(this);
        this.onMouseUp = this.onMouseUp.bind(this);
    }


    // we could get away with not having this (and just having the listeners on
    // our div), but then the experience would be possibly be janky. If there's
    // anything w/ a higher z-index that gets in the way, then you're toast,
    // etc.
    // componentDidUpdate(props, state) {
    //   if (this.state.dragging && !state.dragging) {
    //     document.addEventListener('mousemove', this.onMouseMove)
    //     document.addEventListener('mouseup', this.onMouseUp)
    //   } else if (!this.state.dragging && state.dragging) {
    //     document.removeEventListener('mousemove', this.onMouseMove)
    //     document.removeEventListener('mouseup', this.onMouseUp)
    //   }
    // }
  
    // calculate relative position to the mouse and set dragging=true
    onMouseDown(e) {
      console.log("1")
      console.log(this.state);
      if (e.button !== 0) return
      this.setState({
        dragging: true,
        rel: {
          x: e.pageX - e.nativeEvent.offsetX,
          y: e.pageY - e.nativeEvent.offsetY
        }
      })
      e.stopPropagation()
      e.preventDefault()
    }

    onMouseUp(e) {
      this.setState({dragging: false})
      e.stopPropagation()
      e.preventDefault()
    }

    onMouseMove(e) {
      if (!this.state.dragging) return
      this.setState({
        pos: {
          x: e.pageX - this.state.rel.x,
          y: e.pageY - this.state.rel.y
        }
      })
      e.stopPropagation()
      e.preventDefault()
    }

    render() {
        return(
            <div 
            style={{position: "absolute", left: "175px", top: "65px", border: "2px solid rgb(170, 170, 85)", padding: "10px"}}
            className="my-draggable" data-reactid=".r[2zxee]" id="messi" 
            onMouseDown={this.onMouseDown}
            onMouseUp={this.onMouseUp}
            onMouseDown={this.onMouseDown}
            initialPos = {{x:0,y:0}}
            >
                Drag Me! See how children are passed through to the div!
            </div>
        )
    }
}


export default Draggable;

Everything runs fine in this code the box is shown but I cannot drag the div, I couldn’t figure out what issue is this. How Can I Solve this?

Here is my sample code in jsfiddle

https://jsfiddle.net/6vdurk79/3/

Answer

There were a few things I noticed when converting this into a React.Component:

  1. You never used the this.state.pos when rendering, so even if the position changed in the variables, it wouldn’t move the div. The style attribute of the <div> is just hard-coded with { left: "175px", top: "65px" }
  2. You didn’t properly get the position of the mouse in your this.onMouseDown function, which caused it to forced every movement to be at the corner.
  3. You never bound this.onMouseMove to anything. Uncommenting the big chunk of commented out code fixed this.
  4. The initialPos attribute you place inside the <div> does absolutely nothing. I converted that into a prop in the constructor.

Here’s the updated JSFiddle link: https://jsfiddle.net/ogy4xd1c/3/

And I’ll embed it here on StackOverflow in a snippet.

class Draggable extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      pos: props.initialPos || {
        x: 0,
        y: 0
      },
      dragging: false,
      rel: null
    }

    this.onMouseMove = this.onMouseMove.bind(this);
    this.onMouseDown = this.onMouseDown.bind(this);
    this.onMouseUp = this.onMouseUp.bind(this);
  }

  // calculate relative position to the mouse and set dragging=true
  onMouseDown(e) {
    if (e.button !== 0) return
    const de = document.documentElement;
    const box = ReactDOM.findDOMNode(this).getBoundingClientRect();
    const top = box.top + window.pageYOffset - de.clientTop;
    const left = box.left + window.pageXOffset - de.clientLeft;
    this.setState({
      dragging: true,
      rel: {
        x: e.pageX - left,
        y: e.pageY - top,
      }
    })
    e.stopPropagation()
    e.preventDefault()
  }

  onMouseUp(e) {
    this.setState({
      dragging: false
    })
    e.stopPropagation()
    e.preventDefault()
  }

  onMouseMove(e) {
    if (!this.state.dragging) return
    this.setState({
      pos: {
        x: e.pageX - this.state.rel.x,
        y: e.pageY - this.state.rel.y
      }
    })
    e.stopPropagation()
    e.preventDefault()
  }

  componentDidUpdate(props, state) {
    if (this.state.dragging && !state.dragging) {
      document.addEventListener('mousemove', this.onMouseMove)
      document.addEventListener('mouseup', this.onMouseUp)
    } else if (!this.state.dragging && state.dragging) {
      document.removeEventListener('mousemove', this.onMouseMove)
      document.removeEventListener('mouseup', this.onMouseUp)
    }
  }

  render() {
    return ( <div
      style={{
            position: "absolute",
            left: this.state.pos.x,
            top: this.state.pos.y,
            border: "2px solid rgb(170, 170, 85)",
            padding: "10px"
        }}
        className="my-draggable"
        data-reactid=".r[2zxee]"
        id="messi"
        onMouseDown={this.onMouseDown}
        className="my-draggable"
      >
        Drag Me! See how children are passed through to the div!
      </div>
    )
  }
}

ReactDOM.render(<Draggable initialPos={{ x: 50, y: 20 }} />, document.querySelector("#root"));
.my-draggable {
  cursor: pointer;
  width: 200px;
  height: 200px;
  background-color: #cca;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="root"></div>

If you want to pass in children, you can do that too with this modified version: https://jsfiddle.net/hceLjz90/

class Draggable extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      pos: props.initialPos || {
        x: 0,
        y: 0
      },
      dragging: false,
      rel: null
    }

    this.onMouseMove = this.onMouseMove.bind(this);
    this.onMouseDown = this.onMouseDown.bind(this);
    this.onMouseUp = this.onMouseUp.bind(this);
  }

  // calculate relative position to the mouse and set dragging=true
  onMouseDown(e) {
    if (e.button !== 0) return
    const de = document.documentElement;
    const box = ReactDOM.findDOMNode(this).getBoundingClientRect();
    const top = box.top + window.pageYOffset - de.clientTop;
    const left = box.left + window.pageXOffset - de.clientLeft;
    this.setState({
      dragging: true,
      rel: {
        x: e.pageX - left,
        y: e.pageY - top,
      }
    })
    e.stopPropagation()
    e.preventDefault()
  }

  onMouseUp(e) {
    this.setState({
      dragging: false
    })
    e.stopPropagation()
    e.preventDefault()
  }

  onMouseMove(e) {
    if (!this.state.dragging) return
    this.setState({
      pos: {
        x: e.pageX - this.state.rel.x,
        y: e.pageY - this.state.rel.y
      }
    })
    e.stopPropagation()
    e.preventDefault()
  }

  componentDidUpdate(props, state) {
    if (this.state.dragging && !state.dragging) {
      document.addEventListener('mousemove', this.onMouseMove)
      document.addEventListener('mouseup', this.onMouseUp)
    } else if (!this.state.dragging && state.dragging) {
      document.removeEventListener('mousemove', this.onMouseMove)
      document.removeEventListener('mouseup', this.onMouseUp)
    }
  }

  render() {
    return ( <div
      style={{
            position: "absolute",
            left: this.state.pos.x,
            top: this.state.pos.y,
            border: "2px solid rgb(170, 170, 85)",
            padding: "10px"
        }}
        className="my-draggable"
        data-reactid=".r[2zxee]"
        id="messi"
        onMouseDown={this.onMouseDown}
        className="my-draggable"
      >
        {this.props.children}
      </div>
    )
  }
}

ReactDOM.render(<Draggable initialPos={{ x: 50, y: 20 }}>
    <h1>This is a child element</h1>
    <p>This is also a child element</p>
</Draggable>, document.querySelector("#root"))
.my-draggable {
  cursor: pointer;
  width: 200px;
  height: 200px;
  background-color: #cca;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="root"></div>


Source: stackoverflow