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.
CLI
Manual
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.
- Step1: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-ignoreimport { 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-stateconst [jsxStyleRegistry] = useState(() => createStyleRegistry())const isServerInserted = useRef(false)useServerInsertedHTML(() => {AppRegistry.registerComponent("Main", () => "main")const { getStyleElement } = AppRegistry.getApplication("Main")if (!isServerInserted.current) {isServerInserted.current = trueconst styles = [getStyleElement(), jsxStyleRegistry.styles(), flush()]jsxStyleRegistry.flush()return <>{styles}</>}})return <StyleRegistry registry={jsxStyleRegistry}>{children}</StyleRegistry>}
- Step2: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 = styleTagIdstyle.appendChild(document.createTextNode(""))return style}export const useSafeLayoutEffect =typeof window !== "undefined" ? useLayoutEffect : useEffectexport 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.documentElementif (documentElement) {documentElement.classList.add(mode)documentElement.classList.remove(mode === "light" ? "dark" : "light")documentElement.style.colorScheme = mode}}}, [mode])useSafeLayoutEffect(() => {if (mode !== "system") returnconst media = window.matchMedia("(prefers-color-scheme: dark)")media.addListener(handleMediaQuery)return () => media.removeListener(handleMediaQuery)}, [handleMediaQuery])useSafeLayoutEffect(() => {if (typeof window !== "undefined") {const documentElement = document.documentElementif (documentElement) {const head = documentElement.querySelector("head")let style = head?.querySelector(`[id='${variableStyleTagId}']`)if (!style) {style = createStyle(variableStyleTagId)style.innerHTML = cssVariablesWithModeif (head) head.appendChild(style)}}}}, [])return (<><OverlayProvider><ToastProvider>{props.children}</ToastProvider></OverlayProvider></>)}
- Step3:create a patches folder in the root of your project.
- Step4: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.jsindex 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.jsindex 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 filediff --git a/node_modules/react-native-web/dist/cjs/exports/unmountComponentAtNode/index.js b/node_modules/react-native-web/dist/cjs/exports/unmountComponentAtNode/index.jsindex 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 filediff --git a/node_modules/react-native-web/dist/exports/AppRegistry/renderApplication.js b/node_modules/react-native-web/dist/exports/AppRegistry/renderApplication.jsindex 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.flowindex 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.jsindex 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 filediff --git a/node_modules/react-native-web/dist/exports/render/index.js.flow b/node_modules/react-native-web/dist/exports/render/index.js.flowindex 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 filediff --git a/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.js b/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.jsindex 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 filediff --git a/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.js.flow b/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.js.flowindex 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 filediff --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.flownew file mode 100644index 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 filediff --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.flownew file mode 100644index 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 filediff --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.flownew file mode 100644index 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 filediff --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.flownew file mode 100644index 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"
- Step6:Run following command to apply patches.
npm run postinstall
- Step7: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.