0

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;
sarangkkl
  • 773
  • 3
  • 15

4 Answers4

1

You need to create a wrapper functional component for the class component.

const CurrencyDropdownWrapper = (props) => {
   const hookData = useComponentVisible(true);
   return <CurrencyDropdown {...props} hookData={hookData} />
}

Then use your hook properties from hookData prop inside your class component.

Han Moe Htet
  • 692
  • 1
  • 6
  • 10
1

//Here is the way i solved it

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

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

  handleHideDropdown = (event) => {
    if (event.key === 'Escape') {
      this.setState({ isComponentVisible: false });
    }
  }

  handleClickOutside = (event) => {
    if (this.state.ref.current && !this.state.ref.current.contains(event.target)) {
      this.setState({ isComponentVisible: false });
    }
  }

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

      document.addEventListener('keydown', this.handleHideDropdown, true);
      document.addEventListener('click', this.handleClickOutside, true);
  }
  componentWillUnmount(){
    document.removeEventListener('keydown', this.handleHideDropdown, true);
    document.removeEventListener('click', this.handleClickOutside, true);
  }
  // Function That will handle the change of the currency in the state(●'◡'●)
  handleToggleCurrency(value){
    this.props.toggleCurrency(value)
  }
  render() {
    
    return <>
        {this.state.isComponentVisible && <SelectWrapper ref={this.state.ref}>
        {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;
sarangkkl
  • 773
  • 3
  • 15
  • ohhh your solution is great, but just have a side note that hooks are reusable. With this approach, we cannot reuse it in other components and possibly duplicate our code everywhere, so we need to keep that in mind. Other things else are good! – Nick Vu Jun 28 '22 at 09:37
  • 1
    i Agree with you but i have no other option i need this functional at two places i need to copy paste there is noo way i can use it on my class based components – sarangkkl Jun 28 '22 at 15:29
0

You can not mix functional and class based components. There is no way you can use hook inside class based component, it is completely different mental and technical model of programming react elements. You would need either to convert class based to functional(which I suggest), or to create another-like useComponentVisible mechanism but in a class based style. I would suggest to stick with either class based or functional based style, it can make you troubles when you mix both of the, especially when there is a request to share some functionalities between them.

Milos Pavlovic
  • 1,355
  • 1
  • 2
  • 7
0

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;
Nick Vu
  • 14,512
  • 4
  • 21
  • 31
  • Nick I did not downvote you i have upvoted you...can you convert my hook into class based model or let me know how would i do that i had to use only class based things – sarangkkl Jun 28 '22 at 09:04
  • 1
    You can check it out again. I added another solution to convert your class-based component to a function-based component. Hopefully, it helps you out :D @sarangkkl – Nick Vu Jun 28 '22 at 09:07
  • 1
    Let me know if you face any problems. I can support you till you solve it :D @sarangkkl – Nick Vu Jun 28 '22 at 09:21
  • i appritiate your effort to help me a lot but my brother if i want i can convert my class based component to function but i have condition i can not use functional components anyhow i only have one option that to convert my hook to class based hook But finally i solved it – sarangkkl Jun 28 '22 at 09:27
  • What i did is that i put the code of my hook inside the component itself and it works – sarangkkl Jun 28 '22 at 09:28
  • 1
    ah okie, I'm glad to hear that! keep up your good work, bro! @sarangkkl – Nick Vu Jun 28 '22 at 09:29
  • You can look at my answer i just posted it – sarangkkl Jun 28 '22 at 09:31