Search code examples
javascriptcssreactjsstyled-componentsgatsby

React toggle active class on click of anchor using Gatsby Js


I am Using GatsbyJs and am trying to accomplish a active class toggle. I have a simple Component composed of a anchor tag with a span inside. I want to change some css by toggling a active class on the anchor when it is clicked. Here is my code so far, I am also using styled-components. I tried some vanilla js in the broken code section below, that obviously didn't work hence broken code.

Thanks in advance

styles

const Menu = styled.a`
  position: absolute;
  cursor: pointer; padding: 10px 35px 16px 0px;
  span, span:before, span:after{
    cursor: pointer;
    border-radius: 1px;
    height: 5px;
    width: 35px;
    background: #000000;
    position: absolute;
    display: block;
    content: '';
    transition: all 200ms ease-in-out;
  }
  span:before{
    top: -10px; 
  }
  span:after{
    bottom: -10px; 
  }
  .active span{
    background-color: transparent;
  }
  .active span:before, .active span:after{
    top:0;
  }
  .active span:before{
    transform: rotate(45deg);
  }
  .active span:after{
    transform: rotate(-45deg);
  }
`

component

const TemplateWrapper = ({ children }) => (
  <div>
    <Wrapper>
      <Menu id="nav-toggle" className="menu"><span></span></Menu>
      {children()}
    </Wrapper>       
  </div>
)

TemplateWrapper.propTypes = {
  children: PropTypes.func,
}

export default TemplateWrapper

broken code

document.querySelector( "#nav-toggle" )
  .addEventListener( "click", function() {
    this.classList.toggle( "active" );
  });

This is the html that is rendered

<a class="menu sc-bwzfXH SqHLW" id="nav-toggle"><span></span></a>

Solution

  • Okay, you can take a look here REACT - toggle class onclick . There are several answers that show the way how you can achieve that, but things get messier when you want to use more than one class.

    I like this package to handle the cases where you deal with styling with multiple classes based on the props or state: https://github.com/JedWatson/classnames.

    I simplified the styles for the sake of clarity of this answer and assumed that the menu is a global class, you want to apply as well.

    import React, { Component } from 'react';
    import PropTypes from 'prop-types';
    import classNames from 'classnames';
    import styled from 'styled-components';
    
    const Wrapper = styled.div``;
    
    const Menu = styled.a`
      span {
        color: green;
      }
      &.active span {
        color: blue;
      }
    `;
    
    class TemplateWrapper extends Component {
      state = {
        isActive: false
      };
    
      handleClick = () => {
        this.setState(state => ({ isActive: !state.isActive }));
      };
    
      render() {
        const menuClass = classNames({
          menu: true,
          active: this.state.isActive
        });
    
        return (
          <Wrapper>
            <Menu id="nav-toggle" className={menuClass} onClick={this.handleClick}>
              <span>Test</span>
            </Menu>
            {this.props.children}
          </Wrapper>
        );
      }
    }
    
    TemplateWrapper.propTypes = {
      children: PropTypes.node
    };
    
    TemplateWrapper.defaultProps = {
      children: null
    };
    
    export default TemplateWrapper;
    

    Notice the ampersand (&) before .active. As stated in the docs, styled components supports all of CSS plus nesting.

    Ampersands (&) get replaced by our generated, unique classname for that styled component, making it easy to have complex logic.

    However, you can achieve the same effect without classnames by fully utilizing styled components functionality. Take a look at this section of the documentation.

    A few changes needed:

    const Menu = styled.a`
      span {
        color: ${props => (props.active ? 'blue' : 'green')};
      }
    `;
    

    The render method would look like that:

    render() {
      return (
        <Wrapper>
          <Menu id="nav-toggle" className="menu" active={this.state.isActive} onClick={this.handleClick}>
            <span>Test</span>
          </Menu>
          {this.props.children}
        </Wrapper>
      );
    }