Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I target DOM elements inside a react component, or should I avoid targeting DOM elements all together?

Tags:

dom

reactjs

I've created a script that activates on mouseover on a parent container and should move it's child elements away from the mouse. I've currently got it working but there are some parts of the code that seem to contradict what REACT code should look like. Especially two parts.

  1. I'm using a counter in the render function so that each span gets it's correct custom property from state.customProperties which is an array that updates the custom properties on mouseover of the parent element.

    render() {
        let counter = 0;
        const returnCustomProp = function(that) {
            counter++;
            let x = 0;
            if (that.state.customProperties[counter - 1]) {
                x = that.state.customProperties[counter - 1].x;
            }
            let y = 0;
            if (that.state.customProperties[counter - 1]) {
                y = that.state.customProperties[counter - 1].y;
            }
            return "customProperty(" + x + " " + y + ")";
        }
        return (
            <div onMouseMove={this._testFunction} id="ParentContainer">
                    <section custom={returnCustomProp(this)}>
                        <b>Custom content for part 1</b>
                        <i>Could be way different from all the other elements</i>
                    </section>
                    <section custom={returnCustomProp(this)}>
                        2
                    </section>
                    <section custom={returnCustomProp(this)}>
                        <div>
                            All content can differ internally so I'm unable to create a generic element and loop trough that
                        </div>
                    </section>
                    <section custom={returnCustomProp(this)}>
                        4
                    </section>
                    <section custom={returnCustomProp(this)}>
                        5
                    </section>
                    <section custom={returnCustomProp(this)}>
                        <div>
                            <b>
                                This is just test data, the actualy data has no divs nested inside secions
                            </b>
                            <h1>
                                6
                            </h1>
                        </div>
                    </section>
                    <section custom={returnCustomProp(this)}>
                        7
                    </section>
                    <section custom={returnCustomProp(this)}>
                        8
                    </section>
                </div>
        );
    }
    
  2. In the mousemove function I'm using document.getElementById and querySelectorAll to get all the section elements and compare the mouse coordinates from the mouse to the section elements coordinates.

    var mouseX = e.pageX;
    var mouseY = e.pageY;
    var spans = document.getElementById('ParentContainer').querySelectorAll('section');
    var theRangeSquared = 10 * 10;
    var maxOffset = 5;
    var newCustomProperties = [];
    for (var i = 0; i < spans.length; i++) {
        var position = spans[i].getBoundingClientRect();
        var widthMod = position.width / 2;
        var heightMod = position.height / 2;
        var coordX = position.x + widthMod;
        var coordY = position.y + heightMod + window.scrollY;
        // Distance from mouse
        var dx = coordX - mouseX;
        var dy = coordY - mouseY;
        var distanceSquared = (dx * dx + dy * dy);
        var tx = 0,
            ty = 0;
        if (distanceSquared < theRangeSquared && distanceSquared !== 0) {
            // Calculate shift scale (inverse of distance)
            var shift = maxOffset * (theRangeSquared - distanceSquared) / theRangeSquared;
            var distance = Math.sqrt(distanceSquared);
            tx = shift * dx / distance;
            ty = shift * dy / distance;
        }
        newCustomProperties.push({
            x: tx,
            y: ty
        });
    }
    

I have a feeling I'm going about this all wrong. I'm not sure how I could avoid the counter while keeping a generic returnCustomProp function to return the properties for said element (in the live code I've got about 200 of these elements so setting the array item number for them manually is not efficient).

The second part feels hacky to target an element by ID that is inside the actual component. I feel like I should be able to target this without traversing trough the DOM. Referencing the section elements could be a solution, but I believe references should be kept to a minimum and as stated, the actual code consists of hundreds of these sections.

JSFIDDLE

The code does not do much more atm than update the custom="customProperty(0 0)" property. You can see this happen trough the element inspector on mouseover.

Can I make this functionality work withouth having to count the <section> elements inside the render function and without having to use document.querySelectorAll?

like image 416
timo Avatar asked Dec 03 '19 08:12

timo


People also ask

Which method is used to add components or elements to the DOM in React?

AppendChild. The simplest, most well-known method of appending an element to the DOM is certainly the appendChild() .

How does Reactjs handle DOM manipulation?

React will update the entire Virtual DOM. It'll then compare the Virtual DOM before updating, with the one after updating, to identify what objects have been changed. It uses the Diffing Algorithm. Only the changed objects will get updated on the Real DOM.

Which function is used to create DOM elements with React?

When the ref attribute is used on an HTML element, the ref created in the constructor with React. createRef() receives the underlying DOM element as its current property. When the ref attribute is used on a custom class component, the ref object receives the mounted instance of the component as its current .


3 Answers

How do I target DOM elements inside a react component?

As per React's official docs, you can access the dom elements by using refs. You have to create a ref by using the React.createRef() call.

Should I avoid targeting DOM elements altogether?

Traversing the dom to fetch a particular dom element is not a good practice with React as it will cause a performance issue. However, react allows an alternative way to do the same by using createRef().

Can I make this functionality work without having to count the elements inside the render function and without having to use document.querySelectorAll?

Yes, consider the following steps to implement this:

Inside the constructor, create a ref for parentContainer like this:

  this.ParentContainer=React.createRef();

Then use parentContainer ref in render:

    <div onMouseMove={this._testFunction} id="ParentContainer" 
      ref={this.ParentContainer} >

Inside test component, use this.parentContainer as:

//replace this 
//var spans = document.getElementById('ParentContainer').querySelectorAll('section');
//with this
  var spans = this.parentContainer.current.childNodes;

You can check it here

EDIT

Any thoughts on how I can work around having to use the let counter inside the render function

You can define returnCustomProp outside render like this: (Here you will need to pass the index of each section instead of a this reference)

    returnCustomProp = (index)=> {
      let x = 0;
      if(this.state.customProperties[index]) {
          x = this.state.customProperties[index].x;
      }
      let y = 0;
      if(this.state.customProperties[index]) {
          y = this.state.customProperties[index].y;
      }
      return "customProperty("+x+" "+y+")";
    }

And use it with section like this:

   <section custom={returnCustomProp(0)}>
            <b>Custom content for part 1</b>
            <i>Could be way different from all the other elements</i>
        </section>
        <section custom={returnCustomProp(1)}>
            2
        </section>
        <section custom={returnCustomProp(2)}>
            <div>
                All content can differ internally so I'm unable to create a generic element and loop trough that
            </div>
        </section>
        <section custom={returnCustomProp(3)}>
            4
        </section>
        <section custom={returnCustomProp(4)}>
            5
        </section>
        <section custom={returnCustomProp(5)}>
            <div>
                <b>
                    This is just test data, the actual data has no divs nested inside sections
                </b>
                <h1>
                    6
                </h1>
            </div>
        </section>
        <section custom={returnCustomProp(6)}>
            7
        </section>
        <section custom={returnCustomProp(7)}>
            8
        </section>
like image 185
Jatin Parmar Avatar answered Nov 05 '22 01:11

Jatin Parmar


You should not manipulate DOM directly because react process on virtual dom. According to React doc you should use Ref forwarding. For more detail please read this.

like image 31
Masih Jahangiri Avatar answered Nov 05 '22 02:11

Masih Jahangiri


You can use the method findDOMNode() of ReactDOM if you does not find other solution but how the documentation said:

findDOMNode is an escape hatch used to access the underlying DOM node. In most cases, use of this escape hatch is discouraged because it pierces the component abstraction.

As example I want to share a use case of my app where I need the reference to the container DOM div to implement the drag&drop for the libraries that I was using.

Example:

import React, { forwardRef } from 'react'
import _ from 'lodash'
import { List } from 'office-ui-fabric-react'
import { Draggable } from 'react-beautiful-dnd'
import ReactDOM from 'react-dom'

export const BasicItemList = forwardRef((
  {
    items,
    onClickItem,
    innerRef,
    ...rest
  }, ref) => {
  const itemToCell = (i, idx) => {
    return (
      <Draggable draggableId={id} index={idx} key={idx}>
        {
          (provided, snapshot) => {
            return (
              <MyCell item={i}
                      onClick={onClickItem}
                      innerRef={provided.innerRef}
                      isDragging={snapshot.isDragging}
                      {...provided.dragHandleProps}
                      {...provided.draggableProps}
              />
            )
          }
        }
      </Draggable>
    )
  }
  const refGetter = (comp) => {
    const div = ReactDOM.findDOMNode(comp)
    if (_.isFunction(ref)) {
      ref(comp)
    } else if (ref) {
      ref.current = comp
    }
    if (_.isFunction(innerRef)) {
      innerRef(div)
    }
  }

  return (
    <List items={items}
          onRenderCell={itemToCell}
          componentRef={refGetter}
          {...rest}
    />
  )
})
like image 32
93sauu Avatar answered Nov 05 '22 03:11

93sauu