Published on

Using Ref with React Class Components and Styled-Components

Authors
  • Name
    Ripal & Zalak
    Twitter

How to Properly Use Ref with a React Class Component and Styled-Components

If you're working with styled-components in a React class component and need to detect clicks outside a component, you might run into issues using refs. This guide will show you the proper way to use refs in class components with styled-components.

The Problem

Using the old string-based ref API (this.refs.wrapper) is deprecated and not recommended. Instead, you should use React.createRef(), which allows you to directly reference the DOM node inside your component.

Here’s an example where we attempt to detect outside clicks but run into an issue with the old approach:

import React from 'react'
import styled, { ThemeProvider } from 'styled-components'
import { theme } from '../theme'

const Container = styled.div`
  display: flex;
`

const SelectorDiv = styled.div`
  background-color: black;
  color: white;
  height: 100px;
`

class Test extends React.Component {
  componentDidMount() {
    document.addEventListener('mousedown', this.handleClickOutside)
  }

  componentWillUnmount() {
    document.removeEventListener('mousedown', this.handleClickOutside)
  }

  handleClickOutside = (event) => {
    console.log(this.refs) // Undefined issue
  }

  handleClickInside = () => {
    alert('Clicked inside')
  }

  render() {
    return (
      <ThemeProvider theme={theme}>
        <Container>
          <SelectorDiv onClick={this.handleClickInside} ref={'wrapper'}>
            <h1>This is the content to click</h1>
          </SelectorDiv>
        </Container>
      </ThemeProvider>
    )
  }
}
export default Test

The Solution: Use React.createRef()

To properly use refs in a React class component, follow these steps:

  1. Create a ref in the constructor using React.createRef().
  2. Attach it to the component using the ref attribute.
  3. Access the DOM element using this.ref.current.

Here’s the corrected version of the above code:

import React from 'react'
import styled, { ThemeProvider } from 'styled-components'
import { theme } from '../theme'

const Container = styled.div`
  display: flex;
`

const SelectorDiv = styled.div`
  background-color: black;
  color: white;
  height: 100px;
`

class Test extends React.Component {
  constructor(props) {
    super(props)
    this.selectorRef = React.createRef()
  }

  componentDidMount() {
    document.addEventListener('mousedown', this.handleClickOutside)
  }

  componentWillUnmount() {
    document.removeEventListener('mousedown', this.handleClickOutside)
  }

  handleClickOutside = (event) => {
    if (this.selectorRef.current && !this.selectorRef.current.contains(event.target)) {
      console.log('Clicked outside')
    }
  }

  handleClickInside = () => {
    alert('Clicked inside')
  }

  render() {
    return (
      <ThemeProvider theme={theme}>
        <Container>
          <SelectorDiv onClick={this.handleClickInside} ref={this.selectorRef}>
            <h1>This is the content to click</h1>
          </SelectorDiv>
        </Container>
      </ThemeProvider>
    )
  }
}
export default Test

Key Improvements

  • Uses React.createRef() to store the reference.
  • Attaches the ref properly using ref={this.selectorRef}.
  • Checks if the click is outside the component using this.selectorRef.current.contains(event.target).

FAQs

1. Can I use useRef instead?

No, useRef is a React Hook, which only works in functional components. If you are using class components, React.createRef() is the way to go.

2. What if I need multiple refs?

You can create multiple refs inside the constructor:

this.ref1 = React.createRef()
this.ref2 = React.createRef()

Then attach them individually.

3. What happens if this.selectorRef.current is null?

This can happen if the ref is not attached yet. Make sure to check if this.selectorRef.current exists before accessing it.