Search code examples
javascriptreactjsreact-hooksreact-class-based-component

how to convert a hook into class based component


I have a functional component which i used to detect outside click of the component and once that done i do some logic... now i had to use this inside a class based component

My Hook useComponentVisible

import { useState, useEffect, useRef } from 'react';

export default function useComponentVisible(visible) {
  const [isComponentVisible, setIsComponentVisible] = useState(visible);
  const ref = useRef(null);

  const handleHideDropdown = (event) => {
    if (event.key === 'Escape') {
      setIsComponentVisible(false);
    }
  };

  const handleClickOutside = (event) => {
    if (ref.current && !ref.current.contains(event.target)) {
      setIsComponentVisible(false);
    }
  };

  useEffect(() => {
    document.addEventListener('keydown', handleHideDropdown, true);
    document.addEventListener('click', handleClickOutside, true);
    return () => {
      document.removeEventListener('keydown', handleHideDropdown, true);
      document.removeEventListener('click', handleClickOutside, true);
    };
  });

  return { ref, isComponentVisible, setIsComponentVisible };
}

// How i was using it in my function based component

import useComponentVisible from '../../hooks/useComponentVisible';
import { MenuItem, MenuWrapper } from './MenuDropDownStyle';

const MenuDropDown = () => {
  const { ref, isComponentVisible } = useComponentVisible(true);

  return (
    <>
      {isComponentVisible && (
        <MenuWrapper ref={ref}>
          <MenuItem to="/lectures">Your profile</MenuItem>
          <MenuItem to="/lectures">Log out</MenuItem>
        </MenuWrapper>
      )}
    </>
  );
};

export default MenuDropDown;

//i need to use the component in CurrencyDropdown

import React, { Component } from "react";
import { SelectWrapper } from "./CurrencyDropdownStyle";
import { getCurrency } from "../../../utls/MakeQuery";
import { SelectInput } from '../../';
import useComponentVisible from '../../hooks/useComponentVisible'

export class CurrencyDropdown extends Component {
  constructor() {
    super();
    this.state = {
      currency: [],
    };
    // const { ref, isComponentVisible } = useComponentVisible(true);
  }

  componentDidMount() {
    getCurrency()
      .then((res) => {
        this.setState({
          currency: res.data.currencies,
        });
        this.props.toggleCurrency("USD+$");
      })
      .catch((err) => {
        console.log(err);
      });
  }

  // Function That will handle the change of the currency in the state(●'◡'●)
  handleToggleCurrency(value){
    this.props.toggleCurrency(value)
  }
  render() {
    
    return <SelectWrapper>
        {this.state.currency ? this.state.currency.map((item,index)=>(
                <SelectInput key={index} value={`${item.label}+${item.symbol}`} label={`${item.symbol} ${item.label}`}/>
            )):""}
    </SelectWrapper>;
  }
}

export default CurrencyDropdown;

Solution

  • A class-based component cannot use hooks, which are only designed for function-based components.

    I'd suggest that you should convert your component to a function-based component or use a wrapper to handle hooks.

    Here is how we're using a wrapper to handle your case

    class CurrencyDropdownComponent extends Component {
      constructor() {
        super();
        this.state = {
          currency: [],
        };
        // const { ref, isComponentVisible } = useComponentVisible(true);
      }
    
      componentDidMount() {
        getCurrency()
          .then((res) => {
            this.setState({
              currency: res.data.currencies,
            });
            this.props.toggleCurrency("USD+$");
          })
          .catch((err) => {
            console.log(err);
          });
      }
    
      // Function That will handle the change of the currency in the state(●'◡'●)
      handleToggleCurrency(value){
        this.props.toggleCurrency(value)
      }
      render() {
        
        return <SelectWrapper>
            {this.state.currency ? this.state.currency.map((item,index)=>(
                    <SelectInput key={index} value={`${item.label}+${item.symbol}`} label={`${item.symbol} ${item.label}`}/>
                )):""}
        </SelectWrapper>;
      }
    }
    
    //the function-based component as a wrapper for `CurrencyDropdown` component
    function CurrencyDropdown = (props) => {
       const { ref, isComponentVisible } = useComponentVisible(true);
       return <CurrencyDropdownComponent {...props} isComponentVisible={isComponentVisible} ref={ref}>
    }
    
    export default CurrencyDropdown;
    

    Another solution is converting your class-based component to a function-based component

    function CurrencyDropdown(props) {
      const { ref, isComponentVisible } = useComponentVisible(true);
      const [currency, setCurrency] = React.useState([]) //`this.state` replacement
    
      //componentDidMount replacement
      useEffect(() => {
         getCurrency()
          .then((res) => {
            setCurrency(res.data.currencies);
            props.toggleCurrency("USD+$");
          })
          .catch((err) => {
            console.log(err);
          });
      }, [])
    
      // Function That will handle the change of the currency in the state(●'◡'●)
      handleToggleCurrency(value){
        props.toggleCurrency(value)
      }
      
      return <SelectWrapper>
            {currency ? currency.map((item,index)=>(
                    <SelectInput key={index} value={`${item.label}+${item.symbol}`} label={`${item.symbol} ${item.label}`}/>
                )):""}
        </SelectWrapper>
    }
    
    export default CurrencyDropdown;