37

I'm using react for a project where I have a menu button.

<a ref="btn" href="#" className="btn-menu show-on-small"><i></i></a>

And a Sidenav component like:

<Sidenav ref="menu" />

And I wrote the following code to toggle the menu:

class Header extends React.Component {

    constructor(props){
        super(props);
        this.toggleSidenav = this.toggleSidenav.bind(this);
    }

    render() {
        return (
          <div className="header">
            <i className="border hide-on-small-and-down"></i>
            <div className="container">
          <a ref="btn" href="#" className="btn-menu show-on-small"><i></i></a>
          <Menu className="menu hide-on-small-and-down"/>
          <Sidenav />
        </div>
          </div>
        )
    }

    toggleSidenav() {
        this.refs.btn.classList.toggle('btn-menu-open');
    }

    componentDidMount() {
        this.refs.btn.addEventListener('click', this.toggleSidenav);
    }

    componentWillUnmount() {
        this.refs.btn.removeEventListener('click', this.toggleSidenav);
    }
}

The thing is that this.refs.sidenav is not a DOM element and I cant add a class on him.

Can someone explain me how to toggle class on the Sidenav component like I do on my button?

Ian Oxley
  • 10,916
  • 6
  • 42
  • 49
Hiero
  • 2,182
  • 7
  • 28
  • 47
  • 5
    You are doing it wrong, as you change classes and event handlers using the DOM, instead of the React component render method. Can you add the code of your component? – Ori Drori Apr 04 '16 at 13:15
  • updated, please let me know how can i do it better – Hiero Apr 04 '16 at 13:18
  • 2
    @Hiero this is a fundamental misunderstanding of React. You are essentially storing state in the DOM, which you should avoid at all costs. Instead, you should be tracking the visiblity of the button in `state` and declaring the `className` dynamically in the `render()` based on that state - see https://facebook.github.io/react/docs/interactivity-and-dynamic-uis.html – Matt Apr 04 '16 at 13:30

5 Answers5

35

You have to use the component's State to update component parameters such as Class Name if you want React to render your DOM correctly and efficiently.

UPDATE: I updated the example to toggle the Sidemenu on a button click. This is not necessary, but you can see how it would work. You might need to use "this.state" vs. "this.props" as I have shown. I'm used to working with Redux components.

constructor(props){
    super(props);
}

getInitialState(){
  return {"showHideSidenav":"hidden"};
}

render() {
    return (
        <div className="header">
            <i className="border hide-on-small-and-down"></i>
            <div className="container">
                <a ref="btn" onClick={this.toggleSidenav.bind(this)} href="#" className="btn-menu show-on-small"><i></i></a>
                <Menu className="menu hide-on-small-and-down"/>
                <Sidenav className={this.props.showHideSidenav}/>
            </div>
        </div>
    )
}

toggleSidenav() {
    var css = (this.props.showHideSidenav === "hidden") ? "show" : "hidden";
    this.setState({"showHideSidenav":css});
}

Now, when you toggle the state, the component will update and change the class name of the sidenav component. You can use CSS to show/hide the sidenav using the class names.

.hidden {
   display:none;
}
.show{
   display:block;
}
Aaron Franco
  • 1,560
  • 1
  • 14
  • 19
  • 2
    why its not this.state.showHideSidenav? – Hiero Apr 04 '16 at 13:33
  • 1
    You might be right to use State there. I can't remember off the top of my head. I'm using to using Redux with React which forces all UI updates into the component Props instead of state. Try both, then use which ever works :) – Aaron Franco Apr 04 '16 at 13:39
  • If using ES6 classes set initial state in constructor instead. Ref: https://stackoverflow.com/questions/30668326/what-is-the-difference-between-using-constructor-vs-getinitialstate-in-react-r – Karl Erik Steinbakk Jul 31 '18 at 03:49
20

refs is not a DOM element. In order to find a DOM element, you need to use findDOMNode menthod first.

Do, this

var node = ReactDOM.findDOMNode(this.refs.btn);
node.classList.toggle('btn-menu-open');

alternatively, you can use like this (almost actual code)

this.state.styleCondition = false;


<a ref="btn" href="#" className={styleCondition ? "btn-menu show-on-small" : ""}><i></i></a>

you can then change styleCondition based on your state change conditions.

Bikas
  • 2,709
  • 14
  • 32
  • but why you show me an example of adding class to my button as my question was how to add class on the Sidenav componenet? – Hiero Apr 04 '16 at 13:35
  • Just change the `btn` with `menu`. It's no big deal. I just took one example, essential coding is same in both cases – Bikas Apr 04 '16 at 13:37
  • I suggest option 2 as it confronts to React standards. and yes, it's best practice – Bikas Apr 04 '16 at 13:43
  • Are you sure, it's not a DOM node? (or maybe things changed... ?) Because I can happily do stuff like `this.refs.nav.getElementsByTagName('ul')[0].offsetHeight` ... – Frank N Apr 04 '17 at 11:08
  • @Bikas Thanks the second option i loved it – SakthiSureshAnand May 24 '18 at 14:02
  • In the [React docs](https://reactjs.org/docs/refs-and-the-dom.html), it says: "If you have absolutely no control over the child component implementation, your last option is to use findDOMNode(), but it is discouraged and deprecated in StrictMode." So `findDOMNode` is not recommended as of spring 2019. – rpivovar Jun 03 '19 at 19:47
  • current refers to the DOMelement ref.current.classList.toggle –  Nov 18 '21 at 17:41
4

Toggle function in react

At first you should create constructor like this

constructor(props) {
        super(props);
        this.state = {
            close: true,
        };
    }

Then create a function like this

yourFunction = () => {
        this.setState({
            close: !this.state.close,
        });
    };

then use this like

render() {
        const {close} = this.state;
        return (

            <Fragment>

                 <div onClick={() => this.yourFunction()}></div>

                 <div className={close ? "isYourDefaultClass" : "isYourOnChangeClass"}></div>

            </Fragment>
        )
    }
}

Please give better solutions

Masud Rana
  • 590
  • 1
  • 8
  • 17
3

Ori Drori's comment is correct, you aren't doing this the "React Way". In React, you should ideally not be changing classes and event handlers using the DOM. Do it in the render() method of your React components; in this case that would be the sideNav and your Header. A rough example of how this would be done in your code is as follows.

HEADER

class Header extends React.Component {
constructor(props){
    super(props);
}

render() {
    return (
        <div className="header">
            <i className="border hide-on-small-and-down"></i>
            <div className="container">
                <a ref="btn" href="#" className="btn-menu show-on-small"
                onClick=this.showNav><i></i></a>
                <Menu className="menu hide-on-small-and-down"/>
                <Sidenav ref="sideNav"/>
            </div>
        </div>
    )
}

showNav() {
  this.refs.sideNav.show();
}
}

SIDENAV

 class SideNav extends React.Component {
   constructor(props) {
     super(props);
     this.state = {
       open: false
     }
   }

   render() {
     if (this.state.open) {
       return ( 
         <div className = "sideNav">
            This is a sidenav 
         </div>
       )
     } else {
       return null;
     }

   }

   show() {
     this.setState({
       open: true
     })
   }
 }

You can see here that we are not toggling classes but using the state of the components to render the SideNav. This way, or similar is the whole premise of using react. If you are using bootstrap, there is a library which integrates bootstrap elements with the react way of doing things, allowing you to use the same elements but set state on them instead of directly manipulating the DOM. It can be found here - https://react-bootstrap.github.io/

Hope this helps, and enjoy using React!

  • Thank you Martin, now I understand, but by pushing to the classList of this.refs.btn its fine or should I make that component too? – Hiero Apr 04 '16 at 13:43
  • 2
    Aaron Francos way is a good clean implementation which will solve your problem. Personally though, I think that its nice to have React components to represent every component of your UI. It keeps the code modular, terse and makes it very easy for you to structure your application and reduce coupling. It really comes down to personal preference, as long as you are setting the state of your components and not directly manipulating the DOM, you are on the right track. :) – Martin McKeaveney Apr 04 '16 at 13:49
  • This is the more "React"-ish way to handle this. This should have minimal impact on the DOM. My way is an cool easy way, but admittedly, I wasn't as advanced in react as I am now. Now, I would create a component with conditional rendering. Nice reply! https://reactjs.org/docs/conditional-rendering.html – Aaron Franco Dec 07 '17 at 04:46
2

For anybody reading this in 2019, after React 16.8 was released, take a look at the React Hooks. It really simplifies handling states in components. The docs are very well written with an example of exactly what you need.

Tudor Ravoiu
  • 2,130
  • 8
  • 35
  • 56