Skip to content

RCTBridge parentBridge is nil when presenting custom keyboard via presentCustomInputComponent #3778

Open
@toandn-salto

Description

@toandn-salto

Description

When calling presentCustomInputComponent in iOS native code to display a custom keyboard, the following line in Objective-C:

RCTBridge* bridge = [self.bridge valueForKey:@"parentBridge"];

Related to

  • Components
  • Demo
  • Docs
  • Typings

Steps to reproduce

  1. Register a keyboard component using KeyboardRegistry.registerKeyboard("unicorn.ImagesKeyboard", () => ImagesKeyboard).
  2. Call CustomInputControllerTemp.presentCustomInputComponent(...) from JS.
  3. In native code (presentCustomInputComponent), parentBridge is nil.
  4. The keyboard view shows but is blank (white).

Expected behavior

Actual behavior

More Info

Code snippet

import React from "react"
import { ScrollView, StyleProp, ViewStyle } from "react-native"
import { Keyboard, View, Text, Image, Spacings } from "react-native-ui-lib"
import _ from "lodash"

const KeyboardRegistry = Keyboard.KeyboardRegistry

const images: string[] = [
  "https://images.pexels.com/photos/1148521/pexels-photo-1148521.jpeg?auto=compress&cs=tinysrgb&dpr=1&h=200",
  "https://images.pexels.com/photos/1528975/pexels-photo-1528975.jpeg?auto=compress&cs=tinysrgb&dpr=1&h=200",
  "https://images.pexels.com/photos/1495580/pexels-photo-1495580.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=200",
  "https://images.pexels.com/photos/943150/pexels-photo-943150.jpeg?auto=compress&cs=tinysrgb&dpr=1&h=200",
  "https://images.pexels.com/photos/1769408/pexels-photo-1769408.jpeg?auto=compress&cs=tinysrgb&dpr=1&h=200",
]

function ImagesKeyboard() {
  return (
    <View flex>
      <ScrollView
        horizontal
        showsHorizontalScrollIndicator={false}
        contentContainerStyle={{ padding: Spacings.s4 } as StyleProp<ViewStyle>}
      >
        {images.map((image, i) => (
          <Image
            key={i}
            source={{ uri: image }}
            style={{ height: "100%", width: 200 }}
            marginR-s4
          />
        ))}
      </ScrollView>
    </View>
  )
}

function CustomKeyboard() {
  return (
    <View flex bg-violet80 center>
      <Text h2>Custom Keyboard</Text>
    </View>
  )
}

// Register keyboards with registry
KeyboardRegistry.registerKeyboard("unicorn.ImagesKeyboard", () => ImagesKeyboard())
KeyboardRegistry.registerKeyboard("unicorn.CustomKeyboard", () => CustomKeyboard())


import { AppStackScreenProps } from "@/navigators"
import { observer } from "mobx-react-lite"
import React, { useCallback, useRef, useState } from "react"
import { StyleSheet, TextInput } from "react-native"
import {
  Button,
  Colors,
  Constants,
  Keyboard,
  Spacings,
  Switch,
  Text,
  View,
} from "react-native-ui-lib"

import "./CustomKeyboard"
const KeyboardAccessoryView = Keyboard.KeyboardAccessoryView

const KeyboardUtils = Keyboard.KeyboardUtils
const KeyboardRegistry = Keyboard.KeyboardRegistry
const TrackInteractive = true

const demoKeyboards = [
  {
    id: "unicorn.ImagesKeyboard",
  },
  {
    id: "unicorn.CustomKeyboard",
  },
]

export const ConversationDetailScreen = observer(
  ({ route }: AppStackScreenProps<"ConversationDetail">) => {
    const [customKeyboard, setCustomKeyboard] = useState<any>({})
    const [receivedKeyboardData, setReceivedKeyboardData] = useState<string | undefined>()
    const [useSafeArea, setUseSafeArea] = useState<boolean>(true)
    const [keyboardOpenState, setKeyboardOpenState] = useState<boolean>(false)
    const [keyboardAccessoryViewHeight, setKeyboardAccessoryViewHeight] = useState<
      number | undefined
    >()
    const textInputRef = useRef(null)
    const textInputRef2 = useRef(null)

    const onKeyboardItemSelected = useCallback((component?: string, args?: any) => {
      const receivedData = `onItemSelected from "${component}"\nreceived params: ${JSON.stringify(args)}`
      setReceivedKeyboardData(receivedData)
    }, [])

    const onKeyboardResigned = useCallback(() => {
      resetKeyboardView()
    }, [])

    const isCustomKeyboardOpen = useCallback(() => {
      return keyboardOpenState && !_.isEmpty(customKeyboard)
    }, [keyboardOpenState, customKeyboard])

    const resetKeyboardView = useCallback(() => {
      setCustomKeyboard({})
    }, [])

    const dismissKeyboard = useCallback(() => {
      KeyboardUtils.dismiss()
    }, [])

    const toggleUseSafeArea = useCallback(() => {
      setUseSafeArea((prev) => !prev)

      if (isCustomKeyboardOpen()) {
        dismissKeyboard()
        showLastKeyboard()
      }
    }, [isCustomKeyboardOpen, dismissKeyboard])

    const showLastKeyboard = useCallback(() => {
      setCustomKeyboard({})
      setTimeout(() => {
        setKeyboardOpenState(true)
        setCustomKeyboard((prev: any) => ({ ...prev }))
      }, 500)
    }, [])

    const showKeyboardView = useCallback((component: string, title?: string) => {
      setKeyboardOpenState(true)
      setCustomKeyboard({
        component,
        initialProps: { title },
      })
    }, [])

    const onHeightChanged = useCallback((height: number) => {
      if (Constants.isIOS) {
        setKeyboardAccessoryViewHeight(height)
      }
    }, [])

    const renderKeyboardAccessoryViewContent = useCallback(
      () => (
        <View style={styles.keyboardContainer} paddingV-s4>
          <View bg-white row spread centerV paddingH-s5 paddingV-s3>
            <TextInput ref={textInputRef} placeholder="Test" onFocus={resetKeyboardView} />
            <Button link grey10 onPress={KeyboardUtils.dismiss} marginL-s2 label="Test" />
          </View>
          <View row paddingH-s4 marginT-s2 spread>
            <View row>
              {demoKeyboards.map((keyboard, index) => (
                <Button
                  key={`${keyboard.id}_${index}`}
                  grey10
                  link
                  onPress={() => showKeyboardView(keyboard.id)}
                  marginR-s2
                  label="Test"
                />
              ))}
            </View>
            <Button grey10 label="Reset" link onPress={resetKeyboardView} />
          </View>
        </View>
      ),
      [showKeyboardView, resetKeyboardView],
    )

    const requestShowKeyboard = useCallback(() => {
      KeyboardRegistry.requestShowKeyboard("unicorn.ImagesKeyboard")
    }, [])

    const onRequestShowKeyboard = useCallback(() => {
      setCustomKeyboard({
        component: "unicorn.ImagesKeyboard",
        initialProps: { title: "Keyboard 1 opened by button" },
      })
    }, [])

    const safeAreaSwitchToggle = useCallback(() => {
      if (!Constants.isIOS) {
        return null
      }
      return (
        <View center>
          <View style={styles.separatorLine} />
          <View centerV row margin-10>
            <Text text80 grey40>
              Safe Area Enabled:
            </Text>
            <Switch value={useSafeArea} onValueChange={toggleUseSafeArea} marginL-14 />
          </View>
          <View style={styles.separatorLine} />
        </View>
      )
    }, [useSafeArea, toggleUseSafeArea])

    return (
      <View flex style={{ marginTop: 100 }}>
        <Button
          grey10
          link
          onPress={() => showKeyboardView("unicorn.ImagesKeyboard")}
          label="Test213"
        />
        <View flex>
          <KeyboardAccessoryView
            renderContent={renderKeyboardAccessoryViewContent}
            onHeightChanged={onHeightChanged}
            trackInteractive={TrackInteractive}
            kbInputRef={textInputRef}
            kbComponent={customKeyboard.component}
            kbInitialProps={customKeyboard.initialProps}
            onItemSelected={onKeyboardItemSelected}
            onKeyboardResigned={onKeyboardResigned}
            onRequestShowKeyboard={onRequestShowKeyboard}
            useSafeArea={useSafeArea}
            addBottomView={useSafeArea}
            allowHitsOutsideBounds
          />
        </View>
      </View>
    )
  },
)

const styles = StyleSheet.create({
  scrollContainer: {
    paddingHorizontal: Spacings.s5,
    flex: 1,
    justifyContent: "center",
  },
  textField: {
    flex: 1,
    backgroundColor: Colors.grey60,
    paddingVertical: Spacings.s2,
    paddingHorizontal: Spacings.s4,
    borderRadius: 8,
  },
  button: {
    padding: Spacings.s2,
  },
  keyboardContainer: {
    backgroundColor: Colors.white,
    borderWidth: 1,
    borderColor: Colors.grey60,
  },
  separatorLine: {
    flex: 1,
    height: 1,
    backgroundColor: Colors.grey80,
  },
})

Screenshots/Video

Environment

  • React Native version: 0.76.9
  • React Native UI Lib version: 7.44.0

Affected platforms

  • Android
  • iOS
  • Web

Metadata

Metadata

Assignees

No one assigned

    Labels

    buga bug in one of the components

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions