344

How do I select certain bars in react.js?

This is my code:

var Progressbar = React.createClass({
    getInitialState: function () {
        return { completed: this.props.completed };
    },
    addPrecent: function (value) {
        this.props.completed += value;
        this.setState({ completed: this.props.completed });
    },

    render: function () {

        var completed = this.props.completed;
        if (completed < 0) { completed = 0 };


        return (...);
    }

I want to use this React component:

var App = React.createClass({
    getInitialState: function () {

        return { baction: 'Progress1' };
    },
    handleChange: function (e) {
        var value = e.target.value;
        console.log(value);
        this.setState({ baction: value });
    },
    handleClick10: function (e) {
        console.log('You clicked: ', this.state.baction);
        document.getElementById(this.state.baction).addPrecent(10);
    },
    render: function () {
        return (
            <div class="center">Progress Bars Demo
            <Progressbar completed={25} id="Progress1" />
                <h2 class="center"></h2>
                <Progressbar completed={50} id="Progress2" />
                <h2 class="center"></h2>
                <Progressbar completed={75} id="Progress3" />
                <h2 class="center"></h2>
                <span>
                    <select name='selectbar' id='selectbar' value={this.state.baction} onChange={this.handleChange}>
                        <option value="Progress1">#Progress1</option>
                        <option value="Progress2">#Progress2</option>
                        <option value="Progress3">#Progress3</option>
                    </select>
                    <input type="button" onClick={this.handleClick10} value="+10" />
                    <button>+25</button>
                    <button>-10</button>
                    <button>-25</button>
                </span>
            </div>
        )
    }
});

I want to execute the handleClick10 function and perform the operation for my selected progressbar. But the result I get is:

 You clicked:  Progress1
 TypeError: document.getElementById(...) is null

How do I select the certain Element in react.js?

Waleed Iqbal
  • 1,308
  • 19
  • 35
user504909
  • 9,119
  • 12
  • 60
  • 109

9 Answers9

293

You can do that by specifying the ref

EDIT: In react v16.8.0 with function component, you can define a ref with useRef. Note that when you specify a ref on a function component, you need to use React.forwardRef on it to forward the ref to the DOM element of use useImperativeHandle to to expose certain functions from within the function component

Ex:

const Child1 = React.forwardRef((props, ref) => {
    return <div ref={ref}>Child1</div> 
});

const Child2 = React.forwardRef((props, ref) => {
    const handleClick= () =>{};
    useImperativeHandle(ref,() => ({
       handleClick
    }))
    return <div>Child2</div> 
});
const App = () => {
    const child1 = useRef(null);
    const child2 = useRef(null);

    return (
        <>
           <Child1 ref={child1} />
           <Child1 ref={child1} />
        </>
    )
}

EDIT:

In React 16.3+, use React.createRef() to create your ref:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}

In order to access the element, use:

const node = this.myRef.current;

DOC for using React.createRef()


EDIT

However facebook advises against it because string refs have some issues, are considered legacy, and are likely to be removed in one of the future releases.

From the docs:

Legacy API: String Refs

If you worked with React before, you might be familiar with an older API where the ref attribute is a string, like "textInput", and the DOM node is accessed as this.refs.textInput. We advise against it because string refs have some issues, are considered legacy, and are likely to be removed in one of the future releases. If you're currently using this.refs.textInput to access refs, we recommend the callback pattern instead.

A recommended way for React 16.2 and earlier is to use the callback pattern:

<Progressbar completed={25} id="Progress1" ref={(input) => {this.Progress[0] = input }}/>

<h2 class="center"></h2>

<Progressbar completed={50} id="Progress2" ref={(input) => {this.Progress[1] = input }}/>

  <h2 class="center"></h2>

<Progressbar completed={75} id="Progress3" ref={(input) => {this.Progress[2] = input }}/>

DOC for using callback


Even older versions of react defined refs using string like below

<Progressbar completed={25} id="Progress1" ref="Progress1"/>

    <h2 class="center"></h2>

    <Progressbar completed={50} id="Progress2" ref="Progress2"/>

      <h2 class="center"></h2>

    <Progressbar completed={75} id="Progress3" ref="Progress3"/>

In order to get the element just do

var object = this.refs.Progress1;

Remember to use this inside an arrow function block like:

print = () => {
  var object = this.refs.Progress1;  
}

and so on...

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • 5
    Facebook advises against this approach. See here https://facebook.github.io/react/docs/refs-and-the-dom.html – Dmitry Feb 10 '17 at 16:18
  • 4
    @dmitrymar As I can see the above page is written for v15.4.2 onwards and I guess, this was not there earlier when I wrote the answer. Anyways edited the answer with the correct approach – Shubham Khatri Feb 13 '17 at 03:56
  • 2
    @MattSidor, thanks for the edit, you saved me some time :-) – Shubham Khatri Apr 19 '18 at 05:32
  • Um, question what if I can't access it via the "this", like what if the component I need is an instance of another class? (i.e another child react component within the parent component) – akshay kishore May 13 '19 at 11:53
  • @akshaykishore You would need to pass the ref down using another name say innerRef and pass it to desired component – Shubham Khatri May 13 '19 at 12:26
  • @ShubhamKhatri, I meant what if the parent needs to access the refs in the child component – akshay kishore May 14 '19 at 13:28
  • Both of the example you provide are acceptable for 16.3 and above. The only version issues I can find around this release have to do with ref forwarding method (not shown in this example). – blindguy Aug 05 '19 at 17:14
  • How can I do this with dynamically created element? – Sagar Kodte Sep 17 '19 at 13:12
  • Will you consider to move current React 16.3+ part of the answer to the top ? – Michael Freidgeim Dec 09 '19 at 07:05
  • @MiFreidgeimSO-stopbeingevil, Updated my answer. Thanks for the suggestion – Shubham Khatri Dec 09 '19 at 07:07
  • This method would only work via a parent/child relationship though wouldnt it? You can access any element on the page with document.getelementbyid, you cant with this method described – alexr89 Feb 05 '20 at 11:18
56

For getting the element in react you need to use ref and inside the function you can use the ReactDOM.findDOMNode method.

But what I like to do more is to call the ref right inside the event

<input type="text" ref={ref => this.myTextInput = ref} />

This is some good link to help you figure out.

Ghasem
  • 14,455
  • 21
  • 138
  • 171
EQuimper
  • 5,811
  • 8
  • 29
  • 42
  • 2
    This is the correct way to do this. This adds a reference to the element on the object/class to that you can simply use `this.myTextInput` to access. – Sam Parmenter Feb 12 '17 at 10:42
  • 2
    How can I do this with dynamically created element? – Sagar Kodte Sep 17 '19 at 13:12
  • Calling `React.findDOMNode` gives me `react__WEBPACK_IMPORTED_MODULE_0___default.a.findDOMNode is not a function` – James Poulose Feb 23 '20 at 04:19
  • @JamesPoulose make sure you are not running in strict mode, because react won't allow you to use `React.findDOMNode` in strict mode. – Limpuls Mar 08 '20 at 23:15
  • 3
    This doesn't work with functional components: https://reactjs.org/docs/refs-and-the-dom.html#accessing-refs – Neurion Jul 11 '22 at 03:56
  • 1
    @Neurion Thank you for the link. It guides us on how to do this in functional components. – Sapthaka Jul 15 '22 at 04:15
  • When you create a ref, do you have to pass that reference down the child tree or with useContext even to be able to use that anywhere else? If so your reference needs to be a parent of the component which is utterly stupid, restricted, and encapsulated... I can't just get it from anywhere with a call to get all references. – ii iml0sto1 Nov 02 '22 at 10:03
51

With newer versions of React you can use and manipulate the DOM via hooks like this:

import React, { useEffect, useRef } from "react";

const MyComponent = () => {
  const myContainer = useRef(null);
  
  useEffect(() => {
    console.log("myContainer..", myContainer.current);
  });

  return (
    <>
      <h1>Ref with react</h1>
      <div ref={myContainer}>I can use the DOM with react ref</div>
    </>
  );
};

export default MyComponent;

Whenever you want to access your DOM element just use myContainer.current

IMPixel
  • 98
  • 1
  • 8
Christian
  • 519
  • 4
  • 3
  • 9
    Whenever you want to access your DOM element just use `myContainer.current` – Delmo Jan 13 '22 at 00:08
  • When you create a ref, do you have to pass that reference down the child tree or with useContext even to be able to use that anywhere else? If so your reference needs to be a parent of the component which is utterly stupid, restricted, and encapsulated... I can't just get it from anywhere with a call to get all references. – ii iml0sto1 Nov 02 '22 at 10:04
17

You can replace

document.getElementById(this.state.baction).addPrecent(10);

with

this.refs[this.state.baction].addPrecent(10);


  <Progressbar completed={25} ref="Progress1" id="Progress1"/>
Piyush.kapoor
  • 6,715
  • 1
  • 21
  • 21
5

Disclaimer: While the top answer is probably a better solution, as a beginner it's a lot to take in when all you want is something very simple. This is intended as a more direct answer to your original question "How can I select certain elements in React"

I think the confusion in your question is because you have React components which you are being passed the id "Progress1", "Progress2" etc. I believe this is not setting the html attribute 'id', but the React component property. e.g.

class ProgressBar extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            id: this.props.id    <--- ID set from <ProgressBar id="Progress1"/>
        }
    }        
}

As mentioned in some of the answers above you absolutely can use document.querySelector inside of your React app, but you have to be clear that it is selecting the html output of your components' render methods. So assuming your render output looks like this:

render () {
    const id = this.state.id
    return (<div id={"progress-bar-" + id}></div>)
}

Then you can elsewhere do a normal javascript querySelector call like this:

let element = document.querySelector('#progress-bar-Progress1')
Edward Spencer
  • 448
  • 8
  • 10
4

You have to follow two different ways to do it in Class and Functional components.

  1. For class components

    <input type="text" ref={ref => this.myTextInput = ref} />
    

Look at the above code. Use "ref" attribute to refer to the relevant element. Then you will be able to refer to that element using that reference. In this example, I can use "this.myTextInput" to refer to the above input element.

  1. For functional components

    const textInput = useRef(null)
    

Use the "useRef" hook and set that variable name as the value of the "ref" attribute of the element you want to refer to (like below).

<input type="text" ref={textInput} />

An example for this on functional components.

import React, {useRef} from 'react'

function CustomTextInput(props) {
  // textInput must be declared here so the ref can refer to it
  const textInput = useRef(null);

  function handleClick() {
     textInput.current.focus();
  }

  return (
    <div>
     <input type="text" ref={textInput} />
    </div>
  );
}

Want to learn more? Here you go

Sapthaka
  • 400
  • 1
  • 5
  • 15
-1

Since React uses JSX code to create an HTML we cannot refer dom using regulation methods like documment.querySelector or getElementById.

Instead we can use React ref system to access and manipulate Dom as shown in below example:

constructor(props){

    super(props);
    this.imageRef = React.createRef(); // create react ref
}

componentDidMount(){

    **console.log(this.imageRef)** // acessing the attributes of img tag when dom loads
}


render = (props) => {

const {urls,description} = this.props.image;
    return (

            <img
             **ref = {this.imageRef} // assign the ref of img tag here**
             src = {urls.regular} 
             alt = {description}
             />

        );

}

}

Nikhil Kamani
  • 850
  • 9
  • 12
  • 6
    This is not true. You can add an id to the img element, grab it using document.getElementById, and in most cases, it will work fine. It may cause problems/make your code harder to debug, but react/jsx donlt make it so that you CANT use native Dom methods – adam tropp Jan 22 '20 at 13:44
  • 1
    +1 because this showcases use of ref in a very simple and concise way. However, I'd like to offer the advice that the `**` indicators you added will make inexperienced users think your code is broken (because technically it is with `**`) and generate unnecessary downvotes, so consider removing them. Also note that saying you *cannot* use DOM elements is untrue - you *shouldn't*, but you actually *can*. I'd rather change that line to something like "using the DOM in React to refer to elements is difficult and bad practice, therefore we use refs [...]" – aggregate1166877 Nov 12 '21 at 03:17
-4

In my case, I wasn't able to use ref because elements were somewhere between many child components and I have to access them by class and id instead of ref. So, trying with useEffect hook didn't work as it can't find the element:

useEffect(() => {
  const el1 = document.querySelector('.el1')
  const el2 = document.querySelector('.el2')
}, [])

The element is undefined because when it is mounted the children components also doesn't mounted before this parent component.

So, what I did is to use timeout:

useEffect(() => {
  const timer = setTimeout(() => {
    const el1 = document.querySelector('.el1')
    const el2 = document.querySelector('.el2')
  },500)
  return () => {
    clearTimeout(timer)
  }
}, [])

Now, it worked fine. It found the DOM and I was able to manipulate with them. Hope, this helps someone!

Bhojendra Rauniyar
  • 83,432
  • 35
  • 168
  • 231
  • 8
    It seems like forcing your app (and user!) to wait 500ms before updating is a bad solution. You're obviously adding a delay for the user, and this solution is still brittle as it assumes that the other components you need to access will have been rendered within 500ms. – Hartley Brody Jun 11 '21 at 14:41
  • @HartleyBrody This will run before render hook. So, there'll be no delay. It just wait for the dom available. I had no issue with this approach at all. And I clearly said that I couldn't use the ref and thus applied this trick and posted here which would help in such cases I have faced. – Bhojendra Rauniyar Jun 11 '21 at 16:27
  • 2
    "Mutations, subscriptions, timers, logging, and other side effects are not allowed inside the main body of a function component (referred to as React’s render phase). Doing so will lead to confusing bugs and inconsistencies in the UI." This is an excerpt from the [https://reactjs.org/docs/hooks-reference.html#useeffect](React documentation for useEffect). Don't use side effects inside the function body! – Oliver Tworkowski Aug 30 '21 at 01:13
  • The problem with this code is that it's untestable, as well as not future proof. It will work for specific computers with specific CPU speeds and load capabilities, and not for others. The moment anyone adds new, more complex code, this can cause serious bugs that show up very infrequently and are near-impossible to find. – aggregate1166877 Nov 12 '21 at 03:05
  • Have you tried getting a callback ref for the outermost element `root` (the root of that subtree), and in your callback you could `root.querySelector()` for all the children and grand-children you like? – Martin Mar 27 '22 at 08:23
-8

The equivalent of document.getElementById() in React is document.querySelector().

Alex R
  • 11,364
  • 15
  • 100
  • 180