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.1k stars 2.37k 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)

shivrajkumar commented 7 years ago

@JLLLinn Check the latest of NativeBase i.e, v2.2.0

laxgoalie392 commented 6 years ago

Was this verified that it was fixed? I think I'm experiencing it as well. My issue is that the Modal is covering the Toast so it isn't visible.

I'm on RN 0.46.4 and native base 2.2.1 and using the ios emulator.

Thanks!

vovanmozg commented 6 years ago

Same problem. I'm on RN 0.46.1 and native base 2.2.1 and using the ios emulator.

mdebo commented 6 years ago

Exactly the same problem using RN 0.47 and NativeBase 2.2.1. Any news about it? thanks!

gianpaj commented 6 years ago

The Toast still appears behind the Modal (from React Native):

toast behind modal

the code is basically this:

import React from 'react';
import {
  Modal,
  Toast,
  Text,
  View,
} from 'react-native';

export default class LoginScreen extends React.Component<Props, State> {
  state = {
    modalVisible: false,
  }

  onResetPassword() {
    Toast.show({
      text: message,
      duration: 2000,
      position: "top",
      textStyle: { textAlign: "center" },
    });
  }    

  render() {
    return (
      <View>
        <Modal
          animationType="slide"
          transparent={false}
          visible={this.state.modalVisible}
          >
          <View>...</View>
        </Modal>
      </View>
    )
  }
}

Any suggestions?

I see there's something called zIndex as Layout Props - React Native

Using:

$ yarn list --depth=0 | grep 'native-base@\| react-native@'
├─ native-base@2.3.3
├─ react-native@0.50.3

Can something be done to the ToastContainer.js? https://github.com/GeekyAnts/NativeBase/blob/master/src/basic/ToastContainer.js

SupriyaKalghatgi commented 6 years ago

@gianpaj Looks like you are using React-Native-Seed boilerplate. Nice! We will check this issue

idrakimuhamad commented 6 years ago

I just want to add that I’m having this issue too with v 2.3.3

gianpaj commented 6 years ago

FYI: this issue happens both on Android and iOS

supercamilo commented 6 years ago

Still present on 2.3.5

gianpaj commented 6 years ago

I think I'm just going to use the Toast from ant-design-mobile.

My bad. Ant-Design Toast doesn't work. It also appears under the react-native Modal

idrakimuhamad commented 6 years ago

@gianpaj I'm also using antd-mobile for the datepicker. Perhaps I should take a look at their toast implementation too.

SupriyaKalghatgi commented 6 years ago

@gianpaj What was the purpose of sharing graphs for commit-activity?

ThomasLabstep commented 6 years ago

Any hotfix for now with v2.3.10 ?

AdrianMrn commented 6 years ago

Any chance this will be fixed anytime soon?

N9vn1njdf commented 6 years ago

Сталкивался с этой проблемой недавно, решил следующим способом. Просто добавляем в Modal элемент Root, а в него весь контент который вам нужен

P.S. на английский переводить лень, главное ведь код

translation : Faced this problem recently, I decided in the following way. Just add the Root element to the Modal, and all the content you need is in it

import { Root, Container, Content } from 'native-base';

<Modal>
    <Root>
        <Container>
             <Content>
                  // .. modal content with toast
             </Content>
        </Container>
    </Root>
</Modal>
doreentseng commented 6 years ago

@Zeratyll Hey! It doesn't work for me at all, the toast still appears behind the Modal.

michaellee8 commented 6 years ago

@doreentseng Tried @Zeratyll 's solution of putting <Root/> into <Modal/>, but it still didn't work. @gianpaj Also tried to put style : { zIndex: 1000 } into Toast.show(), no effect as well. All of the results are tested in a real iPad. Tried Modal component from both original react-native and react-native-community, both of them didn't work. Will native-base provide its own modal component? @SupriyaKalghatgi @shivrajkumar Dear collaborators, when can we get an fix?

akhil-ga commented 6 years ago

checked this issue after merging PR https://github.com/GeekyAnts/NativeBase/pull/1700. Placing Root inside a modal component as suggested by @Zeratyll https://github.com/GeekyAnts/NativeBase/issues/985#issuecomment-383596951 is working after merging. See attached Gif.

Sample code

import React from 'react';
import { Modal } from "react-native";
import { createStackNavigator } from 'react-navigation';
import { Root, Container, Text, Header, Left, Body, Right, Title, Content, Button, Toast } from 'native-base';

class HomeScreen extends React.Component {

  state = { modalVisible: false }

  render() {
    return (
      <Container>
        <Header>
          <Left />
          <Body>
            <Title>Home screen</Title>
          </Body>
          <Right />
        </Header>

        <Content>
          <Button onPress={() => this.setState({ modalVisible: !this.state.modalVisible })} style={{ margin: 20 }}>
            <Text>Show modal</Text>
          </Button>
        </Content>

        <Modal
          animationType="slide"
          transparent={false}
          visible={this.state.modalVisible}>
          <Root>
            <Container style={{ padding: 20 }}>
              <Button style={{ margin: 20 }} onPress={() => Toast.show({
                text: 'Wrong password!',
                buttonText: 'Okay'
              })}>
                <Text>Show toast</Text>
              </Button>
              <Button style={{ margin: 20 }} onPress={() => this.setState({ modalVisible: !this.state.modalVisible })}>
                <Text>Hide modal</Text>
              </Button>
            </Container>
          </Root>
        </Modal>
      </Container>
    );
  }
}

const App = createStackNavigator({
  Home: {
    screen: HomeScreen
  },
}, {
    navigationOptions: {
      header: null
    }
  });

export default () => <Root>
  <App />
</Root>

Gif

toast-modal

Edit : As @gianpaj pointed out this may not be a viable option. Please see the below comment.

gianpaj commented 6 years ago

Placing Root inside a modal component as suggested ... is working fine @akhil-geekyants

This is not ideal or neither practical.

The state of Modal(s) should not be at the Root of an application. It should be where the Modal needs to appear, i.e. inside a screen/component/container.

If we had to do the way you suggest, the state of the Modal needs to pass down all the way to the Component in need (via props or in order ways).

I have to say this is not a viable option for any decent size application.

I don't know at the moment how to solve this, but pretty sure that's not it. Happy to be corrected if I'm wrong.

akhil-ga commented 6 years ago

@gianpaj updated my comment.

JeremyBradshaw7 commented 6 years ago

Also having this problem. The Root workaround does work but also has an unwanted side-effect that windowed (non-fullscreen) modals no longer appear vertically centered, and no JSX adjustments appear to help.

lewisflude commented 5 years ago

The above Root fix didn't work for me as I'd like it to, as it completely ruins the vertical centring of the Toast

Aleksandern commented 5 years ago

My workaround for modals.

scene.js

<Scene>
  <ModalComponent />
</Scene>

ModalComponent.js

import Toast from 'mypathto/Toast';
class ModalComponent Component {

    showToast() {
        Toast.show({
          text: 'modal toast',
          position: 'bottom',
          duration: 3000
        });
    }

    render() {
        return (
            <Modal>
                <View>
                    <Button onPress={()=>this.showToast()}><Text>Toast<Text></Button>

                    <Toast
                      ref={c => {
                        if (c) Toast.toastInstance = c;
                      }}
                    />

                </View>
            </Modal>
        )
    }
}

mypathto/Toast.js

import {
  Toast as ToastNB,
} from "native-base";

class Toast extends ToastNB {
}

export default Toast;
tmtung144 commented 5 years ago

Сталкивался с этой проблемой недавно, решил следующим способом. Просто добавляем в Modal элемент Root, а в него весь контент который вам нужен

P.S. на английский переводить лень, главное ведь код

translation : Faced this problem recently, I decided in the following way. Just add the Root element to the Modal, and all the content you need is in it

import { Root, Container, Content } from 'native-base';

<Modal>
    <Root>
        <Container>
             <Content>
                  // .. modal content with toast
             </Content>
        </Container>
    </Root>
</Modal>

Very good, it works with me

AonanLi commented 5 years ago

My workaround for modals.

scene.js

<Scene>
  <ModalComponent />
</Scene>

ModalComponent.js

import Toast from 'mypathto/Toast';
class ModalComponent Component {

    showToast() {
        Toast.show({
          text: 'modal toast',
          position: 'bottom',
          duration: 3000
        });
    }

    render() {
        return (
            <Modal>
                <View>
                    <Button onPress={()=>this.showToast()}><Text>Toast<Text></Button>

                    <Toast
                      ref={c => {
                        if (c) Toast.toastInstance = c;
                      }}
                    />

                </View>
            </Modal>
        )
    }
}

mypathto/Toast.js

import {
  Toast as ToastNB,
} from "native-base";

class Toast extends ToastNB {
}

export default Toast;

Works for me, Nice Solution

filippoitaliano commented 5 years ago

My workaround for modals.

scene.js

<Scene>
  <ModalComponent />
</Scene>

ModalComponent.js

import Toast from 'mypathto/Toast';
class ModalComponent Component {

    showToast() {
        Toast.show({
          text: 'modal toast',
          position: 'bottom',
          duration: 3000
        });
    }

    render() {
        return (
            <Modal>
                <View>
                    <Button onPress={()=>this.showToast()}><Text>Toast<Text></Button>

                    <Toast
                      ref={c => {
                        if (c) Toast.toastInstance = c;
                      }}
                    />

                </View>
            </Modal>
        )
    }
}

mypathto/Toast.js

import {
  Toast as ToastNB,
} from "native-base";

class Toast extends ToastNB {
}

export default Toast;

It's necessary to extend the Toast? I tried the trick directly with NB export and it worked.

EDIT: Yes, it's necessary beacuse the ref to the root is the same for all present and future Toasts (Without extending the Toast, it will try to render within the modal even if it doesn't exist anymore).

jayan2019 commented 5 years ago

onPress={()=>this.setState({ toastVisible: !this.state.toastVisible },() =>Toast.show({ text: 'Wrong password!', buttonText: 'Okay' }))}

This code is working for me. Thanks @akhil-geekyants. This comment helped me https://github.com/GeekyAnts/NativeBase/issues/985#issuecomment-398688935

suvenduchhatoi commented 5 years ago

As mentioned in NB Docs https://docs.nativebase.io/Components.html#toast-def-headref - For Toast to work, you need to wrap your topmost component inside <Root> from native-base. ,the solution is to wrap Toast with Root within Modal, which has already been found in discussion above...

artshevtsov commented 5 years ago

Wrapping Modal content with Root solved the problem with z-indexing Toast, but this problem appears next https://github.com/GeekyAnts/NativeBase/issues/937

Jhony-Reyes commented 4 years ago

In my case I use Modal as component and the Toast is being shown after executing an async function, this works for me:

Toast:

import { Toast as ToastNB } from "native-base";

class Toast extends ToastNB {
}

export default Toast;

Modal Component:

import Toast from 'path/toast';

const ModalContainer = ({
  visible, onClose, children, onShow,
}) => (
  <Modal
    visible={visible}
    onRequestClose={onClose}
    onShow={() => onShow(Toast)}
  >
    <View>
      {children}
    </View>
    <Toast
      ref={(t) => {
        Toast.toastInstance = t;
      }}
    />
  </Modal>
);

Usign the Modal Component:

import ModalContainer from 'path/modalContainer/;

class SomeClass Component {

    showToast() {
       add1(10).then(() =>  this.toastInstance.show({
          text: 'modal toast',
          position: 'bottom',
          duration: 3000
        }));
    }

    render() {
        return (
         <ModalContainer onShow={(e) => { this.toastInstance = e; }}>
              <Text>Hi</Text>
         </ModalContainer>
        )
    }
}
summerkiflain commented 4 years ago

Why can't we add containerStyle to Toast.show() method? all of above solutions seem very unnecessary.

CyberCyclone commented 4 years ago

I think a better solution is to define which component the Toast appears in. Or is it assumed that the top most is the one that it's attached to?

cristianoccazinsp commented 4 years ago

Please re-open this.

Putting a Root component inside Modal does work; however, if you ever try to show another toast (let's say you had a root in your main - non-modal - component), then trying to show another toast will horribly crash because the instance will no longer be available.

PR here: https://github.com/GeekyAnts/NativeBase/pull/3018 Basically, it allows for multiple Root elements (allowing us to use extra Roots on Modals without breaking everything)

HeyShinner commented 4 years ago

My workaround for modals.

scene.js

<Scene>
  <ModalComponent />
</Scene>

ModalComponent.js

import Toast from 'mypathto/Toast';
class ModalComponent Component {

    showToast() {
        Toast.show({
          text: 'modal toast',
          position: 'bottom',
          duration: 3000
        });
    }

    render() {
        return (
            <Modal>
                <View>
                    <Button onPress={()=>this.showToast()}><Text>Toast<Text></Button>

                    <Toast
                      ref={c => {
                        if (c) Toast.toastInstance = c;
                      }}
                    />

                </View>
            </Modal>
        )
    }
}

mypathto/Toast.js

import {
  Toast as ToastNB,
} from "native-base";

class Toast extends ToastNB {
}

export default Toast;

Actually it works for me, but Toast should display on the top of the Login page instead of the top of the config Modal:

image

This is My code:

image

Aryk commented 4 years ago

I tried using this but ran into an issue on react-navigation.

If you click to a page, and then click back...the re-render won't trigger and the ref won't get reset and then you'll get the null toastInstance error.

So, I needed to add a useFocusEffect to reset the instance when it comes back to that screen.

Here is my solution based on the one below...so far seems to be working. Also, I'm not using "Modal", but using instead transparentModal and normal screens in the StackNavigator from react-native-screens but same issue....


// Solution based on the one here: https://github.com/GeekyAnts/NativeBase/issues/985#issuecomment-411379940
import {Container as ContainerNB, NativeBase, Toast as ToastNB} from "native-base";
import {useFocusEffect} from "@react-navigation/native";

class Toast extends ToastNB {}

const Container = ({children, ...props}: NativeBase.Container & {children: any}) => {

  const toastInstance = useRef();

  useFocusEffect(useCallback(() => {
    if (toastInstance.current && toastInstance !== (Toast as any).toastInstance) {
      (Toast as any).toastInstance = toastInstance.current;
    }
  }, []));

  return <ContainerNB {...props}>
    {/*Needs to be first*/}
    {children}
    {/*// @ts-ignore*/}
    <Toast
      ref={c => {
        if (c) {
          (Toast as any).toastInstance = c;
          toastInstance.current = c;
        }
      }}
    />
  </ContainerNB>;
};

My workaround for modals.

scene.js

<Scene>
  <ModalComponent />
</Scene>

ModalComponent.js

import Toast from 'mypathto/Toast';
class ModalComponent Component {

    showToast() {
        Toast.show({
          text: 'modal toast',
          position: 'bottom',
          duration: 3000
        });
    }

    render() {
        return (
            <Modal>
                <View>
                    <Button onPress={()=>this.showToast()}><Text>Toast<Text></Button>

                    <Toast
                      ref={c => {
                        if (c) Toast.toastInstance = c;
                      }}
                    />

                </View>
            </Modal>
        )
    }
}

mypathto/Toast.js

import {
  Toast as ToastNB,
} from "native-base";

class Toast extends ToastNB {
}

export default Toast;
HeyShinner commented 4 years ago

I tried using this but ran into an issue on react-navigation.

If you click to a page, and then click back...the re-render won't trigger and the ref won't get reset and then you'll get the null toastInstance error.

So, I needed to add a useFocusEffect to reset the instance when it comes back to that screen.

Here is my solution based on the one below...so far seems to be working. Also, I'm not using "Modal", but using instead transparentModal and normal screens in the StackNavigator from react-native-screens but same issue....

// Solution based on the one here: https://github.com/GeekyAnts/NativeBase/issues/985#issuecomment-411379940
import {Container as ContainerNB, NativeBase, Toast as ToastNB} from "native-base";
import {useFocusEffect} from "@react-navigation/native";

class Toast extends ToastNB {}

const Container = ({children, ...props}: NativeBase.Container & {children: any}) => {

  const toastInstance = useRef();

  useFocusEffect(useCallback(() => {
    if (toastInstance.current && toastInstance !== (Toast as any).toastInstance) {
      (Toast as any).toastInstance = toastInstance.current;
    }
  }, []));

  return <ContainerNB {...props}>
    {/*Needs to be first*/}
    {children}
    {/*// @ts-ignore*/}
    <Toast
      ref={c => {
        if (c) {
          (Toast as any).toastInstance = c;
          toastInstance.current = c;
        }
      }}
    />
  </ContainerNB>;
};

My workaround for modals. scene.js

<Scene>
  <ModalComponent />
</Scene>

ModalComponent.js

import Toast from 'mypathto/Toast';
class ModalComponent Component {

    showToast() {
        Toast.show({
          text: 'modal toast',
          position: 'bottom',
          duration: 3000
        });
    }

    render() {
        return (
            <Modal>
                <View>
                    <Button onPress={()=>this.showToast()}><Text>Toast<Text></Button>

                    <Toast
                      ref={c => {
                        if (c) Toast.toastInstance = c;
                      }}
                    />

                </View>
            </Modal>
        )
    }
}

mypathto/Toast.js

import {
  Toast as ToastNB,
} from "native-base";

class Toast extends ToastNB {
}

export default Toast;

Got it! I will give my feedback after I try this nice solution!

Rc85 commented 3 years ago

As mentioned in NB Docs https://docs.nativebase.io/Components.html#toast-def-headref - For Toast to work, you need to wrap your topmost component inside <Root> from native-base. ,the solution is to wrap Toast with Root within Modal, which has already been found in discussion above...

I can confirm what @cristianoccazinsp is true. The instance is no longer available after the Toast in Modal hides.

null is not an object (evaluating
'this.toastInstance.root.showToast')
emil-keyvalue commented 3 years ago

@SupriyaKalghatgi The issue is still not solved. Why haven't you added support for multiple roots yet

denven commented 3 years ago

I find this exists in 2021, I just get it shown up via adding a delay:

         setTimeout(() => Toast.show('Your message here') , 500);
pvshum commented 2 years ago

@denven I don't think that the issue is about timing. It looks more like a z-index issue. Toast appearing under the modal.

dexterowy commented 2 years ago

After 4 years toast still appears under the modal. Also, Root component was removed. Could you fix that?

garrettg123 commented 2 years ago

This should be reopened

amitdninawe commented 2 years ago

Adding a blank Toast <Toast /> inside the modal view works. I have tested this in android only. Please give it a shot and let me know

rs6g10 commented 2 years ago

Neither <Toast /> nor Root exist as components in version 3.2.2. There aren't any better workarounds for this. Can we get this issue reopened please?

rs6g10 commented 2 years ago

Since Root no longer exists, a quick workaround is wrapping the entire Modal content in NativeBaseProvider.

I'm using the following workaround for version 3.2.2 to see toasts in my react-native-modal:

<RNModal
   isVisible={modalVisible}
   deviceWidth={deviceWidth}
   deviceHeight={deviceHeight}
   coverScreen={true}
   style={styles.modal}
    >
      <NativeBaseProvider>
        <View style={styles.modalHeader}>
          <TouchableOpacity
            style={{ marginTop: 48 }}
          >
            <Icon
              name='close'
              as={MaterialCommunityIcons}
              onPress={onClose}
              size={12}
              style={{
                right: 16,
                alignSelf: 'flex-end',
              }}
            />
          </TouchableOpacity>
          <View style={styles.modalBody}>
            {children}
          </View>
        </View>
      </NativeBaseProvider>
</RNModal>
md-rehman commented 2 years ago

I'm reopening this issue, and we'll keep it on high priority.

Drakeoon commented 2 years ago

@MD-REHMAN I would also love this feature to be back, ideally a separate API to configure Toast's root element.

Have you guys started working on it already? I would love to help or take care of that issue, so that we can have it back on our project 💪🏻

masrurimz commented 2 years ago

Since Root no longer exists, a quick workaround is wrapping the entire Modal content in NativeBaseProvider.

I'm using the following workaround for version 3.2.2 to see toasts in my react-native-modal:

<RNModal
   isVisible={modalVisible}
   deviceWidth={deviceWidth}
   deviceHeight={deviceHeight}
   coverScreen={true}
   style={styles.modal}
    >
      <NativeBaseProvider>
        <View style={styles.modalHeader}>
          <TouchableOpacity
            style={{ marginTop: 48 }}
          >
            <Icon
              name='close'
              as={MaterialCommunityIcons}
              onPress={onClose}
              size={12}
              style={{
                right: 16,
                alignSelf: 'flex-end',
              }}
            />
          </TouchableOpacity>
          <View style={styles.modalBody}>
            {children}
          </View>
        </View>
      </NativeBaseProvider>
</RNModal>

Could you provide example to customize default theme of native base modal, action sheet and other overlay component so, toast will be appeared on the top of it

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Oct4Pie commented 2 years ago

I have the same issue. The toast doesn’t appear on the modal. No solutions yet.