Installation

To get started with gluestack-ui v2, check out this quick installation guide. It provides simple steps to help you install and use the library in your projects.


Step 1: Setup your project

If you are starting with a new project, setup your project with Next.js or Expo. If you already have a project, you can skip this step.

Prerequisites

Ensure you have the following prerequisites installed on your project to use gluestack-ui CLI:
Package Name
Supported Versions
Upcoming Support
next
13 <= versions <= 14
version 15 (Next.js 15 Support is Manual Only for Now. Please check the Additional Step for Next.js 15)
react-native
versions >= 72.5
-
expo
versions >= 50
-
node
versions > 16
-

Step 2: Initialize

Go to your project, and use the init command at the root of your project to add GluestackUIProvider and the gluestack-ui-provider/config.ts file to your project..
npx gluestack-ui init
Important Note
Installation using gluestack-ui CLI in Expo projects supports for Expo SDK 50 and above only. For Expo SDK < 49, please refer to the manual installation.
Your project is now ready to use gluestack-ui components. To add gluestack-ui components to your project using the CLI, refer to the above command or use the CLI guide.
npx gluestack-ui add box
If you encounter issues during the CLI installation, refer to the manual installation guide available.

Additional Step for Next.js 15.

Currently, Next.js 15 runs on react@19-rc.0. This version of react is not compatible with react-native-web@0.19.13. To resolve this, you need to do following steps.
  1. Step
    1
    :
    Changes in registry.tsx. Copy-paste the following code.
"use client"
import React, { useRef, useState } from "react"
import { useServerInsertedHTML } from "next/navigation"
import { StyleRegistry, createStyleRegistry } from "styled-jsx"
// @ts-ignore
import { AppRegistry } from "react-native-web"
import { flush } from "@gluestack-ui/nativewind-utils/flush"
export default function StyledJsxRegistry({
children,
}: {
children: React.ReactNode
}) {
// Only create stylesheet once with lazy initial state
// x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
const [jsxStyleRegistry] = useState(() => createStyleRegistry())
const isServerInserted = useRef(false)
useServerInsertedHTML(() => {
AppRegistry.registerComponent("Main", () => "main")
const { getStyleElement } = AppRegistry.getApplication("Main")
if (!isServerInserted.current) {
isServerInserted.current = true
const styles = [getStyleElement(), jsxStyleRegistry.styles(), flush()]
jsxStyleRegistry.flush()
return <>{styles}</>
}
})
return <StyleRegistry registry={jsxStyleRegistry}>{children}</StyleRegistry>
}
  1. Step
    2
    :
    Changes in gluestack-ui-provider/index.web.tsx. Copy-paste the following code.
"use client"
import React, { useEffect, useLayoutEffect } from "react"
import { config } from "./config"
import { OverlayProvider } from "@gluestack-ui/overlay"
import { ToastProvider } from "@gluestack-ui/toast"
import { setFlushStyles } from "@gluestack-ui/nativewind-utils/flush"
import { script } from "./script"
const variableStyleTagId = "nativewind-style"
const createStyle = (styleTagId: string) => {
const style = document.createElement("style")
style.id = styleTagId
style.appendChild(document.createTextNode(""))
return style
}
export const useSafeLayoutEffect =
typeof window !== "undefined" ? useLayoutEffect : useEffect
export function GluestackUIProvider({
mode = "light",
...props
}: {
mode?: "light" | "dark" | "system"
children?: React.ReactNode
}) {
let cssVariablesWithMode = ``
Object.keys(config).forEach((configKey) => {
cssVariablesWithMode +=
configKey === "dark" ? `\n .dark {\n ` : `\n:root {\n`
const cssVariables = Object.keys(
config[configKey as keyof typeof config]
).reduce((acc: string, curr: string) => {
acc += `${curr}:${config[configKey as keyof typeof config][curr]}; `
return acc
}, "")
cssVariablesWithMode += `${cssVariables} \n}`
})
setFlushStyles(cssVariablesWithMode)
const handleMediaQuery = React.useCallback((e: MediaQueryListEvent) => {
script(e.matches ? "dark" : "light")
}, [])
useSafeLayoutEffect(() => {
if (mode !== "system") {
const documentElement = document.documentElement
if (documentElement) {
documentElement.classList.add(mode)
documentElement.classList.remove(mode === "light" ? "dark" : "light")
documentElement.style.colorScheme = mode
}
}
}, [mode])
useSafeLayoutEffect(() => {
if (mode !== "system") return
const media = window.matchMedia("(prefers-color-scheme: dark)")
media.addListener(handleMediaQuery)
return () => media.removeListener(handleMediaQuery)
}, [handleMediaQuery])
useSafeLayoutEffect(() => {
if (typeof window !== "undefined") {
const documentElement = document.documentElement
if (documentElement) {
const head = documentElement.querySelector("head")
let style = head?.querySelector(`[id='${variableStyleTagId}']`)
if (!style) {
style = createStyle(variableStyleTagId)
style.innerHTML = cssVariablesWithMode
if (head) head.appendChild(style)
}
}
}
}, [])
return (
<>
<OverlayProvider>
<ToastProvider>{props.children}</ToastProvider>
</OverlayProvider>
</>
)
}
  1. Step
    3
    :
    create a patches folder in the root of your project.
  2. Step
    4
    :
    create a react-native-web+0.19.13.patch file in the patches folder and paste the following code.
diff --git a/node_modules/react-native-web/dist/cjs/exports/AppRegistry/renderApplication.js b/node_modules/react-native-web/dist/cjs/exports/AppRegistry/renderApplication.js
index 0c0cb2f..83fd94b 100644
--- a/node_modules/react-native-web/dist/cjs/exports/AppRegistry/renderApplication.js
+++ b/node_modules/react-native-web/dist/cjs/exports/AppRegistry/renderApplication.js
@@ -1,6 +1,5 @@
"use strict";
-var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
exports.__esModule = true;
exports.default = renderApplication;
@@ -8,7 +7,7 @@ exports.getApplication = getApplication;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _AppContainer = _interopRequireDefault(require("./AppContainer"));
var _invariant = _interopRequireDefault(require("fbjs/lib/invariant"));
-var _render = _interopRequireWildcard(require("../render"));
+var _render = require("../render");
var _StyleSheet = _interopRequireDefault(require("../StyleSheet"));
var _react = _interopRequireDefault(require("react"));
/**
@@ -24,9 +23,8 @@ var _react = _interopRequireDefault(require("react"));
function renderApplication(RootComponent, WrapperComponent, callback, options) {
var shouldHydrate = options.hydrate,
initialProps = options.initialProps,
- mode = options.mode,
rootTag = options.rootTag;
- var renderFn = shouldHydrate ? mode === 'concurrent' ? _render.hydrate : _render.hydrateLegacy : mode === 'concurrent' ? _render.render : _render.default;
+ var renderFn = shouldHydrate ? _render.hydrate : _render.render;
(0, _invariant.default)(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag);
return renderFn(/*#__PURE__*/_react.default.createElement(_AppContainer.default, {
WrapperComponent: WrapperComponent,
diff --git a/node_modules/react-native-web/dist/cjs/exports/render/index.js b/node_modules/react-native-web/dist/cjs/exports/render/index.js
index b41ee11..18d9b2f 100644
--- a/node_modules/react-native-web/dist/cjs/exports/render/index.js
+++ b/node_modules/react-native-web/dist/cjs/exports/render/index.js
@@ -10,15 +10,10 @@
'use client';
-var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
exports.__esModule = true;
-exports.default = renderLegacy;
+exports.default = render;
exports.hydrate = hydrate;
-exports.hydrateLegacy = hydrateLegacy;
-exports.render = render;
-var _reactDom = require("react-dom");
var _client = require("react-dom/client");
-var _unmountComponentAtNode = _interopRequireDefault(require("../unmountComponentAtNode"));
var _dom = require("../StyleSheet/dom");
function hydrate(element, root) {
(0, _dom.createSheet)(root);
@@ -30,21 +25,3 @@ function render(element, root) {
reactRoot.render(element);
return reactRoot;
}
\ No newline at end of file
-function hydrateLegacy(element, root, callback) {
- (0, _dom.createSheet)(root);
- (0, _reactDom.hydrate)(element, root, callback);
- return {
- unmount: function unmount() {
- return (0, _unmountComponentAtNode.default)(root);
- }
- };
-}
-function renderLegacy(element, root, callback) {
- (0, _dom.createSheet)(root);
- (0, _reactDom.render)(element, root, callback);
- return {
- unmount: function unmount() {
- return (0, _unmountComponentAtNode.default)(root);
- }
- };
-}
\ No newline at end of file
diff --git a/node_modules/react-native-web/dist/cjs/exports/unmountComponentAtNode/index.js b/node_modules/react-native-web/dist/cjs/exports/unmountComponentAtNode/index.js
index 3ea3964..e740204 100644
--- a/node_modules/react-native-web/dist/cjs/exports/unmountComponentAtNode/index.js
+++ b/node_modules/react-native-web/dist/cjs/exports/unmountComponentAtNode/index.js
@@ -1,8 +1,7 @@
"use strict";
exports.__esModule = true;
-exports.default = void 0;
-var _reactDom = require("react-dom");
+exports.default = unmountComponentAtNode;
/**
* Copyright (c) Nicolas Gallagher.
*
@@ -11,5 +10,9 @@ var _reactDom = require("react-dom");
*
*
*/
-var _default = exports.default = _reactDom.unmountComponentAtNode;
+
+function unmountComponentAtNode(rootTag) {
+ rootTag.unmount();
+ return true;
+}
module.exports = exports.default;
\ No newline at end of file
diff --git a/node_modules/react-native-web/dist/exports/AppRegistry/renderApplication.js b/node_modules/react-native-web/dist/exports/AppRegistry/renderApplication.js
index b53dff6..c56c1dc 100644
--- a/node_modules/react-native-web/dist/exports/AppRegistry/renderApplication.js
+++ b/node_modules/react-native-web/dist/exports/AppRegistry/renderApplication.js
@@ -11,15 +11,14 @@ import _extends from "@babel/runtime/helpers/extends";
import AppContainer from './AppContainer';
import invariant from 'fbjs/lib/invariant';
-import renderLegacy, { hydrateLegacy, render, hydrate } from '../render';
+import { render, hydrate } from '../render';
import StyleSheet from '../StyleSheet';
import React from 'react';
export default function renderApplication(RootComponent, WrapperComponent, callback, options) {
var shouldHydrate = options.hydrate,
initialProps = options.initialProps,
- mode = options.mode,
rootTag = options.rootTag;
- var renderFn = shouldHydrate ? mode === 'concurrent' ? hydrate : hydrateLegacy : mode === 'concurrent' ? render : renderLegacy;
+ var renderFn = shouldHydrate ? hydrate : render;
invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag);
return renderFn(/*#__PURE__*/React.createElement(AppContainer, {
WrapperComponent: WrapperComponent,
diff --git a/node_modules/react-native-web/dist/exports/AppRegistry/renderApplication.js.flow b/node_modules/react-native-web/dist/exports/AppRegistry/renderApplication.js.flow
index b9df2af..2b671ba 100644
--- a/node_modules/react-native-web/dist/exports/AppRegistry/renderApplication.js.flow
+++ b/node_modules/react-native-web/dist/exports/AppRegistry/renderApplication.js.flow
@@ -11,7 +11,7 @@
import type { ComponentType, Node } from 'react';
import AppContainer from './AppContainer';
import invariant from 'fbjs/lib/invariant';
-import renderLegacy, { hydrateLegacy, render, hydrate } from '../render';
+import { render, hydrate } from '../render';
import StyleSheet from '../StyleSheet';
import React from 'react';
export type Application = {
@@ -20,7 +20,6 @@ export type Application = {
declare export default function renderApplication<Props: Object>(RootComponent: ComponentType<Props>, WrapperComponent?: ?ComponentType<*>, callback?: () => void, options: {
hydrate: boolean,
initialProps: Props,
- mode: 'concurrent' | 'legacy',
rootTag: any,
}): Application;
declare export function getApplication(RootComponent: ComponentType<Object>, initialProps: Object, WrapperComponent?: ?ComponentType<*>): {|
diff --git a/node_modules/react-native-web/dist/exports/render/index.js b/node_modules/react-native-web/dist/exports/render/index.js
index aa91a2a..8f9a14d 100644
--- a/node_modules/react-native-web/dist/exports/render/index.js
+++ b/node_modules/react-native-web/dist/exports/render/index.js
@@ -9,35 +9,15 @@
'use client';
-import { hydrate as domLegacyHydrate, render as domLegacyRender } from 'react-dom';
import { createRoot as domCreateRoot, hydrateRoot as domHydrateRoot } from 'react-dom/client';
-import unmountComponentAtNode from '../unmountComponentAtNode';
import { createSheet } from '../StyleSheet/dom';
export function hydrate(element, root) {
createSheet(root);
return domHydrateRoot(root, element);
}
-export function render(element, root) {
+export default function render(element, root) {
createSheet(root);
var reactRoot = domCreateRoot(root);
reactRoot.render(element);
return reactRoot;
}
\ No newline at end of file
-export function hydrateLegacy(element, root, callback) {
- createSheet(root);
- domLegacyHydrate(element, root, callback);
- return {
- unmount: function unmount() {
- return unmountComponentAtNode(root);
- }
- };
-}
-export default function renderLegacy(element, root, callback) {
- createSheet(root);
- domLegacyRender(element, root, callback);
- return {
- unmount: function unmount() {
- return unmountComponentAtNode(root);
- }
- };
-}
\ No newline at end of file
diff --git a/node_modules/react-native-web/dist/exports/render/index.js.flow b/node_modules/react-native-web/dist/exports/render/index.js.flow
index 1bd771e..729d57d 100644
--- a/node_modules/react-native-web/dist/exports/render/index.js.flow
+++ b/node_modules/react-native-web/dist/exports/render/index.js.flow
@@ -9,11 +9,7 @@
'use client';
-import { hydrate as domLegacyHydrate, render as domLegacyRender } from 'react-dom';
import { createRoot as domCreateRoot, hydrateRoot as domHydrateRoot } from 'react-dom/client';
-import unmountComponentAtNode from '../unmountComponentAtNode';
import { createSheet } from '../StyleSheet/dom';
declare export function hydrate(element: any, root: any): any;
-declare export function render(element: any, root: any): any;
-declare export function hydrateLegacy(element: any, root: any, callback: any): any;
-declare export default function renderLegacy(element: any, root: any, callback: any): any;
\ No newline at end of file
+declare export default function render(element: any, root: any): any;
\ No newline at end of file
diff --git a/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.js b/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.js
index 925051c..cea4dee 100644
--- a/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.js
+++ b/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.js
@@ -7,5 +7,7 @@
*
*/
-import { unmountComponentAtNode } from 'react-dom';
-export default unmountComponentAtNode;
\ No newline at end of file
+export default function unmountComponentAtNode(rootTag) {
+ rootTag.unmount();
+ return true;
+}
\ No newline at end of file
diff --git a/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.js.flow b/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.js.flow
index b950090..90ec151 100644
--- a/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.js.flow
+++ b/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.js.flow
@@ -6,6 +6,4 @@
*
* @noflow
*/
-
-import { unmountComponentAtNode } from 'react-dom';
-export default unmountComponentAtNode;
\ No newline at end of file
+declare export default function unmountComponentAtNode(rootTag: any): any;
\ No newline at end of file
diff --git a/node_modules/react-native-web/dist/modules/addEventListener/__tests__/index-test.js.flow b/node_modules/react-native-web/dist/modules/addEventListener/__tests__/index-test.js.flow
new file mode 100644
index 0000000..9eb6144
--- /dev/null
+++ b/node_modules/react-native-web/dist/modules/addEventListener/__tests__/index-test.js.flow
@@ -0,0 +1,191 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow strict-local
+ */
+
+import * as React from 'react';
+import { act, render } from '@testing-library/react';
+import { createEventTarget } from 'dom-event-testing-library';
+import { addEventListener } from '..';
+describe('addEventListener', () => {
+ describe('addEventListener()', () => {
+ test('event dispatched on target', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('event dispatched on parent', () => {
+ const listener = jest.fn();
+ const listenerCapture = jest.fn();
+ const targetRef = React.createRef();
+ const parentRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const parent = createEventTarget(parentRef.current);
+ act(() => {
+ parent.click();
+ });
+ expect(listener).toBeCalledTimes(0);
+ expect(listenerCapture).toBeCalledTimes(0);
+ });
+ test('event dispatched on child', () => {
+ const log = [];
+ const listener = jest.fn(() => {
+ log.push('bubble');
+ });
+ const listenerCapture = jest.fn(() => {
+ log.push('capture');
+ });
+ const targetRef = React.createRef();
+ const childRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const child = createEventTarget(childRef.current);
+ act(() => {
+ child.click();
+ });
+ expect(listenerCapture).toBeCalledTimes(1);
+ expect(listener).toBeCalledTimes(1);
+ expect(log).toEqual(['capture', 'bubble']);
+ });
+ test('event dispatched on text node', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ const childRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const text = createEventTarget(childRef.current.firstChild);
+ act(() => {
+ text.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('listener can be attached to document', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(arg0: any): any;
+ render(<Component target={document} />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('listener can be attached to window ', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(arg0: any): any;
+ render(<Component target={window} />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('custom event dispatched on target', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ act(() => {
+ const event = new CustomEvent('magic-event', {
+ bubbles: true
+ });
+ targetRef.current.dispatchEvent(event);
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('listeners can be set on multiple targets simultaneously', () => {
+ const log = [];
+ const targetRef = React.createRef();
+ const parentRef = React.createRef();
+ const childRef = React.createRef();
+ const listener = jest.fn(e => {
+ log.push(['bubble', e.currentTarget.id]);
+ });
+ const listenerCapture = jest.fn(e => {
+ log.push(['capture', e.currentTarget.id]);
+ });
+ declare function Component(): any;
+ render(<Component />);
+ const child = createEventTarget(childRef.current);
+ act(() => {
+ child.click();
+ });
+ expect(listenerCapture).toBeCalledTimes(2);
+ expect(listener).toBeCalledTimes(2);
+ expect(log).toEqual([['capture', 'parent'], ['capture', 'target'], ['bubble', 'target'], ['bubble', 'parent']]);
+ });
+ test('listeners are specific to each event handle', () => {
+ const log = [];
+ const targetRef = React.createRef();
+ const childRef = React.createRef();
+ const listener = jest.fn(e => {
+ log.push(['bubble', 'target']);
+ });
+ const listenerAlt = jest.fn(e => {
+ log.push(['bubble', 'target-alt']);
+ });
+ const listenerCapture = jest.fn(e => {
+ log.push(['capture', 'target']);
+ });
+ const listenerCaptureAlt = jest.fn(e => {
+ log.push(['capture', 'target-alt']);
+ });
+ declare function Component(): any;
+ render(<Component />);
+ const child = createEventTarget(childRef.current);
+ act(() => {
+ child.click();
+ });
+ expect(listenerCapture).toBeCalledTimes(1);
+ expect(listenerCaptureAlt).toBeCalledTimes(1);
+ expect(listener).toBeCalledTimes(1);
+ expect(listenerAlt).toBeCalledTimes(1);
+ expect(log).toEqual([['capture', 'target'], ['capture', 'target-alt'], ['bubble', 'target'], ['bubble', 'target-alt']]);
+ });
+ });
+ describe('stopPropagation and stopImmediatePropagation', () => {
+ test('stopPropagation works as expected', () => {
+ const childListener = jest.fn(e => {
+ e.stopPropagation();
+ });
+ const targetListener = jest.fn();
+ const targetRef = React.createRef();
+ const childRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const child = createEventTarget(childRef.current);
+ act(() => {
+ child.click();
+ });
+ expect(childListener).toBeCalledTimes(1);
+ expect(targetListener).toBeCalledTimes(0);
+ });
+ test('stopImmediatePropagation works as expected', () => {
+ const firstListener = jest.fn(e => {
+ e.stopImmediatePropagation();
+ });
+ const secondListener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(firstListener).toBeCalledTimes(1);
+ expect(secondListener).toBeCalledTimes(0);
+ });
+ });
+});
\ No newline at end of file
diff --git a/node_modules/react-native-web/dist/modules/addEventListener/__tests__/index-test.node.js.flow b/node_modules/react-native-web/dist/modules/addEventListener/__tests__/index-test.node.js.flow
new file mode 100644
index 0000000..c1805b7
--- /dev/null
+++ b/node_modules/react-native-web/dist/modules/addEventListener/__tests__/index-test.node.js.flow
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow strict-local
+ */
+
+import * as React from 'react';
+import * as ReactDOMServer from 'react-dom/server';
+import { addEventListener } from '..';
+describe('addEventListener', () => {
+ test('can render correctly using ReactDOMServer', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(): any;
+ const output = ReactDOMServer.renderToString(<Component />);
+ expect(output).toBe('<div></div>');
+ });
+});
\ No newline at end of file
diff --git a/node_modules/react-native-web/dist/modules/useEvent/__tests__/index-test.js.flow b/node_modules/react-native-web/dist/modules/useEvent/__tests__/index-test.js.flow
new file mode 100644
index 0000000..9b57364
--- /dev/null
+++ b/node_modules/react-native-web/dist/modules/useEvent/__tests__/index-test.js.flow
@@ -0,0 +1,247 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow strict-local
+ */
+
+import * as React from 'react';
+import { act, render } from '@testing-library/react';
+import { createEventTarget } from 'dom-event-testing-library';
+import useEvent from '..';
+describe('use-event', () => {
+ describe('setListener()', () => {
+ test('event dispatched on target', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('event dispatched on parent', () => {
+ const listener = jest.fn();
+ const listenerCapture = jest.fn();
+ const targetRef = React.createRef();
+ const parentRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const parent = createEventTarget(parentRef.current);
+ act(() => {
+ parent.click();
+ });
+ expect(listener).toBeCalledTimes(0);
+ expect(listenerCapture).toBeCalledTimes(0);
+ });
+ test('event dispatched on child', () => {
+ const log = [];
+ const listener = jest.fn(() => {
+ log.push('bubble');
+ });
+ const listenerCapture = jest.fn(() => {
+ log.push('capture');
+ });
+ const targetRef = React.createRef();
+ const childRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const child = createEventTarget(childRef.current);
+ act(() => {
+ child.click();
+ });
+ expect(listenerCapture).toBeCalledTimes(1);
+ expect(listener).toBeCalledTimes(1);
+ expect(log).toEqual(['capture', 'bubble']);
+ });
+ test('event dispatched on text node', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ const childRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const text = createEventTarget(childRef.current.firstChild);
+ act(() => {
+ text.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('listener can be attached to document ', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(arg0: any): any;
+ render(<Component target={document} />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('listener can be attached to window ', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(arg0: any): any;
+ render(<Component target={window} />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('listener is replaceable', () => {
+ const listener = jest.fn();
+ const listenerAlt = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(arg0: any): any;
+ const {
+ rerender
+ } = render(<Component onClick={listener} />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ rerender(<Component onClick={listenerAlt} />);
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ expect(listenerAlt).toBeCalledTimes(1);
+ });
+ test('listener is removed when value is null', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(arg0: any): any;
+ const {
+ unmount
+ } = render(<Component off={false} />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+
+ // this should unset the listener
+ unmount();
+ listener.mockClear();
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(0);
+ });
+ test('custom event dispatched on target', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ act(() => {
+ const event = new CustomEvent('magic-event', {
+ bubbles: true
+ });
+ targetRef.current.dispatchEvent(event);
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('listeners can be set on multiple targets simultaneously', () => {
+ const log = [];
+ const targetRef = React.createRef();
+ const parentRef = React.createRef();
+ const childRef = React.createRef();
+ const listener = jest.fn(e => {
+ log.push(['bubble', e.currentTarget.id]);
+ });
+ const listenerCapture = jest.fn(e => {
+ log.push(['capture', e.currentTarget.id]);
+ });
+ declare function Component(): any;
+ render(<Component />);
+ const child = createEventTarget(childRef.current);
+ act(() => {
+ child.click();
+ });
+ expect(listenerCapture).toBeCalledTimes(2);
+ expect(listener).toBeCalledTimes(2);
+ expect(log).toEqual([['capture', 'parent'], ['capture', 'target'], ['bubble', 'target'], ['bubble', 'parent']]);
+ });
+ test('listeners are specific to each event handle', () => {
+ const log = [];
+ const targetRef = React.createRef();
+ const childRef = React.createRef();
+ const listener = jest.fn(e => {
+ log.push(['bubble', 'target']);
+ });
+ const listenerAlt = jest.fn(e => {
+ log.push(['bubble', 'target-alt']);
+ });
+ const listenerCapture = jest.fn(e => {
+ log.push(['capture', 'target']);
+ });
+ const listenerCaptureAlt = jest.fn(e => {
+ log.push(['capture', 'target-alt']);
+ });
+ declare function Component(): any;
+ render(<Component />);
+ const child = createEventTarget(childRef.current);
+ act(() => {
+ child.click();
+ });
+ expect(listenerCapture).toBeCalledTimes(1);
+ expect(listenerCaptureAlt).toBeCalledTimes(1);
+ expect(listener).toBeCalledTimes(1);
+ expect(listenerAlt).toBeCalledTimes(1);
+ expect(log).toEqual([['capture', 'target'], ['capture', 'target-alt'], ['bubble', 'target'], ['bubble', 'target-alt']]);
+ });
+ });
+ describe('cleanup', () => {
+ test('removes all listeners for given event type from targets', () => {
+ const clickListener = jest.fn();
+ declare function Component(): any;
+ const {
+ unmount
+ } = render(<Component />);
+ unmount();
+ const target = createEventTarget(document);
+ act(() => {
+ target.click();
+ });
+ expect(clickListener).toBeCalledTimes(0);
+ });
+ });
+ describe('stopPropagation and stopImmediatePropagation', () => {
+ test('stopPropagation works as expected', () => {
+ const childListener = jest.fn(e => {
+ e.stopPropagation();
+ });
+ const targetListener = jest.fn();
+ const targetRef = React.createRef();
+ const childRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const child = createEventTarget(childRef.current);
+ act(() => {
+ child.click();
+ });
+ expect(childListener).toBeCalledTimes(1);
+ expect(targetListener).toBeCalledTimes(0);
+ });
+ test('stopImmediatePropagation works as expected', () => {
+ const firstListener = jest.fn(e => {
+ e.stopImmediatePropagation();
+ });
+ const secondListener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(firstListener).toBeCalledTimes(1);
+ expect(secondListener).toBeCalledTimes(0);
+ });
+ });
+});
\ No newline at end of file
diff --git a/node_modules/react-native-web/dist/modules/useStable/__tests__/index-test.js.flow b/node_modules/react-native-web/dist/modules/useStable/__tests__/index-test.js.flow
new file mode 100644
index 0000000..49edfc6
--- /dev/null
+++ b/node_modules/react-native-web/dist/modules/useStable/__tests__/index-test.js.flow
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import * as React from 'react';
+import { render } from '@testing-library/react';
+import useStable from '..';
+describe('useStable', () => {
+ let spy = {};
+ declare var TestComponent: (arg0: any) => React.Node;
+ beforeEach(() => {
+ spy = {};
+ });
+ test('correctly sets the initial value', () => {
+ declare var initialValueCallback: () => any;
+ render(<TestComponent initialValueCallback={initialValueCallback} />);
+ expect(spy.value).toBe(5);
+ });
+ test('does not change the value', () => {
+ let counter = 0;
+ declare var initialValueCallback: () => any;
+ const {
+ rerender
+ } = render(<TestComponent initialValueCallback={initialValueCallback} />);
+ expect(spy.value).toBe(0);
+ rerender(<TestComponent initialValueCallback={initialValueCallback} />);
+ expect(spy.value).toBe(0);
+ });
+ test('only calls the callback once', () => {
+ let counter = 0;
+ declare var initialValueCallback: () => any;
+ const {
+ rerender
+ } = render(<TestComponent initialValueCallback={initialValueCallback} />);
+ expect(counter).toBe(1);
+ rerender(<TestComponent initialValueCallback={initialValueCallback} />);
+ expect(counter).toBe(1);
+ });
+ test('does not change the value if the callback initially returns null', () => {
+ let counter = 0;
+ declare var initialValueCallback: () => any;
+ const {
+ rerender
+ } = render(<TestComponent initialValueCallback={initialValueCallback} />);
+ expect(spy.value).toBe(null);
+ rerender(<TestComponent initialValueCallback={initialValueCallback} />);
+ expect(spy.value).toBe(null);
+ });
+});
\ No newline at end of file
5 . Add Script postinstall in package.json.
"postinstall": "patch-package"
  1. Step
    6
    :
    Run following command to apply patches.
npm run postinstall
  1. Step
    7
    :
    Changes in next.config.ts. Copy-paste the following code.
import { withGluestackUI } from "@gluestack/ui-next-adapter"
const nextConfig = {
transpilePackages: ["nativewind", "react-native-css-interop"],
}
export default withGluestackUI(nextConfig)

Common issues

Expo app stuck in tailwindcss(ios) rebuilding... while running expo start command
In this case, you may have your app stored in a directory with a name containing spaces, such as 'Expo App', renaming it to just 'Expo-App' will resolve the issue.