hossein-zare / react-native-dropdown-picker

A single / multiple, categorizable, customizable, localizable and searchable item picker (drop-down) component for react native which supports both Android & iOS.
https://hossein-zare.github.io/react-native-dropdown-picker-website/
MIT License
999 stars 297 forks source link

support auto dimiss when tap out of the dropdown panel #579

Open kongshu612 opened 2 years ago

kongshu612 commented 2 years ago

Hi, Assume we have the multiple dropdown aligned in the vertical one by one. the multi dropdowns can be opened at the same time. If we provide a auto dimiss feature when user tap out of dropdown panel, that will be great.

<DropDownPicker/>
<DropDownPicker/>
<DropDownPicker/>
<DropDownPicker/>

currently, we use a global hook to auto dismiss the opened dropdown to bypass this issue.

aashishshrestha5532 commented 2 years ago

Hy @kongshu612, i think you are missing some props there. in order to use multiple dropdown, you need to pass the open props on the component and also you need to maintain the state for all the related dropdown picker accordingly.

So in your case, it should be like


const [open1,setopen1] = useState(false);
const [open2,setOpen2] = useState(false);
const [open3,setOpen3] = useState(false);

      <DropDownPicker
              multiple={true}
              listMode="MODAL"
              open={open1} // this one
              value={value1}
              items={items1}
              setOpen={setOpen1}

            />

  <DropDownPicker
              multiple={true}
              listMode="MODAL"
              open={open1} // this one
              value={value2}
              items={items2}
              setOpen={setOpen2}

            />

So in your case, you have 4 pickers, you need to maintain the state for 4 different pickers just like above. I hope i am able to answer your issue.Correct me if i failed to understand your issue.

kongshu612 commented 2 years ago

hi @aashishshrestha5532 , thanks for your reply. my case was a little different with what you comment. it make sense to maintain isolated open state for each dropdown in the component level. In my case, these dropdowns live in different components, and those components are composed in one screen. Currently, my workaround is to maintain such open state in the global level, which I think is not good.

If we provide a transparent mask behind the dropdown panel and tap the mask will auto dismiss the dropdown panel, that will be great.

MatheusHCP commented 2 years ago

Hi, I have the same problem, did you manage to solve it?

durgesh94 commented 1 year ago

I'm facing the same issue, any workaround?

kongshu612 commented 1 year ago

Hi @MatheusHCP , @durgesh94 ,sorry for reply late. My workaround is to define a global state by using hookAPI. see the code bellow

import React from 'react';

export interface IClose {
  close: () => void;
  displayName?: string;
}

let modals: Set<IClose> = new Set();
let activeModal: IClose | undefined;

function push(instance: IClose) {
  if (!modals.has(instance)) {
    modals.add(instance);
  }
}

function popup(instance: IClose) {
  if (modals.has(instance)) {
    modals.delete(instance);
  }
  if (activeModal === instance) {
    activeModal = undefined;
  }
}

function toggleOpen(instance: IClose, open: boolean) {
  if (open) {
    if (activeModal !== instance) {
      activeModal?.close();
      activeModal = instance;
    }
  } else {
    if (activeModal === instance) {
      activeModal = undefined;
    }
  }
}

export function useAutoClose(instance: IClose) {
  React.useEffect(() => {
    push(instance);
    return () => {
      popup(instance);
    }
  }, [instance]);

  return React.useCallback((open: boolean) => {
    toggleOpen(instance, open);
  }, [instance]);
}
nathantew14 commented 1 year ago

Hi @MatheusHCP , @durgesh94 ,sorry for reply late. My workaround is to define a global state by using hookAPI. see the code bellow

import React from 'react';

export interface IClose {
  close: () => void;
  displayName?: string;
}

let modals: Set<IClose> = new Set();
let activeModal: IClose | undefined;

function push(instance: IClose) {
  if (!modals.has(instance)) {
    modals.add(instance);
  }
}

function popup(instance: IClose) {
  if (modals.has(instance)) {
    modals.delete(instance);
  }
  if (activeModal === instance) {
    activeModal = undefined;
  }
}

function toggleOpen(instance: IClose, open: boolean) {
  if (open) {
    if (activeModal !== instance) {
      activeModal?.close();
      activeModal = instance;
    }
  } else {
    if (activeModal === instance) {
      activeModal = undefined;
    }
  }
}

export function useAutoClose(instance: IClose) {
  React.useEffect(() => {
    push(instance);
    return () => {
      popup(instance);
    }
  }, [instance]);

  return React.useCallback((open: boolean) => {
    toggleOpen(instance, open);
  }, [instance]);
}

i'm not sure why you have a modals set and all those extra functions but this worked for me:

import React, { SetStateAction } from "react";

export interface IClose {
  setOpen: (value: boolean) => void;
  id: string;
}

let activeDropdown: IClose | undefined;

function toggleOpen(instance: IClose, open: SetStateAction<boolean>) {
  if (open) {
    if (activeDropdown !== instance) {
      activeDropdown?.setOpen(false);
      activeDropdown = instance;
    }
    instance.setOpen(true);
  } else {
    if (activeDropdown === instance) {
      activeDropdown = undefined;
    }
    instance?.setOpen(false);
  }
}

export function closeActiveDropdown() {
  activeDropdown?.setOpen(false);
  activeDropdown = undefined;
}

export function useAutoCloseDropdown(instance: IClose) {
  return React.useCallback(
    (open: SetStateAction<boolean>) => {
      toggleOpen(instance, open);
    },
    [instance]
  );
}

In the component where I render the dropdown:

const [open, setOpen] = useState(false);
const toggle = useAutoCloseDropdown({ id: label, setOpen });

Then simply pass toggle to the setOpen prop of DropdownPicker

For future coders with the same problem: you can also wrap your page in a <TouchableWithoutFeedback> and set the onPress to the globally exported closeActiveDropdown so that tapping outside of the dropdown closes it.

And if you want to use this with a ScrollView inside, it's gonna disable the scrolling, so add an enclosing View inside your ScrollView and add this prop to it: onStartShouldSetResponder={() => true} (from StackoverFlow)

HackX-IN commented 5 months ago

Hi @MatheusHCP , @durgesh94 ,sorry for reply late. My workaround is to define a global state by using hookAPI. see the code bellow

import React from 'react';

export interface IClose {
  close: () => void;
  displayName?: string;
}

let modals: Set<IClose> = new Set();
let activeModal: IClose | undefined;

function push(instance: IClose) {
  if (!modals.has(instance)) {
    modals.add(instance);
  }
}

function popup(instance: IClose) {
  if (modals.has(instance)) {
    modals.delete(instance);
  }
  if (activeModal === instance) {
    activeModal = undefined;
  }
}

function toggleOpen(instance: IClose, open: boolean) {
  if (open) {
    if (activeModal !== instance) {
      activeModal?.close();
      activeModal = instance;
    }
  } else {
    if (activeModal === instance) {
      activeModal = undefined;
    }
  }
}

export function useAutoClose(instance: IClose) {
  React.useEffect(() => {
    push(instance);
    return () => {
      popup(instance);
    }
  }, [instance]);

  return React.useCallback((open: boolean) => {
    toggleOpen(instance, open);
  }, [instance]);
}

i'm not sure why you have a modals set and all those extra functions but this worked for me:

import React, { SetStateAction } from "react";

export interface IClose {
  setOpen: (value: boolean) => void;
  id: string;
}

let activeDropdown: IClose | undefined;

function toggleOpen(instance: IClose, open: SetStateAction<boolean>) {
  if (open) {
    if (activeDropdown !== instance) {
      activeDropdown?.setOpen(false);
      activeDropdown = instance;
    }
    instance.setOpen(true);
  } else {
    if (activeDropdown === instance) {
      activeDropdown = undefined;
    }
    instance?.setOpen(false);
  }
}

export function closeActiveDropdown() {
  activeDropdown?.setOpen(false);
  activeDropdown = undefined;
}

export function useAutoCloseDropdown(instance: IClose) {
  return React.useCallback(
    (open: SetStateAction<boolean>) => {
      toggleOpen(instance, open);
    },
    [instance]
  );
}

In the component where I render the dropdown:

const [open, setOpen] = useState(false);
const toggle = useAutoCloseDropdown({ id: label, setOpen });

Then simply pass toggle to the setOpen prop of DropdownPicker

For future coders with the same problem: you can also wrap your page in a <TouchableWithoutFeedback> and set the onPress to the globally exported closeActiveDropdown so that tapping outside of the dropdown closes it.

And if you want to use this with a ScrollView inside, it's gonna disable the scrolling, so add an enclosing View inside your ScrollView and add this prop to it: onStartShouldSetResponder={() => true} (from StackoverFlow)

this helps thank you !