GeekyAnts / NativeBase

Mobile-first, accessible components for React Native & Web to build consistent UI across Android, iOS and Web.
https://nativebase.io/
MIT License
20.19k stars 2.39k forks source link

[Bug] Toast is not working within a Modal #985

Open JLLLinn opened 7 years ago

JLLLinn commented 7 years ago

react-native, react and native-base version

RN: 0.45.1 React:16.0.0-alpha.12 native-base: 2.1.5

Expected behaviour

Toast to be shown in react native Modal

Actual behaviour

The toast is not shown in react native Modal

Steps to reproduce (code snippet or screenshot)

Do Toast.show() in a Modal

Is the bug present in both ios and android or in any one of them?

No that I know of. This was working with RN on 0.44 and native-base on a lower version (I can't remember exactly which one, either 2.1.4 or 2.1.3)

iooi commented 2 years ago

+1

zzyjiaxinmomo commented 2 years ago

is there any solution now?

Viraj-10 commented 2 years ago

Hi @zzyjiaxinmomo @iooi @Oct4Pie, Can you provide small snack example ?

Oct4Pie commented 2 years ago

Hi @zzyjiaxinmomo @iooi @Oct4Pie, Can you provide small snack example ?

Hello, @Viraj-10. Here it is:

import React from "react";
import {
  Text,
  HStack,
  Center,
  Switch,
  useColorMode,
  NativeBaseProvider,
  extendTheme,
  VStack,
  Modal,
  Input,
  FormControl,
  Button,
  useToast,
} from "native-base";

const config = {
  useSystemColorMode: false,
  initialColorMode: "dark",
};

export const theme = extendTheme({ config });

export default function App() {
  const [show, setShow] = React.useState(false);
  const toast = useToast();

  return (
    <NativeBaseProvider>
      <Center
        _dark={{ bg: "blueGray.900" }}
        _light={{ bg: "blueGray.50" }}
        px={4}
        flex={1}
      >
        <VStack space='3' >
          <Modal onClose={() => setShow(false)} isOpen={show}>
            <Modal.Content>
              <Modal.CloseButton />
              <Modal.Header>Sign In</Modal.Header>
              <Modal.Body>
                Please enter your credentials to proceed
                <FormControl mt={{
                  'base': '3',
                  'lg': '4',
                  '2xl': '5',
                }}>
                  <FormControl.Label>Email</FormControl.Label>
                  <Input />
                </FormControl>
                <FormControl mt={{
                  'base': '3',
                  'lg': '4',
                  '2xl': '5',
                }}>
                  <FormControl.Label>Password</FormControl.Label>
                  <Input />
                </FormControl>
              </Modal.Body>
              <Modal.Footer>
                <Button onPress={() => {
                  toast.show({
                    // variant: "solid",
                    description: "You are signed in!",
                  })
                  // setShow(false);
                }}>
                  Sign In
                </Button>
              </Modal.Footer>
            </Modal.Content>
          </Modal>
          <VStack space={8} alignItems="center">
            <Button w="104" onPress={() => {
              setShow(!show);
            }}>
              Sign In Modal
            </Button>
          </VStack>
          <ToggleDarkMode />
        </VStack>
        <SignInModal />

      </Center>
    </NativeBaseProvider>
  );
}

const SignInModal = () => {
  const [show, setShow] = React.useState(false);
  const toast = useToast();
  return <VStack space='3' >

    <Modal onClose={() => setShow(false)} isOpen={show}>
      <Modal.Content>
        <Modal.CloseButton />
        <Modal.Header>Sign In</Modal.Header>
        <Modal.Body>
          Please enter your credentials to proceed
          <FormControl mt={{
            'base': '3',
            'lg': '4',
            '2xl': '5',
          }}>
            <FormControl.Label>Email</FormControl.Label>
            <Input />
          </FormControl>
          <FormControl mt={{
            'base': '3',
            'lg': '4',
            '2xl': '5',
          }}>
            <FormControl.Label>Password</FormControl.Label>
            <Input />
          </FormControl>
        </Modal.Body>
        <Modal.Footer>
          <Button onPress={() => {
            toast.show({
              variant: "solid",
              description: "You are signed in!",
            })
            // setShow(false);
          }}>
            Sign In
          </Button>
        </Modal.Footer>
      </Modal.Content>
    </Modal>
    <VStack space={8} alignItems="center">
      <Button onPress={() => {
        setShow(!show);
      }}>
        Sign In Modal (component)
      </Button>
    </VStack>
  </VStack>;
}

function ToggleDarkMode() {
  const { colorMode, toggleColorMode } = useColorMode();
  return (
    <HStack space={2} alignItems="center">
      <Text>Dark</Text>
      <Switch
        isChecked={colorMode === "light"}
        onToggle={toggleColorMode}
        aria-label={
          colorMode === "light" ? "switch to dark mode" : "switch to light mode"
        }
      />
      <Text>Light</Text>
    </HStack>
  );
}

As you can see, useToast does not work in the default App function. I need the modal to be in App since the state variables must be passed to other screens. I couldn't find a way to solve this issue and wrote a similar component to toast, but it's not as efficient.

Viraj-10 commented 2 years ago

Hi All, Let me explain what's going on internally. On Android, we use RN Modal and on IOS we use Overlay Component which is View with absoluteFill Styling. Since IOS doesn't support multiple Modal we have to render an Overlay. Now, whenever you render RN modal on android it mounts on top of the application but toast is again an overlay. So it renders below RN Modal. Solution Complete remove RN Modal and use Overlay instead. (Difficulties Accessibility).

danimayfield commented 1 year ago

Is there any solution to this yet?

bilalalbkre commented 1 year ago

that solution worked in my case https://github.com/calintamas/react-native-toast-message/blob/main/docs/modal-usage.md just add "Toast" tag inside the Modal component

sturmenta commented 1 year ago

I've been struggling with this for a while and found a pretty acceptable solution! 🥳

Screenshot 2023-03-27 at 16 53 51

And the Overlay component receive a style prop

Screenshot 2023-03-27 at 17 06 41
const theme = extendTheme({
 {...}
  components: {
    Toast: {
      defaultProps: {
        _overlay: { style: { zIndex: 999 } }
      }
    }
  }
});

then, by passing a high zIndex, the toast is displayed above the Modal! 🎉

Screenshot 2023-03-27 at 17 08 41

note: using Modal & Toast both from native-base v3.4.23

astaninivan1 commented 1 year ago

"useRNModalOnAndroid: false", default for component "Modal" helped

const theme = extendTheme({
  components: {
    Modal: {
      defaultProps: {
        _overlay: {
          useRNModalOnAndroid: false,
        },
      },
    },
  }
});
sturmenta commented 1 year ago

"useRNModalOnAndroid: false", default for component "Modal" helped

const theme = extendTheme({
  components: {
    Modal: {
      defaultProps: {
        _overlay: {
          useRNModalOnAndroid: false,
        },
      },
    },
  }
});

your initial problem was in android or iOS?

tony95271 commented 1 year ago

calintamas/react-native-toast-message it is very simple and easy to use.

it's 2023, any updates to fix this bug?

Aryk commented 1 year ago

In short, no...they slowly (and quietly) moved on to another project - https://ui.gluestack.io/

levon-zakarian commented 11 months ago

After trying everything here and with no luck, I still kept struggling with this for a long time, and after checking how Toast works multiple times I've come across a workaround that finally worked for me.

So under the hood it is using useContext and there is a Provider that is also exported in native-base called ToastProvider, and when you use it Inside the Modal it works as expected.

Here's a sample code:

Container.tsx

import { useState } from "react";

// components
import { Button, Center, VStack, useToast } from "native-base";
import ModalToast from "./ModalToast";

const Container = () => {
  const toast = useToast();
  const [isOpen, setIsOpen] = useState(false);

  const closeModal = () => {
    setIsOpen(false);
  };

  const openModal = () => {
    setIsOpen(true);
  };

  const showToast = () => {
    toast.show({ title: "A Regular Toast In App" });
  };

  return (
    <Center flex={1}>
      <VStack space={5}>
        <Button onPress={openModal}>Open Modal</Button>
        <Button onPress={showToast}>Show Outer Toast</Button>
      </VStack>

      <ModalToast isOpen={isOpen} onClose={closeModal} />
    </Center>
  );
};

export default Container;

ModalToast.tsx

import {
  Button,
  Center,
  HStack,
  Modal,
  ToastProvider,
  useToast,
} from "native-base";

const ModalContent = ({ onClose }: { onClose: () => void }) => {
  const toast = useToast();

  const showToast = () => {
    toast.show({ title: "A Toast over the modal" });
  };

  return (
    <Center flex={1}>
      <HStack space={4}>
        <Button onPress={showToast}>Show Toast</Button>
        <Button onPress={onClose}>Close Modal</Button>
      </HStack>
    </Center>
  );
};

interface ModalToastProps {
  isOpen: boolean;
  onClose: () => void;
}

const ModalToast = ({ isOpen, onClose }: ModalToastProps) => (
  <Modal
    isOpen={isOpen}
    animationPreset="slide"
    bg="gray.200"
    onClose={onClose}
    safeAreaBottom
  >
    <ToastProvider>
      <ModalContent onClose={onClose} />
    </ToastProvider>
  </Modal>
);

export default ModalToast;

The only issue is that the App toast still stays under the modal, as the one inside the modal is another provider so it displays whole other Toast.

https://github.com/GeekyAnts/NativeBase/assets/86891301/7af924fd-6ff1-4d2a-8315-b2ba122b98a9

Using both Modal & Toast from native-base v3.4.28

Hope this will be of help.

jnariai commented 11 months ago

A workaround is to set RNModal to false

      <Modal
        _overlay={{
          useRNModal: false,
          useRNModalOnAndroid: false,
        }}
      >
soemarko commented 11 months ago

I mean, realistically, NativeBase is abandoned. I've just moved all Toast to react-native-toast-message. Bit by bit, I'm slowly removing native-base from my project.

I'll never go to gluestack if this how they handle projects. They'll abandon gluestack too as soon as a new shiny thing coming down the line.

iooi commented 11 months ago

Can we get the refund?

LordAsmodey commented 6 months ago

I have my own separate context provider for toasters, which wraps the parts of my application I need. And as I see, everything now works as it should on iOS devices. And for Android, the setup that astaninivan1 suggested helped me. https://github.com/GeekyAnts/NativeBase/issues/985#issuecomment-1511680753 But the fact that the NB has not been updated for a year makes me sad...