Nativebase: [Bug] Toast is not working within a Modal

Created on 26 Jun 2017  ·  38Comments  ·  Source: GeekyAnts/NativeBase

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)

bug

Most helpful comment

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.

All 38 comments

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

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!

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

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

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@'
├─ [email protected]
├─ [email protected]

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

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

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

FYI: this issue happens both on Android and iOS

Still present on 2.3.5

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

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

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

Any hotfix for now with v2.3.10 ?

Any chance this will be fixed anytime soon?

Сталкивался с этой проблемой недавно, решил следующим способом. Просто добавляем в 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>

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

@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?

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.

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.

@gianpaj updated my comment.

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.

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

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;

Сталкивался с этой проблемой недавно, решил следующим способом. Просто добавляем в 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

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

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).

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

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...

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

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>
        )
    }
}

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

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?

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)

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

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;

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!

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')

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bsiddiqui picture bsiddiqui  ·  3Comments

agersoncgps picture agersoncgps  ·  3Comments

aloifolia picture aloifolia  ·  3Comments

maphongba008 picture maphongba008  ·  3Comments

elnygren picture elnygren  ·  3Comments