kirillzyusko / react-native-keyboard-controller

Keyboard manager which works in identical way on both iOS and Android
https://kirillzyusko.github.io/react-native-keyboard-controller/
MIT License
1.61k stars 67 forks source link

KeyboardToolbar unusable when used inside RN Modal #588

Open aaamoshd opened 1 week ago

aaamoshd commented 1 week ago

Describe the bug When KeyboardToolbar component is used inside a react-native modal the next, prev and done buttons become un-clickable and when clicked just closes the keyboard. No matter of z-index in the button props seem to be doing the trick.

Code snippet

<Modal
  presentationStyle="formSheet"
  onRequestClose={onClose}
  visible={open}
  transparent={false}
>
  <KeyboardAvoidingView
      behavior="padding"
      keyboardVerticalOffset={100}
      style={{ flex: 1, paddingHorizontal: 16 }}
  >
      <Input
          required
          onChangeText={(val) =>
              setValue({
                  ...value,
                  data: val,
              })
          }
          value={value.data}
      />
      <Button color="tertiary" onPress={handleSubmit}>
          <Typography
              color="primary"
              weight="600"
              align="center"
              style={{
                  textTransform: 'uppercase',
                  width: '100%',
              }}
          >
              {localize.translate('add')}
          </Typography>
      </Button>
   </KeyboardAvoidingView>
    <KeyboardToolbar
        button={(props) => (
            <Button {...props} style={{ zIndex: 1000 }} />
        )}
    />
</Modal>

Repo for reproducing Sorry since this is an internal project, I am unable to provide you with the reproducing repo. However, if you install the package, wrap app with and then open a modal with the component from this library along with KeyboardToolbar the navigation buttons become unusable.

To Reproduce Steps to reproduce the behavior:

  1. Create a new RN project
  2. Install react-native-keyboard-controller
  3. Wrap your app with KeyboardProvider
  4. On one of the screen use Modal to show form fields and inject KeyboardToolbar
  5. Try clicking on the toolbar next, prev and done button.

Expected behavior To be able to click on the toolbar to be able to navigate form fields.

Screenshots MyMovie1-ezgif com-video-to-gif-converter

Smartphone (please complete the following information):

Additional context The library is super awesome. It works on all the other screens apart from when being used inside a Modal.

kirillzyusko commented 1 week ago

@aaamoshd just for a context - it doesn't work on iOS only or both iOS and Android?

aaamoshd commented 1 week ago

@kirillzyusko for me it does not work on both iOS and Android. Sorry missed that in the report.

kirillzyusko commented 1 week ago

@aaamoshd I'm afraid I need a reproduction repo. I've added a Modal in my code and it works well on iOS:

https://github.com/user-attachments/assets/305ef039-eb58-46ed-86fc-8d8a0e0d12c9

On Android it indeed doesn't work (focus can not be set 🤷‍♂️ )

Do you have the same problem?

aaamoshd commented 1 week ago

wow that is one weird behavior. So I do have SafeAreaProvider and SafeAreaView wrapping my KeyboardProvider. Is this the result of not using KeyboardProvider as the root wrapper? You are able to easily navigate however whenever I try navigating, all it does is close my keyboard. Also I wanted to know if this example is based on react-navigation and setting screen's presentation as modal.

aaamoshd commented 1 week ago

hi @kirillzyusko,

I got the library working on iOS by adding the multiline property to the inputs and making the prop dependent on a state variable within the RN Modal. However, I'm still running into issues with Android and haven’t had any luck so far. I was hoping you could help me understand why adding this change made things work on iOS but not Android.

Additionally, if we create a screen and set its presentation to 'modal' (instead of using RN Modal), the toolbar works on both iOS and Android. My assumption is that since it's just a screen and not a true Modal, this is why it works. Could the library potentially need something similar to how react-native-gesture-handler wraps components in a HOC for Android Modals?

One more thing I noticed on iOS is that the toolbar doesn't stick perfectly to the top of the keyboard—it sits slightly higher. I suspect it might be a configuration issue on my end, but I'd really appreciate it if you could point me in the right direction.

Below is the behavior of the toolbar comparing Android and iOS.

Android Toolbar (Works Fine) Screenshot_1726767077

iOS Toolbar is slightly higher and is not sticky to the view. Simulator Screenshot - iPhone 15 Pro Max - 2024-09-19 at 10 31 24

Thanks again for your time and for the great work!

kirillzyusko commented 1 week ago

However, I'm still running into issues with Android and haven’t had any luck so far. I was hoping you could help me understand why adding this change made things work on iOS but not Android.

Well, if you can provide a minimal reproduction example - then I'll prioritise this issue over others and will try to fix it ASAP. Without a proper reproduction repository/code I feel like I may do something wrong and just waste time and not reproduce it at all 😔

Additionally, if we create a screen and set its presentation to 'modal' (instead of using RN Modal), the toolbar works on both iOS and Android. My assumption is that since it's just a screen and not a true Modal, this is why it works.

Yeah, you are right. react-navigation most likely just uses internal replica and doesn't use Modal component.

Could the library potentially need something similar to how react-native-gesture-handler wraps components in a HOC for Android Modals?

Well, if you can discover a focused input (and on Android you can easily do that by setting up a global listener), then you should be able to find sibling elements as well, so I believe there is no need to wrap a Modal. Moreover this library tracks all events when Modal becomes shown, so I may inject something without additional user actions 🤫

One more thing I noticed on iOS is that the toolbar doesn't stick perfectly to the top of the keyboard—it sits slightly higher. I suspect it might be a configuration issue on my end, but I'd really appreciate it if you could point me in the right direction.

This component should be placed straight behind the bottom corner (where keyboard starts its movement) of the screen, i. e. ignoring safe area insets. On you screen I believe it's placed behind bottom safe area inset. So basically you have two options:


If you can prepare a simple reproduction example and show a case when both iOS and Android doesn't work properly, then I promise I'll try to fix it in nearest time 🙏

I understand that the scenario for reproduction seems to be a very simple, but again, in my case only Android/fabric is reproducible. And I have a gut feeling that even if I may fix a problem on my end it doesn't mean that it'll be fixed for your use case. So the reproduction example is highly appreciated and can significantly reduce a time for fixing this problem! ❤️

P. S.

Also I wanted to know if this example is based on react-navigation and setting screen's presentation as modal.

No, I used Modal component from react-native. Do you use the same component when it breaks in your case?

kirillzyusko commented 1 week ago

@aaamoshd can you please check if https://github.com/kirillzyusko/react-native-keyboard-controller/pull/590 fixes the problem on Android?

aaamoshd commented 1 week ago

Hi @kirillzyusko I just tried using #590 on my project but still facing the same issue on android where <KeyboardToolbar /> can not be used when opened in a Modal component. I was wondering as to how I could provide you with my problem repository but since it has a lots of custom module, custom components it might be difficult to create a reproduction. Any insight on what particular setting or configuration would be most useful and maybe we can plan accordingly.

Also interestingly enough, ran into similar bug as of #387 in android, where once I open a keyboard on a screen and then try to open a Modal it was crashing the app or the modal would make the app unusable. The Modal would be behind the current screen but then the entire app would just freeze. But once the utility function was added to wait for the keyboard close before opening a modal and then wrapped the modal within View fixed the issue for me.

kirillzyusko commented 1 week ago

I just tried using https://github.com/kirillzyusko/react-native-keyboard-controller/pull/590 on my project but still facing the same issue on android where can not be used when opened in a Modal component.

Well, yeah, that's hat I wrote 😔 :

And I have a gut feeling that even if I may fix a problem on my end it doesn't mean that it'll be fixed for your use case.

I was wondering as to how I could provide you with my problem repository but since it has a lots of custom module, custom components it might be difficult to create a reproduction. Any insight on what particular setting or configuration would be most useful and maybe we can plan accordingly.

Well, if I were you I would try to remove components one by one, and replace all wrappers with plain RN components (i. e. Typography with Text etc.). In the end you will get a code sample, that will not rely on navigation, etc. and this code snippet can be simply pasted in App.tsx and it should be reproducible in any empty project. If you can give me such code sample then I can try to put it in my example project and see how I can reproduce the problem.

Also interestingly enough, ran into similar bug as of https://github.com/kirillzyusko/react-native-keyboard-controller/issues/387 in android, where once I open a keyboard on a screen and then try to open a Modal it was crashing the app or the modal would make the app unusable. The Modal would be behind the current screen but then the entire app would just freeze. But once the utility function was added to wait for the keyboard close before opening a modal and then wrapped the modal within View fixed the issue for me.

Well, this one is also interesting. I fixed an integration with Modal and added a test screen in example app and covered it by e2e tests. Basically the Modal should work 🤷‍♂️ It's very hard to say what exactly goes in a wrong direction just because Modal involves too many things in one component, so reproduction example can shed some light on the problem 👀 But if you decide to post the repro for Modal problem, then please - open a new issue. This problem sounds slightly different to a current one, and I'd like to keep separated problems separately!

aaamoshd commented 6 days ago

hi @kirillzyusko I think you may have fixed the issue regarding toolbar and android when used inside the modal. I was trying to create a minimal reproduction repository and saw that the package worked for both android and iOS. So got interested into seeing why there was an issue on my project. I cleared all the cache and performed a clean install of the fix #590. Found out that the issue was with the screen where the modal was being injected. Turns out very first <ScrollView> needs to have keyboardShouldPersistTaps={'always'} and keyboardShouldPersistTaps={'handled'} on the <KeyboardAwareScrollView> inside the <Modal> component.

Essentially, Screen index:

<ScrollView keyboardShouldPersistTaps={'always'}>
{... other UI components}
<Modal isVisible={open} {...}>
<SafeAreaView>
    <KeyboardAwareScrollView keyboardShouldPersistTaps={'handled'}>
    </KeyboardAwareScrollView>
</SafeAreaView>
<KeyboardToolbar /> //this needs to be here injected to the modal directly
</Modal>
</ScrollView>

However, I do not have issues with forms that are directly on the screens and they work without having to use the prop keyboardShouldPersistTaps.

As far for the other issue with opening modal while keyboard is open, I wrote a wrapper component for my modals and set a watcher on the isVisible property. Then on android, I call the utility function mentioned in #387 to close the keyboard before opening the modal. Everything seems to be working fine.

kirillzyusko commented 4 days ago

I see @aaamoshd

So to sum it up:

As far for the other issue with opening modal while keyboard is open, I wrote a wrapper component for my modals and set a watcher on the isVisible property.

What is the problem? That Modal is not stretched to full window screen? I. e. you see something like:

image
aaamoshd commented 2 days ago

Hi @kirillzyusko: Yup, at least thats what my analysis resulted in terms of the Keyboard Toolbar behavior inside a modal.

Or if possible try to show Modal not inside the ScrollView

Most of my screens follow a certain layout and one of the component defined in the layout is a <ScrollView>. It is essential for the app to have that layout which means the logic to display a modal is defined inside the children passed in the layout.

Also, looks like for the android even with the #590 fix, we need to dirty the form once (either type in the input or click on a different input once the keyboard opens) for it to allow navigation otherwise it does not focus properly. Once I dirty an input that opened the keyboard I can navigate using the toolbar. any idea as to why its happening? It only happens when using a release build on a physical device, on a simulator it works just fine. Also, looks like it only happens first time you open the app and try using the keyboard.

What is the problem? That Modal is not stretched to full window screen? I. e. you see something like:

Yeah, I think thats what's happening, Keyboard would close and I wouldn't even see the modal but I know the component was made visible just the whole screen would freeze and I would have to close out the app.

aaamoshd commented 2 days ago

As a workaround I tried passing a custom button props as

<KeyboardToolbar
  button={(props) => (
      <TouchableOpacity
          {...props}
          onPress={undefined}
          onPressIn={props.onPress}
      />
  )}
/>

and it worked out. So, thinking this has to do again with ScrollView and it managing taps.