import { useEffect, useState } from "react"; import { KeyboardEvents } from "../../bindings"; import { KeyboardController } from "../../module"; import type { IKeyboardState } from "../../types"; const EVENTS = ["keyboardDidShow", "keyboardDidHide"] as const; const getLatestState = () => ({ ...KeyboardController.state(), isVisible: KeyboardController.isVisible(), }); type KeyboardStateSelector = (state: IKeyboardState) => T; const defaultSelector: KeyboardStateSelector = (state) => state; /** * React Hook that represents the current keyboard state on iOS and Android. * It tracks keyboard visibility, height, appearance, type and other properties. * This hook subscribes to keyboard events and updates the state reactively. * * @template T - A type of the returned object from the `selector`. * @param selector - A function that receives the current keyboard state and picks only necessary properties to avoid frequent re-renders. * @returns Object {@link KeyboardState|containing} keyboard state information. * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/hooks/keyboard/use-keyboard-state|Documentation} page for more details. * @example * ```tsx * function MyComponent() { * const isVisible = useKeyboardState((state) => state.isVisible); * const height = useKeyboardState((state) => state.height); * * return ( * * Keyboard is {isVisible ? 'visible' : 'hidden'} * Keyboard height: {height} * * ); * } * ``` */ function useKeyboardState( selector: KeyboardStateSelector = defaultSelector as KeyboardStateSelector, ): T { const [state, setState] = useState(() => selector(getLatestState())); useEffect(() => { const subscriptions = EVENTS.map((event) => KeyboardEvents.addListener(event, () => // state will be updated by global listener first, // so we simply read it and don't derive data from the event setState(selector(getLatestState())), ), ); // update `appearance` prematurely const willShowSubscription = KeyboardEvents.addListener( "keyboardWillShow", (e) => setState(selector({ ...getLatestState(), appearance: e.appearance })), ); // we might have missed an update between reading a value in render and // `addListener` in this handler, so we set it here. If there was // no change, React will filter out this update as a no-op. setState(selector(getLatestState())); return () => { subscriptions.forEach((subscription) => subscription.remove()); willShowSubscription.remove(); }; }, []); return state; } export { useKeyboardState };