Quantcast
Channel: Active questions tagged react-native+typescript - Stack Overflow

Failed to obtain view for PanGestureHandler in functional component

$
0
0

I'm working on a React Native app using Expo and react-native-gesture-handler.When I try to use PanGestureHandler inside my App.tsx, I get the following error at runtime:

ERROR [Error: [Gesture Handler] Failed to obtain view for PanGestureHandler.Note that old API doesn't support functional components.]

Here’s my simplified App.tsx code:

import React from 'react';import './global.css';import { StatusBar } from 'expo-status-bar';import RootNavigation from './src/navigation';import { View, Text, Platform, StyleSheet, ActivityIndicator } from 'react-native';import { GestureHandlerRootView, PanGestureHandler, PanGestureHandlerGestureEvent } from 'react-native-gesture-handler';import { useFonts, Inter_400Regular } from '@expo-google-fonts/inter';export default function App() {  const [fontsLoaded] = useFonts({ Inter_400Regular });  if (!fontsLoaded) {    return (<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}><ActivityIndicator size="large" /></View>    );  }  const onGestureEvent = (e: PanGestureHandlerGestureEvent) => {    // no-op  };  return (<GestureHandlerRootView style={styles.root}><View style={styles.boxContainer}><PanGestureHandler onGestureEvent={onGestureEvent}><View style={styles.box}><Text>Drag me (no-op)</Text></View></PanGestureHandler></View></GestureHandlerRootView>  );}const styles = StyleSheet.create({  root: { flex: 1 },  boxContainer: { flex: 1, alignItems: 'center', justifyContent: 'center' },  box: { width: 200, height: 200, alignItems: 'center', justifyContent: 'center', backgroundColor: '#eee' },});

I have wrapped everything inside GestureHandlerRootView as recommended by the docs, but the error persists.

Environment:

  • React Native: 0.81.4
  • Expo SDK: 54
  • react-native-gesture-handler: ~2.28.0

What I've tried:

  • Verified that GestureHandlerRootView wraps the entire app.
  • Cleaned build cache (npx expo start -c).
  • Tried moving PanGestureHandler to a separate component.

The error seems to say that the "old API doesn't support functional components" — but I'm using the latest versions and a functional component for App.


React Native NativeWind (Frameless) Versions Compatibility Problem

$
0
0

I'm new to React Native and trying to create an app using tailwind classes but getting different types of error while building the application.The errors are as follows:

The development server returned response error code: 500

URL:http://10.0.2.2:8081/index.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.MyNewApp&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server

I've tried to setup everything according to the official documentation of React Native and Nativewind but nothing is helpful.Moreover I'm getting continuously error because of some Babel configuration.Here are the files that I configured:babel.config.js

module.exports = function (api) {  api.cache(true);  return {    presets: ['module:@react-native/babel-preset'],    plugins: [      ['nativewind/babel', { mode: 'transform' }],'react-native-reanimated/plugin',    ],  };};

metro.config.js

const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');const { withNativeWind } = require('nativewind/metro');const config = mergeConfig(getDefaultConfig(__dirname), {});module.exports = withNativeWind(config, { input: './global.css' });

tailwind.config.js

/** @type {import('tailwindcss').Config} */module.exports = {  content: ['./App.{js,jsx,ts,tsx}', './src/**/*.{js,jsx,ts,tsx}'],  presets: [require('nativewind/preset')],  theme: { extend: {} },  plugins: [],};

tsconfig.json

{"extends": "@react-native/typescript-config","include": ["**/*.ts","**/*.tsx","nativewind-env.d.ts"  ],"exclude": ["**/node_modules","**/Pods"  ]}

Note: I don't want to use expo. The application is very big so it should be frameless.

How can I resolve the error "the expected type comes from property 'component' which is declared here on type"?

$
0
0

In my code here, I am trying to create a Stack Navigator.

This is the stack navigator I have made.

import React from 'react';import { View, Text, StyleSheet } from 'react-native';import { createStackNavigator } from '@react-navigation/stack';import EnterPassword from '../screens/EnterPassword';import EnterAccount from '../screens/EnterAccount';const GoogleLoginStack = createStackNavigator();const GoogleLogin = () => {    return (<GoogleLoginStack.Navigator><GoogleLoginStack.Screen name='EnterAccount' component={EnterAccount} /><GoogleLoginStack.Screen name='EnterPassword' component={EnterPassword} /></GoogleLoginStack.Navigator>    )};export default GoogleLogin;

It is then used in App.tsx file where:

...<Stack.Screen name="Login" component={Login} /><GoogleLogin /><Stack.Screen name="SignUp" component={SignUp} />...

The screen I have created is as follow:

import React from 'react';import { View, Text, StyleSheet } from 'react-native';const EnterAccount = (props: any) =>  {<View style={styles.screen}><Text>            Enter Account Screen</Text></View>}const styles = StyleSheet.create({    screen: {        flex: 1,        justifyContent: 'center',        alignItems: 'center',    }})export default EnterAccount;

However, I am getting this error:Error text: https://i.sstatic.net/zKbAa.png

I understand that it is because of how I defined the type of props to be but I am unsure of what's the right way to define the type of props.

Thank you so much for your help!

TextInput padding/scroll state persists after navigation or note change (dynamic padding not resetting)

$
0
0

I’m building a note-taking app in React Native (see code below) where each note’s content is displayed in a multiline, non-editable TextInput. The paddingTop of the TextInput is dynamically set based on whether the content is overflowing and whether the user has scrolled. The logic works for a single note, but when navigating between notes with different content lengths (some overflowing, some not), the padding/scroll/overflow state “leaks” or gets stuck. This results in incorrect padding or scroll state for the next note.

What I’ve tried:

Resetting all local state (overflow, scroll, etc.) on note/content change.Using a key prop on the TextInput and/or parent View to force remount.Explicitly setting all padding and style props.Using content length as a fallback for overflow detection.Hybrid approaches (content length fallback, then overflow detection).Forcing layout recalculation and remounts.None of these approaches fully resolve the issue.It seems like TextInput’s internal layout/scroll state is not reliably reset by navigation, remount, or state changes. This happens on iOS , not sure whether it would effect Android also.

Example code (simplified):

<View  key={note?.id || 'no-note'}  style={[styles.editContentInputWrapper, { padding: 0, paddingVertical: 0 }]}><TextInput    key={(note?.id || 'no-note') +'-'+ editNoteContent.length}    ref={editContentInputRef}    style={[      styles.input,      styles.multilineInput,      {        paddingTop:          isContentOverflowing === null            ? (editNoteContent && editNoteContent.length >= 20                ? hp(22.5)                : hp(8))            : isContentOverflowing              ? (hasUserScrolledContent ? hp(8) : hp(22.5))              : hp(8),        paddingVertical: 0,        padding: 0,      }    ]}    value={editNoteContent}    multiline={true}    editable={false}    scrollEnabled={true}    onContentSizeChange={handleContentSizeChange}    // ...touch/scroll handlers...  /></View>

Full context:

React Native 0.7x (Expo),navigation: Stack (React Navigation),state is reset on note/content change,key prop is used for remounting &styles are explicitly set.

How can I reliably reset or isolate the TextInput’s layout/scroll/padding state so that navigating between notes always shows the correct padding and scroll state? Is there a known workaround, or is this a React Native bug? Any help or insight would be greatly appreciated!

@typescript-eslint/prefer-readonly-parameter-types: allow StyleProp

$
0
0

I need to pass a parameter of type StyleProp<ViewStyle> to my function. eslint is issuing Parameter should be a read only type. I allow StyleProp and ViewStyle in the rule options but eslint is still issuing the same error.eslint is silent if I just use ViewStyle.

What am I doing wrong?

package.json:

"devDependencies": {"@eslint/js": "^9.38.0","@types/react": "^19.2.2","eslint": "^9.38.0","globals": "^16.4.0","react": "^19.2.0","react-native": "^0.79.7","typescript": "^5.9.3","typescript-eslint": "^8.46.2"}

eslint.config.mjs:

import js from '@eslint/js'import globals from 'globals'import tseslint from 'typescript-eslint'import { defineConfig } from 'eslint/config'export default defineConfig([  {    files: ['**/*.{js,mjs,cjs,ts,mts,cts}'],    plugins: { js },    extends: ['js/recommended'],    languageOptions: {      globals: globals.browser,      parserOptions: {        projectService: true      }    }  },  tseslint.configs.recommendedTypeChecked,  {    rules: {'@typescript-eslint/prefer-readonly-parameter-types': ['error',        {          allow: [            {              from: 'file',              name: ['StyleProp']            },            {              from: 'file',              name: ['ViewStyle']            }          ],          ignoreInferredTypes: true        }      ]    }  }])

My function:

const setCarouselStyle = (carouselStyle: StyleProp<ViewStyle>): void => {  //}

Edit 1

I changed my allow array

allow: [            {              from: 'package',              name: ['StyleProp'],              package: 'react-native'            },            {              from: 'package',              name: ['ViewStyle'],              package: 'react-native'            }          ]

, but nothing changed.

Custom text input with an optional icon and styling

$
0
0

I am trying to create my TextInput component that wraps around React Native's TextInput, but applies additional styles and adds more features.

Unfortunately, I am stuck at the very start of my component... I cannot create its type.

Here's what I have so far:

import React, {JSX} from 'react';import {StyleSheet, TextInput as RNTextInput, View,} from 'react-native';import {useTheme} from '@/context/ThemeProvider';export type TextInputProps = {    icon?: JSX.Element | undefined;    textInputProps?: React.ComponentProps<typeof RNTextInput>;};export default function TextInput({textInputProps, icon}: TextInputProps) {    const {theme} = useTheme();    return (<View            style={[                styles.container,                {                    backgroundColor: theme.backgrounds.tertiary,                    borderBottomColor: theme.borders.secondary,                },            ]}>            {icon && <View style={styles.icon}>{icon}</View>}<RNTextInput                placeholderTextColor={theme.text.lowEmphasis}                style={[                    styles.input,                    {color: theme.text.highEmphasis},                ]}                {...textInputProps}            /></View>    );}const styles = StyleSheet.create({    container: {        height: 44,        paddingHorizontal: 12,        borderBottomWidth: 2,    },    icon: {},    input: {        flex: 1,        paddingVertical: 0,    },});

However, I get this when spreading any additional props that might have been passed to TextInput in <RNTextInput {...textInputProps}/>:

Type ...React.ComponentProps<typeof RNTextInput> | undefined is not assignable to type React.ReactNode | undefined

My app runs fine with this error, but I want to have a clean file. What should be the value of textInputProps? in type TextInputProps?

How to import based on variable to avoid loading unnecessary assets

$
0
0

Using React Native+Expo

I'm trying to load only the correct font based on the user font.

const comicFontName = 'Comic-Neue';const fonts = [comicFontName];export function DisplayPost(props: DisplayPostProps) {    const { fontName } = props;    const [fontCSS, setFontCSS] = useState<string | null>(null);    useEffect(() => {        async function fetchFontCSS() {            if (!fontName) {                return;            }            const stylesheetModule = (await import('../../assets/fonts/'+ comicFontName // works, but fonts[0] or fontName throw error            ));            setFontCSS(stylesheetModule.default);        }        fetchFontCSS();    }, [fontName]);

Using the variable name as hardcoded string works, but using with from array or from props won't work and throw an error.

Invalid call at line 60: import('../../assets/fonts/'+ fonts[0])

There are multiple available fonts, each of them is about 500kb, I don't want to import all of them if only one is being used.Or maybe I don't understand correctly how to imports work and this is being resolved in runtime?

Expo 54 How to read bundled assets directly without extraction to cache?

$
0
0

I'm building an app with React Native / Expo SDK 54.The app bundles ~42,000 compressed HTML files (.zst format, ~224 MB compressed) that need to be accessible without network access.


The Problem

My production iOS build is 325 MB, but on first launch it creates another 330 MB in “Documents and Data” - basically duplicating all the assets.

First launch also takes 1+ minute, which is unacceptable.


What I’ve Tried

Originally used Asset.fromModule() with imports for each doc file.

  • This causes Expo to automatically extract all imported assets to cache on first launch.
  • Hence the duplication and slow startup.

Tried accessing files via Paths.bundle with the new File API (SDK 54).

  • Files don’t exist there.
  • I think this is because I’m importing them, so they’re embedded in the JS bundle, not included as separate bundled assets.

Tested multiple asset access methods in production.

  • Only asset.localUri works, but it points to the extracted cache location (the duplication I’m trying to avoid).
  • Same with DownloadAsync - works, but duplicates the file and is slower.

What I Want

Files should be bundled in the app at build time, then read directly from the bundle on-demand when a user opens a doc page.

It shouldn’t need to copy anything - just open the bundled file and read its bytes.

This feels like it should be simple - the files are in the bundle (325 MB proves it).I just want to read them without Expo copying them to cache first.

It also works perfectly in development builds and on web - it’s just native production builds causing issues.


Current Architecture

  • Thousands of .zst compressed HTML files
  • Manifests with import statements for each file (auto-generated)
  • Files referenced by hash in the manifest, loaded on-demand when a user navigates to a page
  • zst filetype is added as external asset to the metro config
  • Currently not doing anything in app.json for these assets

Questions

  1. Should I use assetBundlePatterns instead of imports?Would that make them accessible via Paths.bundle?

  2. Is there a way to bundle files without imports so Expo doesn’t auto-extract them?

  3. Is the asset.localUri extraction unavoidable when using Asset.fromModule()?

  4. Should I be using a completely different approach for bundling this many files?Or not bundle them at all and access them another way?


Environment:

  • Expo SDK 54
  • React Native 0.81.5
  • EAS Build (production iOS builds)
  • New architecture enabled

TypeError: Cannot assign to read only property 'userId' of object '#'

$
0
0

This code worked previously, but now that I upgraded my expo version I am getting the error "TypeError: Cannot assign to read only property 'userId' of object '#'".

export interface CurrentUser {  userId: string;  ...}

This is the creation of the object

import { CurrentUser } from "../types/CurrentUser";let currentUser: CurrentUser|null = null;

This is the assignment that is throwing the error.

if (currentUser) {currentUser.userId = data?.employee_id || "";}

I'm not super familiar with React Native, so any help would be much appreciated.

I'm using expo version 54.0.21

Slice text on full available space

$
0
0

Hope you're having a nice day.

I'm working on a React Native todo list app.

I want to slice my text when the available width is full and add "..." at the end. Right now I've the algorithm to slice the text length with a hardcoded value but I want it to be dynamic.

const itemName = "test1test1test1test1test1test1test1test1test1test1test1test1test1test1"const sliceText = 25const displayText =    itemName.length > sliceText ? itemName.slice(0, sliceText) +'...' : itemName

Do you have any idea how can I achieve that?

| Device #1 | Device #2 |

Thank you for your time!

How to return a non Never type in supabase.rpc?

$
0
0

I created a pgsql function in Supabase and tried to connect my VSCode with the function in order to upload data onto Supabase.

My code:

 const { data, error: uploadError } = await supabase.rpc('some_function',                                                        { column1: value1,                                                          column2: value2,                                                          column3: value3 })

I keep getting Argument of type '"some_function"' is not assignable to parameter of type 'never'.ts(2345).The problem changes to Expected 2 type arguments, but got 1.ts(2558) when I added Type to supabase.rpc (supabase.rpc<MyType>).

I have tried to add useRef for column1 and column2

So the full code is

 export default function SpeciesImage({ url, onUpload }: Props) { ...  async function uploadData() {  ...   const column1Ref = useRef(null); // Create a ref for the Column 1 TextInput   const column2Ref = useRef(null); // Create a ref for the Column2 TextInput   const column1 = column1Ref.currentTarget?.value || column1Ref.currentTarget?.props.value;   const column2 = column2Ref.currentTarget?.value || column2Ref.currentTarget?.props.value;   const { data, error: uploadError } = await supabase.rpc('some_function',                                                            {column1: value1,                                                             column2: value2,                                                            column3: value3 })   ...   return (<><View><Text>Some Text:</Text><TextInput                ref={column1Ref}                style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}                placeholder="some_placeholder"            /><Text>Some Text:</Text><TextInput                ref={column2Ref}                style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}                placeholder="some_placeholder"            /><Text>Press Button To Submit Data</Text><Button title="Upload" onPress={uploadData}/></View></>

but to no avail. Moreover, ref={column1Ref} and ref={column2Ref} have the error of Cannot find name 'column1Ref'.ts(2304) and Cannot find name 'column2Ref'.ts(2304) respectively.

I do not want to change the strict mode to false in ts. What should I do to change the Never type?

Xcode simulator black screen problem when a specific model was closed - React native expo

$
0
0

Problem
After a successful request, when the modal is closed with setVisible(false), a black screen appears just like that. To explain it more clearly: I open the modal with this button and after filling out the form in the modal, when I press the submit button, a loading popup appears and then the full black screen immediately.

Related part of my code:

const mutation = useMutation<AffirmationRes, AxiosError, AffirmationReq>({    mutationFn: createAffirmation,    onSuccess: async () => {      resetAffirmation("RESET_REQUEST", affirmationCtx);      setSelectedCategory("All");      setAffirmationText("");      setImage("");      setIsPrivate(true);      showSuccess("Affirmation created successfully!");      setVisible(false); // <- I think this cause the problem    },    onError: (err) => {      handleErrors(err, affirmationCtx);    },  });  const handlePress = () => {    const payload: AffirmationReqMode = {      mode: "manuel",      data: {        category: selectedCategory,        image: image,        isPublic: !isPrivate,        tags: [],        text: affirmationText,      },    };    const req = handleAffirmationReq(payload, affirmationCtx);    if (!req) {      return;    }    mutation.mutate(req);  };

If I remove setVisible(false) then no more black screen but why?

Quirks

  • If I close the modal using the cross button, there is no problem (Cross button closes the modal with setVisible(false) too by the way).
  • There is no such problem when I try it on my Android device and Android simulator (I can't try on an ios device cause I don't have any iPhone, so I have to use xcode simulator for that).
  • There is another modal that uses a similar (or even almost identical) system to the one this specific modal uses, and it doesn't cause any problems.
  • There are no errors on expo cli console.

What I use

  • Hardware: Mac mini m4
  • OS: macOS Tahoe 26.1
  • Simulator: Xcode simulator iPhone 16e and iPhone 17 pro (both iOS 26.1)
  • Xcode version: Version 26.1.1 (17B100)
  • The phone I have: Samsung galaxy A23
  • Environment: react-native (0.81.4), expo (54.0.12)

Full of my code

NewAffirmationForm.tsx (the problematic one)

import { ThinLoadingIcon } from "@/components/icons";import { AppPopup } from "@/components/overlays";import {  AppButton,  AppHeader,  AppPhotoInput,  AppPicker,  AppTextArea,  CloseButton,  ToggleCard,} from "@/components/ui";import { popupContent } from "@/content";import { spacing } from "@/design-tokens";import { AppTheme } from "@/design-tokens/colors";import usePremium from "@/features/payment/hooks/usePremium";import useImage from "@/hooks/useImage";import useStyles from "@/hooks/useStyles";import {  Affirmation,  AffirmationCategory,  AffirmationReq,  AffirmationReqMode,  AffirmationRes,} from "@/types";import { showSuccess } from "@/utils/toast";import { FlashListRef } from "@shopify/flash-list";import { useMutation } from "@tanstack/react-query";import { AxiosError } from "axios";import { router } from "expo-router";import { RefObject, useState } from "react";import { Modal, ScrollView, StyleSheet, View } from "react-native";import { useSafeAreaInsets } from "react-native-safe-area-context";import { createAffirmation } from "../api";import { categories } from "../content";import { useAffirmation } from "../hooks";import { handleErrors } from "../utils/api";import { resetAffirmation, setAffirmationPopup } from "../utils/dispatch";import { handleAffirmationReq } from "../utils/query";import AffirmationPopup from "./AffirmationPopup";type props = {  visible: boolean;  setVisible: React.Dispatch<React.SetStateAction<boolean>>;  affirmationsFeedRef: RefObject<FlashListRef<Affirmation> | null>;  onClose: () => void;};const NewAffirmationForm = ({ visible, setVisible, onClose }: props) => {  const [selectedCategory, setSelectedCategory] =    useState<AffirmationCategory>("All");  const [affirmationText, setAffirmationText] = useState<string>("");  const [image, setImage] = useState<string>("");  const [isPrivate, setIsPrivate] = useState<boolean>(true);  const insets = useSafeAreaInsets();  const { pickImage } = useImage();  const { styles, theme } = useStyles(makeStyles);  const affirmationCtx = useAffirmation();  const premium = usePremium();  const mutation = useMutation<AffirmationRes, AxiosError, AffirmationReq>({    mutationFn: createAffirmation,    onSuccess: async () => {      resetAffirmation("RESET_REQUEST", affirmationCtx);      setSelectedCategory("All");      setAffirmationText("");      setImage("");      setIsPrivate(true);      showSuccess("Affirmation created successfully!");      setVisible(false);    },    onError: (err) => {      handleErrors(err, affirmationCtx);    },  });  const handlePress = () => {    const payload: AffirmationReqMode = {      mode: "manuel",      data: {        category: selectedCategory,        image: image,        isPublic: !isPrivate,        tags: [],        text: affirmationText,      },    };    const req = handleAffirmationReq(payload, affirmationCtx);    if (!req) {      return;    }    mutation.mutate(req);  };  const handlePressUploadImage = () => {    const isPremium = premium.data?.data.subscription.isPremium;    if (!isPremium) {      setAffirmationPopup(affirmationCtx, popupContent.payment.photo, () => {        setVisible(false);        router.push("/paywall");      });      return;    }    pickImage(setImage);  };  return (<Modal visible={visible} animationType="slide" onRequestClose={onClose}><View        style={[          styles.contentContainer,          { paddingTop: insets.top, paddingBottom: insets.bottom },        ]}><ScrollView          contentContainerStyle={styles.container}          keyboardShouldPersistTaps="handled"          showsVerticalScrollIndicator={false}><AppHeader            title="New Affirmation"            icon={<CloseButton onPress={onClose} />}            side="right"          /><AppPicker            label="Pick a category"            value={selectedCategory}            setValue={setSelectedCategory}            data={categories}            icon="product"          /><AppTextArea            value={affirmationText}            setValue={setAffirmationText}            maxLength={100}            showLabel            label="Enter your affirmation"            placeholder="I am a peaceful sanctuary..."            showCharacterCount          /><AppPhotoInput            setImage={setImage}            image={image}            label="Photo (optional)"            showLabel            onUploadPress={handlePressUploadImage}            disabled={premium.isFetching}          /><ToggleCard            selected={isPrivate}            setSelected={setIsPrivate}            label="Make private"          /><AppButton onPress={handlePress}>            {mutation.isPending ? (<ThinLoadingIcon color={theme.primary} width={25} height={25} />            ) : ("Create"            )}</AppButton></ScrollView></View><AffirmationPopup />      {mutation.isPending && <AppPopup isVisible status="loading" />}</Modal>  );};const makeStyles = (theme: AppTheme) =>  StyleSheet.create({    container: {      backgroundColor: theme.background,      paddingHorizontal: spacing["s-4"],      paddingBottom: spacing["s-4"],      gap: spacing["s-4"],      justifyContent: "space-between",    },    contentContainer: {      gap: spacing["s-4"],      flex: 1,      backgroundColor: theme.background,    },  });export default NewAffirmationForm;

NormalEntry.tsx (the another similar modal (I mentioned) but not causing black screen like above one.)

import { ThinLoadingIcon } from "@/components/icons";import { AppButton, AppHeader, CloseButton } from "@/components/ui";import { DayStreak } from "@/components/ui/DayStreak";import { popupContent } from "@/content";import { spacing } from "@/design-tokens";import useTheme from "@/hooks/useTheme";import { CreateEntryRes, EntryReq, GratitudeEntry } from "@/types";import { getClientTimeZone } from "@/utils/date";import { buildDeviceString } from "@/utils/metaData";import { getTagsFromString } from "@/utils/string";import { showSuccess } from "@/utils/toast";import { FlashListRef } from "@shopify/flash-list";import { useMutation } from "@tanstack/react-query";import { AxiosError } from "axios";import { RefObject } from "react";import { Modal, ScrollView, View } from "react-native";import { GestureHandlerRootView } from "react-native-gesture-handler";import { useSafeAreaInsets } from "react-native-safe-area-context";import { createEntry } from "../api";import { TodaysAffirmation } from "../components";import { useEntry } from "../hooks";import {  AddEntrySection,  EntryEmoteSection,  EntryPhotoSection,  EntryToggleSection,} from "../sections";import { handleErrors } from "../utils/api";import {  resetEntry,  setEntryError,  setEntryPopup,  setEntryStatus,} from "../utils/dispatch";import EntryPopup from "./EntryPopup";const NormalEntry = ({  visible,  onClose,  setVisible,  entriesFeedRef,}: {  visible: boolean;  setVisible: React.Dispatch<React.SetStateAction<boolean>>;  entriesFeedRef: RefObject<FlashListRef<GratitudeEntry> | null>;  onClose: () => void;}) => {  const insets = useSafeAreaInsets();  const { theme } = useTheme();  const entryCtx = useEntry();  const mutation = useMutation<CreateEntryRes, AxiosError, EntryReq>({    mutationFn: createEntry,    onSuccess: (res) => {      showSuccess("Entry created successfully!");      resetEntry("RESET_REQUEST", entryCtx);      if (res) {        if (!res.data) return;        setVisible(false);        entriesFeedRef.current?.scrollToIndex({ index: 0, animated: true });      }    },    onError: (err) => {      handleErrors(err, entryCtx);    },  });  const handlePress = async () => {    const ctxReq = entryCtx.state.values.request;    const req: EntryReq = {      gratefulFor: ctxReq.gratefulFor,      mood: ctxReq.mood,      photo: ctxReq.photo,      isPrivate: ctxReq.isPrivate,      tags: getTagsFromString(ctxReq.gratefulFor.join(", ")),      metaData: {        deviceInfo: buildDeviceString(),        timeZone: getClientTimeZone(),      },    };    if (!Array.isArray(req.gratefulFor) || req.gratefulFor.length === 0) {      setEntryError(entryCtx, {        ...entryCtx.state.errors,        gratefulFor: ["add at least one"],      });      setEntryPopup(entryCtx, popupContent.createEntry.missingText);      setEntryStatus(entryCtx, "fail");      return;    }    if (!req.mood) {      setEntryError(entryCtx, {        ...entryCtx.state.errors,        mood: ["please select a mood"],      });      setEntryPopup(entryCtx, popupContent.createEntry.missingEmote);      setEntryStatus(entryCtx, "fail");      return;    }    mutation.mutate(req);  };  return (<Modal      transparent      visible={visible}      animationType="slide"      onRequestClose={onClose}><GestureHandlerRootView        style={{ flex: 1, backgroundColor: theme.foreground }}><View          style={{            flex: 1,            backgroundColor: theme.background,            paddingTop: insets.top,            paddingBottom: insets.bottom,          }}><ScrollView            contentContainerStyle={{              paddingHorizontal: spacing["s-4"],              paddingBottom: spacing["s-4"],              gap: spacing["s-4"],            }}            keyboardShouldPersistTaps="handled"            showsVerticalScrollIndicator={false}><View><AppHeader                title={new Date().toLocaleDateString("en-EU")}                icon={<CloseButton onPress={onClose} />}                side="right"              /><DayStreak /><View style={{ gap: spacing["s-6"] }}><AddEntrySection /><EntryEmoteSection /><EntryPhotoSection setVisible={setVisible} /><EntryToggleSection /><TodaysAffirmation /></View></View><AppButton onPress={handlePress}>              {mutation.isPending ? (<ThinLoadingIcon color={theme.primary} width={25} height={25} />              ) : ("Sent"              )}</AppButton></ScrollView></View></GestureHandlerRootView><EntryPopup /></Modal>  );};export default NormalEntry;

AffirmationScreen.tsx (Where I called the NewAffirmationForm component. Parent of that.)

import { ThinLoadingIcon } from "@/components/icons";import { AppPopup } from "@/components/overlays";import { AppHeader, AppText } from "@/components/ui";import { AFFIRMATIONS_FEED_QUERY_KEY } from "@/constants";import { spacing } from "@/design-tokens";import { AppTheme } from "@/design-tokens/colors";import { fetchAffirmations } from "@/features/affirmation/api";import { AffirmationCard } from "@/features/affirmation/components";import { useAffirmation } from "@/features/affirmation/hooks";import {  AffirmationPopup,  NewAffirmationForm,} from "@/features/affirmation/overlay";import {  AffirmationCollection,  AffirmationSlider,  EntryUnlockSection,} from "@/features/affirmation/sections";import {  closeAffirmationPopup,  resetAffirmation,} from "@/features/affirmation/utils/dispatch";import usePremium from "@/features/payment/hooks/usePremium";import useStyles from "@/hooks/useStyles";import useTheme from "@/hooks/useTheme";import { Affirmation, AffirmationCategory, AffirmationsRes } from "@/types";import { FlashList, FlashListRef } from "@shopify/flash-list";import { InfiniteData, useInfiniteQuery } from "@tanstack/react-query";import { AxiosError } from "axios";import { useMemo, useRef, useState } from "react";import {  RefreshControl,  StyleSheet,  TouchableOpacity,  View,} from "react-native";import { SafeAreaView } from "react-native-safe-area-context";const AffirmationScreen = () => {  const [showNewAffirmationForm, setShowNewAffirmationForm] =    useState<boolean>(false);  const [selectedFilter, setSelectedFilter] =    useState<AffirmationCategory>("All");  const { styles } = useStyles(makeStyles);  const { theme } = useTheme();  const affirmationCtx = useAffirmation();  const premium = usePremium();  const affirmationsFeedRef = useRef<FlashListRef<Affirmation>>(null);  const isPremium = premium.data?.data.subscription.isPremium;  const PAGE_SIZE = isPremium ? 5 : 3;  const {    data,    error,    isLoading,    isRefetching,    fetchNextPage,    hasNextPage,    isFetchingNextPage,    refetch,  } = useInfiniteQuery<    AffirmationsRes,    AxiosError,    InfiniteData<AffirmationsRes, number>,    [typeof AFFIRMATIONS_FEED_QUERY_KEY, AffirmationCategory],    number>({    queryKey: [AFFIRMATIONS_FEED_QUERY_KEY, selectedFilter],    initialPageParam: 1,    queryFn: ({ pageParam, queryKey, signal }) => {      const [, filter] = queryKey;      return fetchAffirmations({        params: { page: pageParam, limit: PAGE_SIZE, category: filter },        opts: { signal },      });    },    getNextPageParam: (lastPage, _allPages, lastPageParam) => {      if (!isPremium) return;      const pg = lastPage.data.pagination;      return pg.hasNext ? lastPageParam + 1 : undefined;    },  });  const affirmations: Affirmation[] = useMemo(() => {    return data?.pages.flatMap((p) => p.data.affirmations) ?? [];  }, [data]);  if (isLoading) {    return (<View style={{ padding: spacing["s-6"], alignItems: "center", flex: 1 }}><ThinLoadingIcon color={theme.primary} width={40} height={40} /></View>    );  }  if (error) {    return (<View style={{ padding: spacing["s-6"], flex: 1 }}><AppText>Something went wrong: {error.message}</AppText><TouchableOpacity onPress={() => refetch()}><AppText>Refetch (temp)</AppText></TouchableOpacity></View>    );  }  const handleClose = () => {    setShowNewAffirmationForm(false);    resetAffirmation("RESET_REQUEST", affirmationCtx);    closeAffirmationPopup(affirmationCtx);  };  return (<SafeAreaView style={styles.container} edges={["left", "right", "top"]}><FlashList        ref={affirmationsFeedRef}        style={styles.list}        data={affirmations}        keyExtractor={(item) => item?._id}        renderItem={({ item }) => (<AffirmationCard            text={item?.text.replace(/['"]+/g, "")}            image={{ uri: item.image }}          />        )}        ListHeaderComponent={<><AppHeader title="Affirmations" /><AffirmationSlider /><AffirmationCollection              selectedFilter={selectedFilter}              setSelectedFilter={setSelectedFilter}              setShowNewAffirmationForm={setShowNewAffirmationForm}            /></>        }        refreshControl={<RefreshControl refreshing={isRefetching} onRefresh={refetch} />        }        onEndReached={() => {          if (hasNextPage && !isFetchingNextPage) fetchNextPage();        }}        ListFooterComponent={<>            {!premium.data?.data.subscription.isPremium && (<EntryUnlockSection />            )}            {isFetchingNextPage && (<ThinLoadingIcon                color={theme.primary}                width={25}                height={25}                style={{ marginBottom: spacing["s-3"] }}              />            )}</>        }        onEndReachedThreshold={undefined}        showsVerticalScrollIndicator={false}      />      {showNewAffirmationForm && (<View style={{ flex: 1, backgroundColor: theme.foreground }}><NewAffirmationForm            visible={showNewAffirmationForm}            setVisible={setShowNewAffirmationForm}            affirmationsFeedRef={affirmationsFeedRef}            onClose={handleClose}          /></View>      )}      {(isLoading || premium.isFetching) && (<AppPopup isVisible status="loading" />      )}<AffirmationPopup /></SafeAreaView>  );};const makeStyles = (theme: AppTheme) =>  StyleSheet.create({    container: {      flex: 1,      backgroundColor: theme.background,      paddingHorizontal: spacing["s-4"],    },    list: {      flex: 1,      alignSelf: "stretch",    },  });export default AffirmationScreen;

handleAffirmationReq function

const handleAffirmationReq = (  payload: AffirmationReqMode,  affirmationCtx: AffirmationCtx): AffirmationReq | undefined => {  let req: AffirmationReq = payload.data;  if (payload.mode === "ai") {    console.log("ai"); // temp    const aiData = payload.data as AffirmationReqWithAI;    const aiPrompt = aiData?.aiPrompt?.trim() ?? "";    if (aiPrompt.length < 10) {      setAffirmationPopup(affirmationCtx, popupContent.affirmation.aiPrompt);      return;    }    req = {      category: aiData.category,      image: aiData.image,      isPublic: aiData.isPublic,      tags: getTagsFromString(aiPrompt),      generateWithAI: true,      aiPrompt,    };  } else if (payload.mode === "manuel") {    console.log("manuel"); // temp    const manualData = payload.data as AffirmationReqWithoutAI;    const text = manualData?.text?.trim() ?? "";    if (text.length < 10) {      setAffirmationPopup(        affirmationCtx,        popupContent.affirmation.textTooShort      );      return;    }    req = {      category: manualData.category,      image: manualData.image,      isPublic: manualData.isPublic,      tags: getTagsFromString(text),      text,    };  }  if (!req) setAffirmationPopup(affirmationCtx, popupContent.undefinedData);  return req;};

How can I call an async function on double tap?

$
0
0

I have an Expo app where I have an element. I want to call an async function when I double tap that element. For doing that, I found the react-native-gesture-handler library. I wrapped the whole component in a <GestureHandlerRootView> as the docs said, getting the code below. However, the async function editUserTask(), which is defined in another file, doesn't even run here when I double tap. Why is that? I've seen several ideas about using runOnJS(), but VS Code tells me it is deprecated. Any solutions?

// import...import {  Gesture,  GestureDetector,  GestureHandlerRootView,} from "react-native-gesture-handler";export default function Todo() { <GestureHandlerRootView>        // ...        userTasks[0]?.map((pendingUserTask, index) => {              const doubleTap = Gesture.Tap()                .numberOfTaps(2)                .onStart(() => {                  async () => {                    await editUserTask({                      id: pendingUserTask.id,                      title: pendingUserTask.title,                      description: pendingUserTask.description,                      deadline: pendingUserTask.deadline.toDate(),                      user_id: pendingUserTask.user_id as never,                      done: true,                      tags: pendingUserTask.tags,                    });                    console.log("A");                  };                });              return (<GestureDetector key={`pending-${index}`} gesture={doubleTap}><View collapsable={false}><TaskElement                      userTask={pendingUserTask}                      fetchTasksFunction={fetchUserTasks}                      openTaskModalFunction={openTaskModal}                    /></View></GestureDetector></GestureHandlerRootView>}

React Native Metro suddenly trying to process TypeScript files it can't understand

$
0
0

I get the following error after running npx expo start --dev-client to start my react native expo app:

SyntaxError in node_modules/react-freeze/src/index.tsx: Unexpected token, expected "," (8:1)

It seems Metro is trying to process TypeScript that it doesn't understand. Metro requires pre-compiled JavaScript.

I hadn't updated any dependencies, so it couldn't have introduced changes that require TypeScript files to be processed differently, or modified metro.config.js. I also hadn't changed any libraries, so none could contain uncompiled TypeScript files instead of precompiled JavaScript that would require Metro to handle .tsx files which would lead to errors if the TypeScript syntax is unsupported.

Babel and Metro hadn't been changed, so they shouldn't be processing previously ignored files. I didn't add any new dependencies or update existing ones, so TypeScript files couldn't have been introduced that Metro is now trying to process.

I had a similar error for a different library, but after modifying my metro.config.js and babel.config.js files it didn't come back, but now I get the same type of error for react-freeze.

My current metro.config.js:

// Learn more https://docs.expo.io/guides/customizing-metroconst { getDefaultConfig } = require('expo/metro-config');const exclusionList = require('metro-config/src/defaults/exclusionList');/** @type {import('expo/metro-config').MetroConfig} */const defaultConfig = getDefaultConfig(__dirname);module.exports = {    ...defaultConfig,    transformer: {        babelTransformerPath: require.resolve('metro-react-native-babel-transformer'),        assetPlugins: ['expo-asset/tools/hashAssetFiles'],        getTransformOptions: async () => ({            transform: {                experimentalImportSupport: false,                inlineRequires: true,            },        }),    },    resolver: {        ...defaultConfig.resolver,        extraNodeModules: {            ...defaultConfig.resolver.extraNodeModules,            // Add any custom mappings if needed        },        sourceExts: [            ...defaultConfig.resolver.sourceExts,'js', 'jsx', 'json', 'cjs'        ],        assetExts: [...defaultConfig.resolver.assetExts, 'ttf', 'webp'],        blacklistRE: exclusionList([/node_modules\/react-freeze\/.*/]),    }};defaultConfig.resolver.assetExts.push('ttf', 'webp');module.exports = defaultConfig;

My current babel.config.js:

module.exports = function (api) {    api.cache(true);    return {        presets: ["babel-preset-expo","@babel/preset-flow",            ["module:metro-react-native-babel-preset", {"useTransformReactJSXExperimental": true            }],"@babel/preset-typescript"        ],        plugins: ["react-native-reanimated/plugin","@babel/plugin-transform-flow-strip-types","@babel/plugin-transform-modules-commonjs"        ],        ignore: [            /node_modules\/(?!react-native|@react-native|react-clone-referenced-element|@react-native-community|expo|@expo|react-navigation|@react-navigation|@unimodules|unimodules|sentry-expo|native-base|@testing-library|react-native)/,        ],    };};

Expo: 52.0.47

React Native: 0.76.9

Metro: 0.81.5

Metro Config: 0.81.5

metro-react-native-babel-preset: ^0.77.0

metro-react-native-babel-transformer: ^0.77.0

Expo router stack screen not working based on order

$
0
0

Expo stack screen not working , Not getting load login page on first load based on order by insted (tabs) screen loading first. When iam trying to load my login screen first by changing screen order i'm not able to change it . I cleared catch and i did every possible things please helpme. Here is my _layout.tsx

import FontAwesome from '@expo/vector-icons/FontAwesome';import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';import { useFonts } from 'expo-font';import { SplashScreen, Stack } from 'expo-router';import { useEffect } from 'react';import { useColorScheme } from 'react-native';import { RootSiblingParent } from 'react-native-root-siblings';import SignInPage from './screens/Login';export {  // Catch any errors thrown by the Layout component.  ErrorBoundary,} from 'expo-router';// export const unstable_settings = {//   // Ensure that reloading on `/modal` keeps a back button present.//   initialRouteName: '(tabs)',// };export default function RootLayout() {  const [loaded, error] = useFonts({    SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),    ...FontAwesome.font,  });  // Expo Router uses Error Boundaries to catch errors in the navigation tree.  useEffect(() => {    if (error) throw error;  }, [error]);  return (<>      {/* Keep the splash screen open until the assets have loaded. In the future, we should just support async font loading with a native version of font-display. */}      {!loaded && <SplashScreen />}      {loaded &&  <RootSiblingParent><RootLayoutNav /></RootSiblingParent>}</>  );}function RootLayoutNav() {  const colorScheme = useColorScheme();  return (<><ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}><Stack><Stack.Screen name="Login"  options={{ title: 'Log in' }} /><Stack.Screen name="screens/Mylistings" options={{ title: 'Log in' }} /><Stack.Screen name="screens/Signup" options={{ title: 'Sign Up' }} /><Stack.Screen name="modal" options={{ presentation: 'modal' }} /><Stack.Screen name="(tabs)" options={{ headerShown: false }} /></Stack></ThemeProvider></>  );}

Login.tsx

import React, { useState } from 'react';import { View, TextInput, Text, StyleSheet } from 'react-native';import { TouchableOpacity } from 'react-native-gesture-handler';import PasswordInput from '../../components/user/PasswordInput';import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';import { Link, useRouter,Stack } from 'expo-router';import Toast from 'react-native-root-toast';import { FirebaseError } from 'firebase/app';const SignInPage = () => {  const router = useRouter();  const [email, setEmail] = useState('');  const [password, setPassword] = useState('');  const handleSignIn = async () => {    try {      const auth = getAuth();      await signInWithEmailAndPassword(auth, email, password);      // Handle successful sign-in      console.log('User signed in successfully');      // router.push('../../one');      // Perform any other necessary actions after successful sign-in    } catch (error) {      // Function to format Firebase error codes to user-friendly messages      const formatErrorMessage = (error :any) => {        switch (error.code) {          case 'auth/invalid-email':            return 'Invalid email address. Please enter a valid email.';          case 'auth/user-disabled':            return 'Your account has been disabled. Please contact support.';          case 'auth/user-not-found':          case 'auth/wrong-password':            return 'Invalid email or password. Please check your credentials.';          // Add more error codes and corresponding messages as needed          default:            return 'An error occurred. Please try again later.';        }      };      // Handle sign-in error      const firebaseError = error as FirebaseError;      const errorMessage = formatErrorMessage(firebaseError);      let toast = Toast.show(`Sign-in error: ${errorMessage}`, {        duration: Toast.durations.LONG,      });      // If you want to use console.error for debugging      // console.error('Sign-in error:', error);      // Add any necessary error handling logic or display error message to the user    }  };  return (<><Stack.Screen options={{ title: 'Oops!' }} /><View style={styles.container}><View style={styles.box}><TextInput          style={styles.input}          placeholder="Email"          value={email}          onChangeText={setEmail}          keyboardType="email-address"        /><PasswordInput          placeholder="Password"          value={password}          onChangeText={setPassword}        /><View style={{ flexDirection: 'row', alignItems: 'center' }}><TouchableOpacity style={styles.buttonContainer} onPress={handleSignIn}><Text style={styles.buttonText}>Sign In</Text></TouchableOpacity><Text>            New User? <Link href={'./Signup'}>Sign Up</Link></Text></View><Link href='./forgotPassword'>Forgot Password</Link></View></View></>  );};const styles = StyleSheet.create({  container: {    flex: 1,    backgroundColor: 'white',    justifyContent: 'center',    alignItems: 'center',    padding: 16,  },  box: {    width: '90%',    borderRadius: 4,    backgroundColor: 'white',  },  input: {    width: '100%',    marginBottom: 12,    padding: 8,    borderWidth: 1,    borderRadius: 4,  },  buttonContainer: {    backgroundColor: '#4287f5',    justifyContent: 'center',    alignItems: 'center',    borderRadius: 20,    paddingVertical: 10,    paddingHorizontal: 20,  },  buttonText: {    color: '#fff',    fontSize: 16,    fontWeight: 'bold',  },});export default SignInPage;

Lottie file not animated in react native expo

$
0
0

I'm using React Native with TypeScript and Expo. I downloaded a JSON file of an animated icon from Lottie Files, but it's not animating. I'm not sure what I'm missing; I followed the documentation.

this is my Imports

import LottieView from "lottie-react-native";import React, { useEffect, useRef } from "react"

useRef

 const animationRef = useRef<LottieView>(null);  useEffect(() => {    animationRef.current?.play();  }, []);

component

<LottieView            style={styles.lottie}            ref={animationRef}            source={require("../assets/animated_icon.json")} />

Expo camera permission causes white screen during page transition

$
0
0

I have Ran into an issue on android devices only, where during a page transition animation a white screen shows real fast. This doesn't have anything to do with the expo camera mounting, because even if don't render anything and only ask camera permission the white screen shows. If i just render a normal view it works perfectly. So the issue lies somewhere while i ask for permission, a timeout on the permission is my temporary fix but i do not want this.

I have another page where there is a permission check for camera access and here the white screen doesnt appear. I also want a check for permission on both pages not a global one. The issue only shows from Settings screen > Qr screen (where i check the permission )

Root background color is also not a solution. I have set a background color already in my app.config and works because i have seen it on the other pages. This issue is solely with the permission on this one page.

enter image description here

React Native: Azure ACS Chat SDK is not working on release build on iOS - WebSocket connection is terminated

$
0
0

I'm trying to implement chat feature in my mobile app and I faced a problem with WebSocket connection on release build.

I followed instructions from here:

https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/chat/react-native

and build the functionality into my app.

The bidirectional communication works on the development build (simulator and device) and I can receive the messages in real time. When I build a release build, the real-time communication is not working. Azure client is throwing no exception, all I found in system logs is something like this:

nw_endpoint_flow_failed_with_error [C14.1.1.1 146.75.121.155:443 in_progress channel-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, dns, uses wifi)] already failing, returning

The IP address:

146.75.121.155:443

I suspect that is the address of the WebSocket connection, which is terminated by iOS Network restrictions on the release build. That IP is different every time I open the chat.

I tried to open these addresses in browser and noticed certificate problem: Certificate Error

(please don't mind that IP on screenshot differs from the one I pasted).

I tried for test purposes set the flag:

NSAllowsArbitraryLoads

but it didn't helped either.

I'm out of leads, why this Azure Client is not working, had anyone tried such scenario and had this problem?

Cannot find getReactNativePersistence when using Firebase Auth with AsyncStorage in React Native (Expo)

$
0
0

I'm developing a React Native app using Expo and trying to persist Firebase Auth state across sessions with AsyncStorage.

Here is my firebaseConfig.ts file:

import { initializeApp } from "firebase/app";import { initializeAuth, getReactNativePersistence } from "firebase/auth";import ReactNativeAsyncStorage from '@react-native-async-storage/async-storage';const firebaseConfig = {  apiKey: "...",  authDomain: "...",  projectId: "...",  storageBucket: "...",  messagingSenderId: "...",  appId: "..."};export const app = initializeApp(firebaseConfig);export const auth = initializeAuth(app, {  persistence: getReactNativePersistence(ReactNativeAsyncStorage) });

When trying to import getReactNativePersistence, I get this error:

Module '"firebase/auth"' has no exported member 'getReactNativePersistence'.

Is there any way to properly use getReactNativePersistence in this setup, or another recommended approach to persist Firebase Auth state in Expo without ejecting or using a custom dev client?

Any help would be appreciated!

What I've already tried:

Installed dependencies:

npm install firebasenpm install @react-native-async-storage/async-storage

My package.json includes:

"@react-native-async-storage/async-storage": "^2.1.2","firebase": "^11.8.1"

Tried adding this to tsconfig.json based on suggestions I found:

{"compilerOptions": {"paths": {"@firebase/auth": ["./node_modules/@firebase/auth/dist/index.rn.d.ts"]    }  },"extends": "expo/tsconfig.base"}

However, this did not solve the issue. Also, when I check my node_modules/@firebase directory, there is no auth folder at all.

Keeping BLE and GPS active in background with screen locked (React Native + Expo)

$
0
0

I’m developing a mobile app in React Native using Expo, and I’m testing it through an EAS development build (not Expo Go). The app uses Bluetooth Low Energy (BLE) to communicate with an external device and relies on continuous geolocation updates.

The issue I’m facing is that when the phone screen is locked, I’m unable to keep both BLE and GPS active at the same time. After the device goes into lock screen, the OS starts restricting the app: sometimes the BLE connection drops, sometimes location updates stop, and occasionally both are suspended.

The intended behavior requires the app to work with the screen off, so background execution is essential. I’ve already tried requesting “always” location permissions, disabling battery optimizations, and using background tasks / services, but the behavior remains unstable once the screen is locked.

I’m trying to understand what the correct approach is in this scenario. Is a foreground service mandatory to reliably keep BLE and GPS running in the background? Are there known limitations with Expo / React Native regarding this kind of use case, or recommended architectural patterns to handle it properly?

Any insights, best practices, or references to documentation would be very helpful.

Thanks in advance.

How to Avoid Repeatedly Calling Hooks for Theme and Responsive Styles in React Native Components?

$
0
0

What I want to achieve: A performant react native theme manager/changer.

Styles should be usable like: <View style={styles.screen}> where styles comes from a styles.ts file where I define my style. There, I should have access to theme color (ex: Dark/Light) and dynamic dimensions (width, height), meaning that if I change to landscape and back, the styling updates accordingly.

Currently, I have this implementation. However, in every component I have to call this hook with my specific style const styles = useStyles();. I'd like to avoid that (or hide this) and simply only have to import them before using them, so I won't have to call such a hook for every single style I import in every component.

App.tsx

import { NavigationContainer, DefaultTheme } from '@react-navigation/native';import { createStackNavigator, StackScreenProps } from '@react-navigation/stack';import { Button, View, Text, TouchableOpacity } from 'react-native';import { useThemeStore, colors } from './themeStore';import { useStyles } from './styles';type RootStackParamList = {  Home: undefined;  Settings: undefined;};const Stack = createStackNavigator<RootStackParamList>();function HomeScreen({ navigation }: StackScreenProps<RootStackParamList, 'Home'>) {  const styles = useStyles();  const toggleTheme = useThemeStore(state => state.toggleTheme);  return (<View style={styles.screen}><View style={[styles.container, { marginTop: 40 }]}><Text style={styles.header}>Home Screen</Text><Text style={styles.text}>Current theme settings</Text></View><View style={styles.container}><Button title="Toggle Theme" onPress={toggleTheme} /><TouchableOpacity          style={[styles.button, { marginTop: 16 }]}          onPress={() => navigation.navigate('Settings')}><Text style={styles.buttonText}>Go to Settings</Text></TouchableOpacity></View></View>  );}function SettingsScreen({ navigation }: StackScreenProps<RootStackParamList, 'Settings'>) {  const styles = useStyles();  const theme = useThemeStore(state => state.theme);  return (<View style={styles.screen}><View style={styles.container}><Text style={styles.header}>Settings Screen</Text><Text style={styles.text}>Current Theme: {theme.toUpperCase()}</Text><TouchableOpacity          style={[styles.button, { marginTop: 16 }]}          onPress={() => navigation.goBack()}><Text style={styles.buttonText}>Go Back</Text></TouchableOpacity></View></View>  );}export default function App() {  const theme = useThemeStore(state => state.theme);  const navigationTheme = {    ...DefaultTheme,    dark: theme === 'dark',    colors: {      ...DefaultTheme.colors,      ...colors[theme],    },  };  return (<NavigationContainer theme={navigationTheme}><Stack.Navigator        screenOptions={{          headerStyle: {            backgroundColor: navigationTheme.colors.card,          },          headerTintColor: navigationTheme.colors.text,        }}><Stack.Screen name="Home" component={HomeScreen} /><Stack.Screen name="Settings" component={SettingsScreen} /></Stack.Navigator></NavigationContainer>  );}

themeStore.ts

import { MMKVLoader } from 'react-native-mmkv-storage';import {create} from 'zustand';import { persist } from 'zustand/middleware';import { storage } from './storage';export const storage = new MMKVLoader()  .withInstanceID('themeStorage')  .initialize();export const colors = {  light: {    primary: '#007AFF',    background: '#FFFFFF',    card: '#FFFFFF',    text: '#000000',    border: '#D3D3D3',    notification: '#FF3B30',  },  dark: {    primary: '#BB86FC',    background: '#121212',    card: '#1E1E1E',    text: '#FFFFFF',    border: '#383838',    notification: '#CF6679',  },};interface ThemeState {  theme: 'light' | 'dark';  toggleTheme: () => void;}export const useThemeStore = create<ThemeState>()(  persist(    (set) => ({      theme: 'light',      toggleTheme: () => set((state) => ({        theme: state.theme === 'light' ? 'dark' : 'light'      })),    }),    {      name: 'theme-storage',      storage: {        getItem: async (name) => {          const value = storage.getString(name);          return value ? JSON.parse(value) : null;        },        setItem: async (name, value) => {          storage.setString(name, JSON.stringify(value));        },        removeItem: async (name) => {          storage.removeItem(name);        },      },    }  ));

styles.ts

import { useThemeStore } from './themeStore';import { useDimensionsStore } from './dimensionsStore';import { StyleSheet } from 'react-native';import { colors } from './themeStore';export const useStyles = () => {  const theme = useThemeStore(state => state.theme);  const { width, height } = useDimensionsStore();  const themeColors = colors[theme];  return StyleSheet.create({    screen: {      flex: 1,      backgroundColor: themeColors.background,      width,      height,    },    container: {      padding: 16,      backgroundColor: themeColors.card,      borderRadius: 8,      margin: 8,    },    text: {      color: themeColors.text,      fontSize: 16,    },    header: {      color: themeColors.primary,      fontSize: 24,      fontWeight: 'bold',    },    button: {      backgroundColor: themeColors.primary,      padding: 12,      borderRadius: 8,      alignItems: 'center',    },    buttonText: {      color: themeColors.background,      fontWeight: 'bold',    },  });};

React Native iOS 26 Liquid Glass Text Input

$
0
0

I'm struggling to figure out how to properly create a text input, like in the attached file (add a reply text input), using React Native. I'm currently using Expo, but I am open to whatever. It should be just like the iOS native apps. I've talked with a lot of people and can't seem to find anything in documentation regarding this. Would someone know and would be able to point me into the right direction here?

What I'm trying to recreate


How to handle initial/default state when using tRPC RouterOutputs as the source of truth for TypeScript types?

$
0
0

I’m using tRPC in an Expo (React Native) app and deriving frontend types directly from the generated API output.

import type { RouterOutputs } from "../utils/trpc";type UserDetails = RouterOutputs["users"]["details"];

When initializing component state before the query finishes loading, I tried to define a default value:

export const defaultUser: UserDetails = {  id: 0,  username: "",};

TypeScript reports:

TS2322: Property 'profilePalette' is missing in type'{ id: number; username: string; }'but required in type 'RouterOutputs["users"]["details"]'

The profilePalette field was recently added on the backend and is required in the API response.

When client state is typed directly from a tRPC RouterOutputs type, how should the “not yet loaded” state be represented so that it remains type-safe without requiring a complete default object?

How can I place a Drawer inside a Stack with React Native Expo router?

$
0
0

I need to place a Drawer inside a Stack layout in my React Native Expo application. The idea is the following:

  • The main screen of the application consists of three screens, A, B and C.
  • The main screen contains a FAB (Floating Action Button), which allows opening editors
  • Each editor should be stacked on top of the drawer, so that the user can return to the drawer with the back button.
  • Screens A, B and C also have an option to open the editor.

I'm struggling with implementing this solution in such a way that it aligns with Expo router's concept of file-based navigation.

The src/app/_layout.tsx looks like following:

import { Stack, useRouter } from "expo-router";import { useCallback, useEffect, useState } from "react";import { useTranslation } from "react-i18next";import { FloatingActionButton } from "../components/main/floatingActionButton";import { Loading } from "../components/main/loading";import { ServiceContainer, ServiceContainerContext } from "../services/serviceContainer";const serviceContainer = new ServiceContainer();export default function RootLayout() {    const { t } = useTranslation();    const router = useRouter();    const [progress, setProgress] = useState(0);    const [isLoaded, setIsLoaded] = useState(false);    const onAddOperation = useCallback(() => {        router.push("./operationEditor");    }, []);    const onAddFundEntry = useCallback(() => {        router.push("./fundEntryEditor");    }, []);    const onAddMileage = useCallback(() => {        router.push("./mileageEditor");    }, []);    const fabItems = [        { name: t("root:addMileage"), icon: "fuel", action: onAddMileage },        { name: t("root:addFundEntry"), icon: "bank", action: onAddFundEntry },        { name: t("root:addOperation"), icon: "currency-eur", action: onAddOperation }    ];    useEffect(() => {        setProgress(0.5);        serviceContainer.initialize()            .then(() => {                setProgress(1);                setIsLoaded(true);                router.push("./main");            });    }, []);    return isLoaded ? (<><ServiceContainerContext.Provider value={serviceContainer}><Stack screenOptions={{ headerShown: false }} /><FloatingActionButton items={fabItems} /></ServiceContainerContext.Provider></>    ) : (<Loading progress={progress} />    );}

The src/app/main/_layout.tsx file looks like the following:

import Drawer from "expo-router/drawer";export default function Main() {  return (<Drawer><Drawer.Screen        name="dashboard"        options={{          drawerLabel: 'Dashboard',          title: 'dashboard',        }}      /><Drawer.Screen        name="operationList"        options={{          drawerLabel: 'Operations',          title: 'operations',        }}      /><Drawer.Screen        name="mileageList"        options={{          drawerLabel: 'Mileage',          title: 'mileage',        }}      /><Drawer.Screen        name="synchronization"        options={{          drawerLabel: 'Synchronization',          title: 'synchronization',        }}      /></Drawer>  );}

The src/app/index.tsx file is just a placeholder, because the main screen in the stack should be the drawer:

import { Text } from 'react-native';export default function Index() {  return (<Text>Index</Text>  )}

I don't know how to make the Stack show the Drawer in the /main route as the main screen (the one you can't go back from). After starting the application, I just see the placeholder index screen.

Also, attempts to enforce routing by calling router.replace("./main/dashboard") when loading/initialization is done causes the app to fall into infinite loop of showing loading screen, then loading screen again and so on.

How can I properly implement Stack on the top level and Drawer as a main screen on the stack using React Native with Expo?

Property * does not exist on type typeof * - static parent method

$
0
0

React Native, TypeScript code JS(non-TS) ORM module:

Parent BaseModel:

export default class BaseModel {  static createTable() {    ...  }  ...

My model of Animal does NOT redefine the method, it's just defined as:
export default class Animal extends BaseModel { ...

Now this code await Animal.createTable(); actually works, but VSCode TypeScript checker gives following error in code:

Property 'createTable' does not exist on type 'typeof Animal'.ts(2339)

Is this the editor/checker issue? Or should the JS/TS code be defined somehow better?

How to get rid of this error in React native

$
0
0

Does anyone else get this error and just have to ignore it constantly? Unexpected text node: . A text node cannot be a child of a <View>. There's not an extra '.' somewhere in my code as it suggests. there may be in some package that I'm using. I feel like every project I make, it inevitably gets this error and I can't trace it back to any piece of code that I've written. I could be wrong but I feel like I'm going crazy so does this happen to anyone else?

I've tried commenting out every line of code and it's always some component or something that I find to be throwing the error but I can never find the actual 'period' that is causing the error. It's so annoying

How do I use App.tsx instead of App.js in React?

$
0
0

I've created a react project with the instructions from it's documentation, which automatically has added the App.js file to the project. My issue is that I want to use App.tsx instead of App.js. For some reason, I am not able to run App.tsx file. How can I fix this, please?

I have read this question asked here, but I couldn't use it as it didn't make any sense to me since this is my first project with react.

This is my folder structure. enter image description here

It can be seen on this image, I get an error on the browser when I comment it out the App.js file. enter image description here

This is the index.js file, which is not changed.

import React from 'react';import ReactDOM from 'react-dom';import './index.css';import App from './App';import * as serviceWorker from './serviceWorker';ReactDOM.render(<App />, document.getElementById('root'));serviceWorker.unregister();

This is the package.json file

{"name": "dualnback","version": "0.1.0","private": true,"dependencies": {"@testing-library/jest-dom": "^4.2.4","@testing-library/react": "^9.4.0","@testing-library/user-event": "^7.2.0","@types/lodash": "^4.14.149","@types/material-ui": "^0.21.7","atob": "^2.1.2","bcryptjs": "^2.4.3","body-parser": "^1.19.0","bootstrap": "^4.4.1","concurrently": "^5.0.0","cookie-parser": "^1.4.4","cors": "^2.8.5","express": "^4.17.1","express-session": "^1.17.0","express-ws": "^4.0.0","hammerjs": "^2.0.8","jsonwebtoken": "^8.5.1","lodash": "^4.17.15","material-ui": "^0.20.2","mongoose": "^5.7.8","morgan": "^1.9.1","passport": "^0.4.0","passport-jwt": "^4.0.0","passport-local": "^1.0.0","react": "^16.12.0","react-dom": "^16.12.0","react-router-dom": "^5.1.2","react-scripts": "3.3.0","reactstrap": "^8.2.0","rxjs": "~6.4.0","rxjs-compat": "^6.5.3","tslib": "^1.10.0","typescript": "^3.7.3","websocket": "^1.0.30","zone.js": "~0.9.1"  },"scripts": {"start": "react-scripts start","build": "react-scripts build","test": "react-scripts test","eject": "react-scripts eject"  },"eslintConfig": {"extends": "react-app"  },"browserslist": {"production": [">0.2%","not dead","not op_mini all"    ],"development": ["last 1 chrome version","last 1 firefox version","last 1 safari version"    ]  },"devDependencies": {"@types/react-router-dom": "^5.1.3","@types/reactstrap": "^8.2.0","react-router-dom@next":"*"  }}

How to change the Favicon in Storybook

$
0
0

I'm trying to implement Storybook into my project and I can't seem to figure out how to change the Favicon. I'm using version 8.2.9 and I can't find any relevant information online. I don't have a manager-head.html file and I've tried things like

In my manager.ts file

document.addEventListener('DOMContentLoaded', () => {  document.querySelector('link[rel="shortcut icon"]')?.setAttribute('href', favicon);});

and also this in my main.ts file

config.plugins?.push(      new HtmlWebpackPlugin({        title: 'Title',        favicon: './public/favicon.png',       })    );

Does anyone know how to do this?

start function is not firing up the Copilot Steps

$
0
0

Current Behavior
Everything was working fine till I was at react-native: 0.77.3. As soon as I upgraded to react-native: 0.83.0, I start getting issue of copilot-Steps not appearing or appearing after a lot of time then expected. I fire up the copilot events at start so I use onLayout but when it didn't appear, I tried to fire those events using start function with a Pressable Button but that didn't worked either. Also I have newArchEnabled=true which is essential for my build. Trying Below to give you some glimpse of my code:

Input Code

const { start, copilotEvents } = useCopilot();  // start is ran using onLayout in main Viewconst [lastEvent, setLastEvent] = useState<string | null>(null);useEffect(() => {      copilotEvents.on("stepChange", (step) => {        setLastEvent(`stepChange: ${step?.name}`);      });      copilotEvents.on("start", () => {        setLastEvent(`start`);      });      copilotEvents.on("stop", () => {        setLastEvent(`stop`);        dispatch(updateLocalStorageInfo("Schedule_Walkthrough_Completed"))        console.log("Steps Completed")      });    }, [copilotEvents]);<View style={styles.mainStyle}               onLayout={() => {                 setTimeout(() => {                    start();                   }, 2000);               }}>
  • Repo link (1596th Line):
https://github.com/RAWLICK/Rescheduler-App/blob/main/src/Components/Tabs/Schedule.tsx

Expected behavior/code
start function should normally fire up the copilot events as it was working before

Environment

  • Device: [Realme P4x]

  • OS: [Android 15]

  • react-native-copilot: [3.3.3]

  • react-native: [0.83.0]

  • react-native-svg: [15.15.1]


How can I set the source of an image dynamically in React native with expo?

$
0
0

could someone help me dynamically define the source of an image in native React with expo?

because I send to my component this table:

const sidebarContent: SidebarItemData[] = [        { icon: "home.svg", alt: "Icône du tableau de bord", label: "Tableau de bord", uri: "DashboardHome" },        { icon: "cog-6-tooth.svg", alt: "Icône des paramètres", label: "Paramètres", uri: "DashboardSettings", bottom: true },        { icon: "arrow-left-start-on-rectangle.svg", alt: "Icône de déconnexion", label: "Déconnexion", uri: "Logout", bottom: true }    ];

and I put the path of the image in an image that is in my component sidebaritem but it doesn’t work because of the bundle, would someone have an idea please?

<SidebarItem                key={`${item.uri}-${index}`}                link={item.uri}                src={`src/assets/${item.icon}`}                alt={item.alt}                label={item.label}                className={index !== items.length - 1 ? "mb-4" : ""}                classNameImg="w-6 h-6"                isCollapsed={isCollapsed}            /><Image                source={{ uri: src }}


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>