react-onclickoutside icon indicating copy to clipboard operation
react-onclickoutside copied to clipboard

Hooks with Typescript: ts(2339) Property 'handleClickOutside' does not exist.

Open aliankarali opened this issue 6 years ago • 9 comments

Update: Another thing I noticed with this pattern is that when you have multiple instances of the same Component, only the last one triggers handleClickOutside.

Here is my code, grossly simplified (sorry if it is more of a TS question rather than this library, but in any way it would be nice to see an example).

error: Property 'handleClickOutside' does not exist on type '({ options, }: Props) => Element'. ts(2339)

The code works fine but in the editor Dropdown.handleClickOutside lines highlights this error. Other than using // @ts-ignore, how can I solve this issue? how can I specify the property?

import * as React from 'react';
import onClickOutside from 'react-onclickoutside';

interface Props {
    options: string[];
}

export const Dropdown = ({
    options,
}: Props) => {
    const [isOpen, setIsOpen] = React.useState(false);

    Dropdown.handleClickOutside = () => {
        setIsOpen(false);
    };

    return (
        <div>
            <ul isOpen={isOpen}>
                {options.map((option, i) => {
                    return (
                        <li
                            key={i}
                        >
                            {option}
                        </li>
                    );
                })}
            </ul>
        </div>
    );
};

export default onClickOutside(
    Dropdown,
    { handleClickOutside: () => Dropdown.handleClickOutside }
);

aliankarali avatar Feb 08 '19 12:02 aliankarali

try this in the same file:

namespace Dropdown {
  export let handleClickOutside: () => void;
}

pasih avatar Mar 07 '19 07:03 pasih

thanks for the tip 👍

aliankarali avatar Mar 07 '19 19:03 aliankarali

I tried this solution but typescript complains this:

Cannot redeclare block-scoped variable 'Dropdown'.

nerdmax avatar Mar 21 '19 04:03 nerdmax

I also tried it and I get Duplicate identifier 'Dropdown'. react-onclickoutside v6.8.0, typescript v.3.3.4000

n10v avatar Apr 08 '19 13:04 n10v

Third on the train, the create-react-app default linter doesn't like it

Failed to compile.

./src/components/MenuBar/MenuBar.tsx
  Line 45:  ES2015 module syntax is preferred over custom TypeScript modules and namespaces  @typescript-eslint/no-namespace

Search for the keywords to learn more about each error.

not an error, but if someone could suggest an alternative solution, would be great.

hlolli avatar Jun 18 '19 22:06 hlolli

As a solution you can get rid of this library and use this small hook from https://usehooks.com/useOnClickOutside/. It works for me really well:

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

// Usage
function App() {
  // Create a ref that we add to the element for which we want to detect outside clicks
  const ref = useRef();
  // State for our modal
  const [isModalOpen, setModalOpen] = useState(false);
  // Call hook passing in the ref and a function to call on outside click
  useOnClickOutside(ref, () => setModalOpen(false));

  return (
    <div>
      {isModalOpen ? (
        <div ref={ref}>
          👋 Hey, I'm a modal. Click anywhere outside of me to close.
        </div>
      ) : (
        <button onClick={() => setModalOpen(true)}>Open Modal</button>
      )}
    </div>
  );
}

// Hook
function useOnClickOutside(ref, handler) {
  useEffect(
    () => {
      const listener = event => {
        // Do nothing if clicking ref's element or descendent elements
        if (!ref.current || ref.current.contains(event.target)) {
          return;
        }

        handler(event);
      };

      document.addEventListener('mousedown', listener);
      document.addEventListener('touchstart', listener);

      return () => {
        document.removeEventListener('mousedown', listener);
        document.removeEventListener('touchstart', listener);
      };
    },
    // Add ref and handler to effect dependencies
    // It's worth noting that because passed in handler is a new ...
    // ... function on every render that will cause this effect ...
    // ... callback/cleanup to run every render. It's not a big deal ...
    // ... but to optimize you can wrap handler in useCallback before ...
    // ... passing it into this hook.
    [ref, handler]
  );
}

n10v avatar Jun 19 '19 12:06 n10v

Thanks for the hooks @bogem ;) For those looking for an external solution I would recommend https://github.com/airbnb/react-outside-click-handler

aliankarali avatar Oct 08 '19 15:10 aliankarali

Defining the handler on the function after its definition worked for me. I think it's ok, as its value will be set on rendering the component anyways.

import {useState} from 'react'
import onClickOutside from 'react-onclickoutside'

const UserInfo = () => {
  const [menuVisible, setMenuVisible] = useState(false)
  UserInfo.clickOutside = () => setMenuVisible(false)

  return (
    <div className="cursor-pointer"
      onClick={() => setMenuVisible(!menuVisible)}      
    >
      <p>Click me</p>
      <div className={!menuVisible && 'hidden'}>
        <p>I am zee menu</p>
        <p>I am zee menu</p>
      </div>
    </div>
  )
}
UserInfo.clickOutside = null /// !! this

export default onClickOutside(UserInfo, {
  handleClickOutside: () => UserInfo.clickOutside,
})

softatac avatar Apr 07 '21 09:04 softatac

Just a note: if you've already written the code to make hooks work, PRs that add a hook solution are most certainly welcome, and I'll be more than happy to push out a new version.

Pomax avatar Apr 07 '21 15:04 Pomax