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

Cannot find module '@/assets/image.png' or its corresponding type declarations.ts

$
0
0

I'm working on a React Native project with TypeScript, and I'm trying to import an image like this:

Cannot find module '@/assets/image.png' or its corresponding type declarations.tsI've already checked that the image exists at the specified path. How do I resolve this error and properly handle static assets like images in a TypeScript-based project?Any help would be appreciated!


What causes difference in how title is displayed for tabs with and without "/index" in their names in React Native Expo Navigation?

$
0
0

Just started learning react native, specifically the tab navigation, and went through the following tutorial on the topic.

What I can't figure out is some inconsistent behaviour of how the tabs titles are displayed.

E.g. consider the following file structure, in which both tab_1 and tab_2 dirs have the same file structure and the same immediate parent i.e. (tabs) dir:

app│ _layout.tsx│ (tabs)││ _layout.tsx││ _index.tsx││ tab_1│││ index.tsx││ tab_2││ index.tsx

Here is the code of app/(tabs)/_layout.tsx:

const TabsLayout = () => {    return (<Tabs><Tabs.Screen                name="index"                options={{                    headerTitle: "Home Tab",                    title: "Home Tab Title"                }}            /><Tabs.Screen                name="tab_1"                options={{                    headerTitle: "Tab 1",                    title: "Tab 1 Title"                }}            /><Tabs.Screen                name="tab_2/index"                options={{                    headerTitle: "Tab 2",                    title: "Tab 2 Title"                }}                 /></Tabs>    )} 

Notice that name for the tab_1 tab is just "tab_1", but the name for the tab_2 tab is "tab_2/index" (i.e. not just "tab_2" unlike with its tab_1 counterpart). With such setup, the tab_2 title is correctly displayed i.e. "Tab 2 Title".

enter image description here

However, once I change the tab_2 name to just "tab_2", the name changes to "tab_2/index", which is the path to the index.tsx in tab_2 dir.

enter image description here

Interestingly enough, if I change the tab_1 name to "tab_1/index, the tab_1 title changes to "tab_1", and not "tab_1/index", unlike its tab_2 counterpart.

enter image description here

The author of the article specifically mentions that such name difference for tab_1 and tab_2 has to take place:

Modify app/(tabs)/_layout.tsx to change the name for the tab from tab_1/index to tab_1

but doesn't indicate the reason for it.Since I've asked him, but hasn't gotten a reply, wondering if anyone knows

  1. the reason for such different behaviour of the tabs, despite the same structure?
  2. if there is a way to make them work identical (i.e. both tabs names have the same pattern e.g. either "tab_1" & "tab_2" or "tab_1/index" & "tab_2/index" and the titles show properly for both of them)?

Thank you

metro.config.js Using NativeWind and SVG transformer

$
0
0

I've been searching for hours how to mix NativeWind and SVG reader for react native application, here's the answer for me:

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;

The action 'RESET' with payload {"index":0,"routes":[{"name":"Home"}]} was not handled by any navigator

$
0
0

I am stuck with an error. When I am trying to reset the screen navigation stack, I am getting this:

ERROR The action 'RESET' with payload {"index":0,"routes":\[{"name":"Home"}\]} was not handled by any navigator.

This is a development-only warning and won't be shown in production.

This is my codeApp.tsx

import { StatusBar } from "expo-status-bar";import { ActivityIndicator, StyleSheet, View } from "react-native";import { useEffect, useState } from "react";// Providersimport AppProvider from "./src/Context/Provider";import AuthProvider, { useAuth } from "./src/Context/AuthContext";// Navigationimport { NavigationContainer } from "@react-navigation/native";import AuthenticatedStack from "./src/Navigation/AuthenticatedStack";import UnauthenticatedStack from "./src/Navigation/UnauthenticatedStack";// App Navigatorconst AppNavigator = () => {  const { isLoggedIn } = useAuth();  const [loading, setLoading] = useState(true);  console.log('====================================');  console.log('isLoggedIn', isLoggedIn);  console.log('====================================');  useEffect(() => {    setTimeout(() => setLoading(false), 1000);  }, []);  if (loading) {    return (<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}><ActivityIndicator size="large" color="#0000ff" /></View>    );  }  return isLoggedIn ? <AuthenticatedStack /> : <UnauthenticatedStack />;};export default function App() {  return (<AppProvider><AuthProvider><NavigationContainer><AppNavigator /></NavigationContainer></AuthProvider></AppProvider>  );}const styles = StyleSheet.create({});

AuthenticatedStack:

import { createStackNavigator } from '@react-navigation/stack';import HomeScreen from '../Screens/HomeScreen';import ProfileScreen from '../Screens/ProfileScreen';const Stack = createStackNavigator();const AuthenticatedStack = () => (<Stack.Navigator screenOptions={{ headerShown: false }}><Stack.Screen name="Home" component={HomeScreen} /><Stack.Screen name="Profile" component={ProfileScreen} /></Stack.Navigator>);export default AuthenticatedStack;

UnauthenticatedStack:

import { createStackNavigator } from '@react-navigation/stack';import SignupScreen from '../Screens/SignupScreen';import LoginScreen from '../Screens/LoginScreen';const Stack = createStackNavigator();const UnauthenticatedStack = () => (<Stack.Navigator screenOptions={{ headerShown: false }}><Stack.Screen name="Signup" component={SignupScreen} /><Stack.Screen name="Login" component={LoginScreen} /></Stack.Navigator>);export default UnauthenticatedStack;

Logout Function:

const handleLogout = () => {    setUser(null);    AsyncStorage.removeItem("jwtToken");    AsyncStorage.removeItem("isLoggedIn");    navigation.reset({      index: 0,      routes: [{ name: "Login" }],    })  };

Login Function:

const handleLogin = ()=>{await AsyncStorage.setItem("jwtToken", token);        await AsyncStorage.setItem("isLoggedIn", JSON.stringify(true));        navigation.reset({          index: 0,          routes: [{ name: "Home" }],        });}

I think when I am not logged screen the state on isLoggedIn is false and it loads the UnauthenticatedStack. Then when the state of the isLoggedIn changes to true after logging in, the AuthenticatedStack does not load and it shows error that Home is not recognized as the route.

When I reload the app, the AuthenticatedStack loads and the Home screen is shown. This also happens when I log out from the profile screen.

Can someone tell me the solution? I have browsed the whole internet and read the documentation but can not find the solution to this.

I want it so that if I log in to the app it redirects to the home screen, and when I log out then it redirects to the login screen

TypeScript getting error TS2307 cannot find module 'src/' when running tsc

$
0
0

I am trying to configure my tsconfig.json for a package inside my react native project with metro to add module resolution and make the code cleaner so instead of

import { LABELS } from "../../../constants";

I can just write

import { LABELS } from "src/constants"; or import { LABELS } from "@app-content/constants";

here is my tsconfig.json

{"compilerOptions": {"baseUrl": ".","paths": {"@app-content/*": ["./src/*"]        }    },"include": ["__tests__/**/*", "src/**/*", "typings/*"]}

so the folder structure is like this:

root--src--tsconfig.json--packages---test-package----src-----components-----constants----tsconfig.json

There's a tsconfig file at root level and then there's one inside packages/test-package within this test-package I want to be able to use it's src/.. or @app-content/..

The resolution is working correctly on vs-code if I write "src/" or "@app-content/" it shows the correct folders and there are no errors at that time, also the app is running successfully but when I run the command tsc it gives error

error TS2307: Cannot find module 'src/constants' or its corresponding type declarations.

Uncaught Error: useNatureRecord must be used within a NatureRecordProvider in React Native TypeScript app

$
0
0

I'm developing a React Native app with TypeScript and using Firebase for data storage. In my app, I have a context provider (NatureRecordProvider) that should wrap two main screens, NatureRecordForm and RecordStorage. However, when I navigate to these screens, I encounter the following error in my web server:

Error Message:

enter image description here

The full error trace suggests that useNatureRecord is being used outside of the NatureRecordProvider, though I believe I've wrapped the necessary components with the provider.

Here’s a summary of my setup:

  1. Context Setup:
  • NatureRecordProvider is defined in RecordContext.tsx and provides several functions (addEnvironmentalRecord, addBiologicalRecord, etc.) to manage records.
  • useNatureRecord is a custom hook to access the context.
  1. Navigation Configuration:
  • RecordNavigation component wraps NavigationContainer with NatureRecordProvider.
  1. Component Usage:
  • NatureRecordForm and RecordStorage are both accessed via RecordNavigation.

Code Samples:

  1. RecordNavigation.tsx:
const RecordNavigation = () => {  return (<NatureRecordProvider><NavigationContainer><Stack.Navigator initialRouteName="NatureRecordForm"><Stack.Screen name="NatureRecordForm" component={NatureRecordForm} /><Stack.Screen name="RecordStorage" component={RecordStorage} /></Stack.Navigator></NavigationContainer></NatureRecordProvider>  );};
  1. RecordStorage.tsx
import React, { useState, useEffect } from 'react';import { ScrollView, Alert, View, Text, Image, StyleSheet } from 'react-native';import { useNatureRecord } from '../../RecordLogic/RecordContext'; // Adjust the path as neededimport TopBanner from '../../RecordComponents/TopBanner';import TabNavigation from '../../RecordComponents/TabNavigation';import { NatureRecord as ImportedNatureRecord, NatureRecord } from '../../RecordLogic/NatureRecordInput';interface LocalNatureRecord extends ImportedNatureRecord {  sliders: any;}const RecordStorage: React.FC = () => {  console.log("NatureRecordStorage component rendered");  const { environmentalRecords: rawEnvironmentalRecords, biologicalRecords: rawBiologicalRecords, fetchRecords } = useNatureRecord();  const [selectedTab, setSelectedTab] = useState<string>('environmental');  useEffect(() => {    fetchRecords();  }, []);  const environmentalRecords: LocalNatureRecord[] = rawEnvironmentalRecords.map(record => ({    ...record,    id: record.id || 0, // Ensure id is present  }));  const biologicalRecords: LocalNatureRecord[] = rawBiologicalRecords.map(record => ({    ...record,    id: record.id || 0, // Ensure id is present  }));  const handleClosePress = () => {    Alert.alert("닫기","정말닫으시겠습니까?",      [        { text: "취소", onPress: () => console.log("Close canceled"), style: "cancel" },        { text: "확인", onPress: () => console.log("Close confirmed") }      ]    );  };  const handleTabPress = (tab: string) => {    setSelectedTab(tab);  };  const { EnvironmentalRecordsList, BiologicalRecordsList }: { EnvironmentalRecordsList: React.FC<{ records: LocalNatureRecord[]; }>; BiologicalRecordsList: React.FC<{ records: LocalNatureRecord[]; }>; } = createRecordLists();  return (<ScrollView style={{ flex: 1, padding: 0, margin: 0 }}><TopBanner         source={require('C:\\app\\my-app\\NatureImg\\close.svg')}         text="도감"        onClosePress={handleClosePress}      /><TabNavigation onTabPress={handleTabPress} selectedTab={selectedTab} /><View style={{ padding: 20 }}>        {selectedTab === 'environmental'&& (<EnvironmentalRecordsList records={environmentalRecords} />        )}        {selectedTab === 'biological'&& (<BiologicalRecordsList records={biologicalRecords} />        )}</View></ScrollView>  );  function createRecordLists() {    const EnvironmentalRecordsList: React.FC<{ records: NatureRecord[]; }> = ({ records }) => (<View>        {records.map((record, index) => (<View key={index} style={styles.recordContainer}><Text style={styles.reviewText}>{record.review}</Text><Image source={{ uri: record.photo }} style={styles.photo} />            {record.sliders.map((slider: { name: string; value: number; }, sliderIndex: number) => (<Text key={sliderIndex} style={styles.sliderText}>{slider.name}: {slider.value}</Text>            ))}</View>        ))}</View>    );    const BiologicalRecordsList: React.FC<{ records: NatureRecord[]; }> = ({ records }) => (<View>        {records.map((record, index) => (<View key={index} style={styles.recordContainer}><Text style={styles.reviewText}>{record.review}</Text><Image source={{ uri: record.photo }} style={styles.photo} />            {record.sliders.map((slider: { name: string; value: number; }, sliderIndex: number) => (<Text key={sliderIndex} style={styles.sliderText}>{slider.name}: {slider.value}</Text>            ))}</View>        ))}</View>    );    return { EnvironmentalRecordsList, BiologicalRecordsList };  }};const styles = StyleSheet.create({  recordContainer: {    marginBottom: 20,    padding: 10,    borderWidth: 1,    borderColor: '#ccc',    borderRadius: 5,  },  reviewText: {    fontSize: 16,    fontWeight: 'bold',  },  photo: {    width: 100,    height: 100,    marginVertical: 10,  },  sliderText: {    fontSize: 14,  },});export default RecordStorage;
  1. NatureRecordForm.tsx
import React, { useState, useMemo, useCallback } from 'react';import { ScrollView, View, TextInput, StyleSheet, Alert, Text } from 'react-native';import { useNavigation } from '@react-navigation/native';import { useNatureRecord } from '@/RecordLogic/RecordContext';import SaveButton from '@/RecordComponents/SaveButton'; import ReviewComponentStyle from '@/RecordStyle/ReviewComponentStyle'; import TopBanner from '@/RecordComponents/TopBanner';import SliderComponent from '@/RecordComponents/SliderComponent';import PhotoDisplay from '@/RecordComponents/PhotoDisplay';import BigQuestionStyle from '@/RecordStyle/BigQuestionStyle';const initialSliders = [  { name: '탐험한곳은얼마나아름다웁니까?', value: 50 },  { name: '다른사람에게이곳을탐험하는걸추천하고싶으면어느정도?', value: 50 },  { name: '탐험하는동안얼마나즐거웠나요?', value: 50 },  { name: '탐험하는동안얼마나가치있었나요?', value: 50 },  { name: '탐험난이도는어느정도였나요?', value: 50 },];const labels = [  { text: '아름다움' },  { text: '추천수준' },  { text: '즐거움' },  { text: '가치' },  { text: '난이도' },];const NatureRecordForm: React.FC = () => {  const [review, setReview] = useState('');  const [photo, setPhoto] = useState('');  const [sliders, setSliders] = useState(initialSliders);  const [inputHeight, setInputHeight] = useState(40); // Initial height for the TextInput  const navigation = useNavigation<any>();  const { addEnvironmentalRecord } = useNatureRecord();  const memoizedSliders = useMemo(() => sliders, [sliders]);  const handleAddRecord = useCallback(async () => {    try {      const newRecord = {        id: Date.now(), // Generate a unique id        review,        photo,        sliders: memoizedSliders,        timestamp: new Date().toISOString(), // Ensure timestamp is in a consistent format      };      await addEnvironmentalRecord(newRecord);      Alert.alert('Record saved successfully!');      navigation.navigate('RecordStorage'); // Navigate to RecordStorage page    } catch (error) {      console.error('Error saving record: ', error);      Alert.alert('Failed to save record.');    }  }, [review, photo, memoizedSliders, addEnvironmentalRecord, navigation]);  const handleSave = useCallback((data: { name: string; value: number }[]) => {    setSliders(data);  }, []);  const handleClosePress = useCallback(() => {    Alert.alert("뒤로넘어가면기록이삭제되는데괜찮습니까?","",      [        { text: "취소", style: "cancel" },        { text: "확인", onPress: () => console.log("Close confirmed") }      ]    );  }, []);  return (<ScrollView style={{ flex: 1, padding: 0, margin: 0 }}><TopBanner        source={require('C:\\app\\my-app\\NatureImg\\close.svg')}        text="자연환경도감등록"       onClosePress={handleClosePress}      />      {photo ? <PhotoDisplay photoUri={photo} /> : null}<View style={BigQuestionStyle.container}><Text style={BigQuestionStyle.howWouldYou}>탐험을하셨군요!{'\n'}{'\n'}다음을평가해주세요!</Text></View><SliderComponent initialSliders={memoizedSliders} onSave={handleSave} labels={labels} /><View style={{ marginBottom: 20 }} /><View style={ReviewComponentStyle.inputContainer}><View style={ReviewComponentStyle.inputWrapper}><TextInput            placeholder="한줄입력"            value={review}            onChangeText={setReview}            style={[ReviewComponentStyle.input, { height: inputHeight }]}            multiline            onContentSizeChange={(e) => setInputHeight(e.nativeEvent.contentSize.height)}          /></View></View><TextInput        placeholder="사진 URL 입력"        value={photo}        onChangeText={setPhoto}        style={ReviewComponentStyle.photo}      /><View style={styles.buttonWrapper}><SaveButton title="기록저장" onPress={handleAddRecord} /></View></ScrollView>  );};const styles = StyleSheet.create({  buttonWrapper: {    marginVertical: 20,    alignItems: 'center',  },});export default React.memo(NatureRecordForm);
  1. RecordContext.tsx
import React, { createContext, useState, useContext, ReactNode, useEffect } from "react";import { collection, addDoc, getDocs } from 'firebase/firestore';import { ref, uploadBytes, getDownloadURL } from 'firebase/storage';import { db, storage } from '@/firebase';interface NatureRecord {  id: number;  photo: string;  review: string;  sliders: { name: string; value: number }[];}interface NatureRecordContextProps {  environmentalRecords: NatureRecord[];  biologicalRecords: NatureRecord[];  addEnvironmentalRecord: (record: NatureRecord) => Promise<void>;  addBiologicalRecord: (record: NatureRecord) => Promise<void>;  fetchRecords: () => Promise<void>;}const NatureRecordContext = createContext<NatureRecordContextProps | undefined>(undefined);export const NatureRecordProvider = ({ children }: { children: ReactNode }) => {  console.log("Provider rendering"); // Add this line to check if the provider is rendering  const [environmentalRecords, setEnvironmentalRecords] = useState<NatureRecord[]>([]);  const [biologicalRecords, setBiologicalRecords] = useState<NatureRecord[]>([]);  const addEnvironmentalRecord = async (record: NatureRecord) => {    console.log("Adding environmental record", record); // Debugging log    try {      let photoURL = '';      if (record.photo) {        const storageReference = ref(storage, `photos/${Date.now()}_${record.photo}`);        const response = await fetch(record.photo);        const blob = await response.blob();        await uploadBytes(storageReference, blob);        photoURL = await getDownloadURL(storageReference);      }      const newRecord = {        ...record,        photo: photoURL,        timestamp: new Date().toISOString(),      };      await addDoc(collection(db, 'environmentalRecords'), newRecord);      setEnvironmentalRecords([...environmentalRecords, newRecord]);    } catch (error) {      console.error('Error saving record to Firebase: ', error);    }  };  const addBiologicalRecord = async (record: NatureRecord) => {    console.log("Adding biological record", record); // Debugging log    try {      let photoURL = '';      if (record.photo) {        const storageReference = ref(storage, `photos/${Date.now()}_${record.photo}`);        const response = await fetch(record.photo);        const blob = await response.blob();        await uploadBytes(storageReference, blob);        photoURL = await getDownloadURL(storageReference);      }      const newRecord = {        ...record,        photo: photoURL,        timestamp: new Date().toISOString(),      };      await addDoc(collection(db, 'biologicalRecords'), newRecord);      setBiologicalRecords([...biologicalRecords, newRecord]);    } catch (error) {      console.error('Error saving record to Firebase: ', error);    }  };  const fetchRecords = async () => {    console.log("Fetching records"); // Debugging log    try {      const environmentalSnapshot = await getDocs(collection(db, 'environmentalRecords'));      const biologicalSnapshot = await getDocs(collection(db, 'biologicalRecords'));      const environmentalData = environmentalSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })) as unknown as NatureRecord[];      const biologicalData = biologicalSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })) as unknown as NatureRecord[];      setEnvironmentalRecords(environmentalData);      setBiologicalRecords(biologicalData);    } catch (error) {      console.error('Error fetching records from Firebase: ', error);    }  };  useEffect(() => {    fetchRecords();  }, []);  return (<NatureRecordContext.Provider value={{ environmentalRecords, biologicalRecords, addEnvironmentalRecord, addBiologicalRecord, fetchRecords }}>      {children}</NatureRecordContext.Provider>  );};export const useNatureRecord = () => {  const context = useContext(NatureRecordContext);  if (!context) {    throw new Error("useNatureRecord must be used within a NatureRecordProvider");  }  return context;};

Troubleshooting Steps:

  • Confirmed that NatureRecordProvider wraps NavigationContainer.
  • Checked that useNatureRecord is only called within components under RecordNavigation.

Questions:

  • Why am I receiving this error even though NatureRecordProvider appears to wrap the necessary components?
  • Could this be related to the navigation structure or async data loading within the provider?

Any guidance on resolving this error would be greatly appreciated!

I expected the NatureRecordProvider context to provide data to all components within the navigation stack, as I wrapped the navigation stack in NatureRecordProvider. I anticipated that useNatureRecord would work within both NatureRecordForm and RecordStorage without error. However, instead, I encountered the error message: “useNatureRecord must be used within a NatureRecordProvider.”

Typescript Error: Argument of type '{ task: string; }' is not assignable to parameter of type 'void'

$
0
0

Getting this error when trying to pass an argument through the dispatch method.

Error: Argument of type '{ task: string; }' is not assignable toparameter of type 'void'

index.tsx:

import store from "../store/configureStore";import { addTask, completeTask, removeTask } from "../store/tasks";export default function Gallery() {  /* store.subscribe(() => {    console.log("Updated", store.getState());  }); */  // console.log(addTask({ task: "Task 1" }));  store.dispatch(addTask({ task: "Task 1" })); // getting error on these dispatch method lines  store.dispatch(addTask({ task: "Task 2" })); //  console.log(store.getState());  store.dispatch(completeTask({ id: 1 }));     //  store.dispatch(removeTask({ id: 1 }));       //  // store.dispatch(fetchTodo());  console.log(store.getState());

tasks.js:

// Actionsexport const addTask = createAction("ADD_TASK");export const removeTask = createAction("REMOVE_TASK");export const completeTask = createAction("TASK_COMPLETED");

Using Redux-Thunk it works but not with Redux-Toolkit.


Animated View deactivating touchability

$
0
0

So i have this component in my RN application. when I try to animated the component by wrapping it in an animated view, the touchable opacity in the component stops working. it does not even respond to touches but as soon as i take out the animated view, everything works fine and the component is clickable.

`<Animated.Viewstyle={{width: width,justifyContent: "center",alignItems: "center",position: "absolute",top: height * 0.1,transform: [{ translateY }],backgroundColor: theme.colors.mainBlue,paddingBottom: 15,}}

        pointerEvents={"box-none"}><View style={styles.switchBar}>            {options.map((option) => (<TouchableOpacity                    key={option}                    onPress={() => handleSelect(option)}                    style={[                        styles.option,                        selected === option ? styles.selected : styles.unselected,                    ]}><Text                        style={[                            styles.optionText,                            selected === option ? styles.selectedText : styles.unselectedText,                        ]}>                        {option}</Text></TouchableOpacity>            ))}</View></Animated.View>`

there is the code snipet. the touchable opacity just does not work when the animated view is on but when i take it out, it does.

I tried PointersEvent="Box-none" bur that did not work.i also set useNativeDriver={false} on the animation but that also did not fix it.

Shopify/restyle Pass variant value to child component

$
0
0

Apologies if this has been answered elsewhere. Is there a typesafe way to pass variant information to child components?

I am trying to create a custom Button component using the useRestyle hook as descriebd in the page https://shopify.github.io/restyle/fundamentals/components/custom-components. I have this code:

const Button = ({  onPress,  label,  buttonType,  variant,  ...rest}: Props) => {  const props = useRestyle(restyleFunctions, {variant, ...rest});

I extracted out the variant, but passed it back to useRestyle as it expects the variant prop in the second parameter. I lose type safety though when I do so.

Eg: Button variant is 'primary' | 'secondary'. restyleFunctions shows the error:The types of 'propertiesMap.variant' are incompatible between these types.Type 'boolean | undefined' is not assignable to type 'boolean'.Type 'undefined' is not assignable to type 'boolean'.ts(2345)

Thank you.

what are the necessary parameters need when using in constainer style `display : "flex" in react?` [duplicate]

$
0
0

I'm new to react so as the title implies, my question is "what is flex in display : "flex" in react?"

when reading multiple documentation it says that it's for flexbox, which from my understanding is for laying out the components but this just further confuses me. I assume that in this case, the height and width are what flexbox will use to distance itself from other components if I understand correctly.

const ColoredCircleIcon = ({ color = 'orange', size = 50 }: ColoredCircleIconProps): JSX.Element => {  const containerStyles: React.CSSProperties = {    display: 'flex',    alignItems: 'center',    justifyContent: 'center',    height: '100vh',    // Full viewport height    width: '100vw',     // Full viewport width  };  return (<div style={containerStyles}><FaCircle color={color} size={size} /> {/* Circle Icon */}</div>  );};

in the react documentation it shows code examples but without any display : "flex"which also begs the question, what are the parameters that display : "flex" will need for the flexbox algorithm. And how does one find the list of parameters?

How to install Storybook properly in Expo project?

$
0
0

I have created a demo project on react-native following the instruction on their official site. I chose to use Expo platform and the project is typescript based. I tried to add a storybook also following the instructions on their official site. Installing it with the line bellow

npx sb init --type react

allowed me to run successfully a storybook with default installation. Durring installation I had to choose between Webpack 5 or Vite and I selected Webpack. The installation completed with no errors. But that storybook is not capble of resolving React Native components. Then I execute another line according to the instructions:

yarn add react-dom react-native-web babel-plugin-react-native-web @storybook/addon-react-native-web @react-native/babel-preset --dev

Then I modified .storybook/main.ts to include a plugin that would allow me to import React Native components in my storybook as explained in the official site. After that I tried to run the storybook, but the terminal gave me the following error:

> yarn storybookyarn run v1.22.19warning ../package.json: No license field$ storybook dev -p 6006@storybook/core v8.3.6info => Starting manager..info => Starting preview..info => Using implicit CSS loadersUsing @react-native/babel-presetinfo => Using default Webpack5 setup<i> [webpack-dev-middleware] wait until bundle finishedERROR in mainModule not found: Error: Can't resolve 'babel-loader' in '/home/zemia/Coding/prime.code/pawmatix'preview compiled with 1 error=> Failed to build the preview99% end closing watch compilationWARN Force closed preview buildSB_BUILDER-WEBPACK5_0003 (WebpackCompilationError): There were problems when compiling your code with Webpack.Run Storybook with --debug-webpack for more information.    at starter (./node_modules/@storybook/builder-webpack5/dist/index.js:1:8004)    at starter.next (<anonymous>)    at Module.start (./node_modules/@storybook/builder-webpack5/dist/index.js:1:9972)    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)    at async storybookDevServer (./node_modules/@storybook/core/dist/core-server/index.cjs:47328:11)    at async buildOrThrow (./node_modules/@storybook/core/dist/core-server/index.cjs:46581:12)    at async buildDevStandalone (./node_modules/@storybook/core/dist/core-server/index.cjs:48518:78)    at async withTelemetry (./node_modules/@storybook/core/dist/core-server/index.cjs:47080:12)    at async dev (./node_modules/@storybook/core/dist/cli/bin/index.cjs:2877:3)    at async r.<anonymous> (./node_modules/@storybook/core/dist/cli/bin/index.cjs:2929:74)WARN Broken build, fix the error above.WARN You may need to refresh the browser.

I installed babel-loader, because I neighter found that package mentioned anywhere in my package.json, nor it existed under node_modules. Thats the command I applied for installing babel-loader as sugested in the official repository.

npm install -D babel-loader @babel/core @babel/preset-env webpack

Storybook initialization is still broken but that time with another error:

$ npm run storybook> pawmatix@1.0.0 storybook> storybook dev -p 6006@storybook/core v8.3.6attention => Storybook now collects completely anonymous telemetry regarding usage.This information is used to shape Storybook's roadmap and prioritize features.You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:https://storybook.js.org/telemetryinfo => Starting manager..info => Starting preview..info => Using implicit CSS loadersUsing @react-native/babel-presetinfo => Using default Webpack5 setup<i> [webpack-dev-middleware] wait until bundle finishedERROR in ./node_modules/@storybook/instrumenter/dist/index.js 1:2542-2556Module not found: Error: Can't resolve 'tty' in '/home/zemia/Coding/prime.code/pawmatix/node_modules/@storybook/instrumenter/dist'BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.This is no longer the case. Verify if you need this module and configure a polyfill for it.If you want to include a polyfill, you need to:    - add a fallback 'resolve.fallback: { "tty": require.resolve("tty-browserify") }'    - install 'tty-browserify'If you don't want to include a polyfill, you can use an empty module like this:    resolve.fallback: { "tty": false } @ ./node_modules/@storybook/test/dist/index.js 20:13534-13568 179:26783-26817 @ ./stories/Page.stories.ts 1:306-332 @ ./stories/ lazy ^\.\/.*$ namespace object ./Page.stories.ts ./Page.stories @ ./storybook-stories.js 1:551-585 1:901-935 @ ./storybook-config-entry.js 1:174-207 1:929-1061ERROR in ./node_modules/@storybook/test/dist/index.js 11:23752-23765Module not found: Error: Can't resolve 'os' in '/home/zemia/Coding/prime.code/pawmatix/node_modules/@storybook/test/dist'BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.This is no longer the case. Verify if you need this module and configure a polyfill for it.If you want to include a polyfill, you need to:    - add a fallback 'resolve.fallback: { "os": require.resolve("os-browserify/browser") }'    - install 'os-browserify'If you don't want to include a polyfill, you can use an empty module like this:    resolve.fallback: { "os": false } @ ./stories/Page.stories.ts 1:306-332 @ ./stories/ lazy ^\.\/.*$ namespace object ./Page.stories.ts ./Page.stories @ ./storybook-stories.js 1:551-585 1:901-935 @ ./storybook-config-entry.js 1:174-207 1:929-1061ERROR in ./node_modules/@storybook/test/dist/index.js 11:23770-23784Module not found: Error: Can't resolve 'tty' in '/home/zemia/Coding/prime.code/pawmatix/node_modules/@storybook/test/dist'BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.This is no longer the case. Verify if you need this module and configure a polyfill for it.If you want to include a polyfill, you need to:    - add a fallback 'resolve.fallback: { "tty": require.resolve("tty-browserify") }'    - install 'tty-browserify'If you don't want to include a polyfill, you can use an empty module like this:    resolve.fallback: { "tty": false } @ ./stories/Page.stories.ts 1:306-332 @ ./stories/ lazy ^\.\/.*$ namespace object ./Page.stories.ts ./Page.stories @ ./storybook-stories.js 1:551-585 1:901-935 @ ./storybook-config-entry.js 1:174-207 1:929-1061ERROR in ./stories/Configure.mdx 18:32Module parse failed: Unexpected token (18:32)You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders| import AddonLibrary from "./assets/addon-library.png";| > export const RightArrow = () => <svg |     viewBox="0 0 14 14" |     width="8px"  @ ./stories/ lazy ^\.\/.*$ namespace object ./Configure.mdx @ ./storybook-stories.js 1:551-585 1:901-935 @ ./storybook-config-entry.js 1:174-207 1:929-1061ERROR in ./stories/assets/avif-test-image.avif 1:0Module parse failed: Unexpected character '' (1:0)You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders(Source code omitted for this binary file) @ ./stories/ lazy ^\.\/.*$ namespace object ./assets/avif-test-image.avif @ ./storybook-stories.js 1:551-585 1:901-935 @ ./storybook-config-entry.js 1:174-207 1:929-1061preview compiled with 5 errors=> Failed to build the preview99% end closing watch compilationWARN Force closed preview buildSB_BUILDER-WEBPACK5_0003 (WebpackCompilationError): There were problems when compiling your code with Webpack.Run Storybook with --debug-webpack for more information.    at starter (./node_modules/@storybook/builder-webpack5/dist/index.js:1:8004)    at starter.next (<anonymous>)    at Module.start (./node_modules/@storybook/builder-webpack5/dist/index.js:1:9972)    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)WARN Broken build, fix the error above.WARN You may need to refresh the browser.

My package.json dependencies before the babel-loader's installation:

"dependencies": {"@expo/vector-icons": "^14.0.2","@react-native/babel-preset": "^0.76.1","@react-navigation/native": "^6.0.2","@storybook/addon-react-native-web": "^0.0.26","babel-plugin-react-native-web": "^0.19.13","expo": "~51.0.28","expo-constants": "~16.0.2","expo-dev-client": "~4.0.28","expo-font": "~12.0.9","expo-linking": "~6.3.1","expo-router": "~3.5.23","expo-splash-screen": "~0.27.5","expo-status-bar": "~1.12.1","expo-system-ui": "~3.0.7","expo-web-browser": "~13.0.3","react": "18.2.0","react-dom": "^18.2.0","react-native": "0.74.5","react-native-gesture-handler": "~2.16.1","react-native-reanimated": "~3.10.1","react-native-safe-area-context": "4.10.5","react-native-screens": "3.31.1","react-native-web": "^0.19.13","redux": "^5.0.1"  },"devDependencies": {"@babel/core": "^7.20.0","@chromatic-com/storybook": "^1.9.0","@storybook/addon-essentials": "^8.3.6","@storybook/addon-interactions": "^8.3.6","@storybook/addon-links": "^8.3.6","@storybook/addon-onboarding": "^8.3.6","@storybook/addon-webpack5-compiler-swc": "^1.0.5","@storybook/blocks": "^8.3.6","@storybook/react": "^8.3.6","@storybook/react-webpack5": "^8.3.6","@storybook/test": "^8.3.6","@tsconfig/react-native": "^3.0.5","@types/jest": "^29.5.14","@types/react": "~18.2.45","@types/react-test-renderer": "^18.3.0","jest": "^29.2.1","jest-expo": "~51.0.3","react-test-renderer": "18.2.0","storybook": "^8.3.6","typescript": "~5.3.3"  },

Node version: v20.3.1

no view found for id 0xe2a for fragment screenstackFragment {fd62d7} (a33df46d-25b8-49e6-823c-829b8ba9b189 id=0xe2a)

$
0
0

i am getting this error while navigating between tab. I am using react native navigation for navigating between tabs but not fix this please help me fix this issue working on it for past 2 days but not able to fix it is it related to react native bottom tabs

Android build failed - Expo react native

$
0
0

I have an application developed with expo 49 and typescript 5.1.3. And I need to build it to upload the app to Google Play.

In the vs code terminal I entered the commands 1), 2), 3) and 4).

1)npm install -g eas-cli

2)eas login

3)eas build:configure

√ Which platforms would you like to configure for EAS Build? » Android

√ Generated eas.json. Learn more: https://docs.expo.dev/build-reference/eas-json/

🎉 Your project is ready to build.

Run eas build when you are ready to create your first build.

Once the build is completed, run eas submit to upload the app to app stores.

Learn more about other capabilities of EAS Build: https://docs.expo.dev/build/introduction

4)eas build --platform android

Loaded "env" configuration for the "production" profile: no environment variables specified. Learn more: https://docs.expo.dev/build-reference/variables/

✔ Using remote Android credentials (Expo server)

✔ Using Keystore from configuration: Build Credentials wdhdOKG1L- (default)Compressing project files and uploading to EAS Build. Learn more: https://expo.fyi/eas-build-archive

✔ Uploaded to EAS 1s

Waiting for build to complete. You can press Ctrl+C to exit.

✖ Build failed🤖 Android build failed:Gradle build failed with unknown error.On the expo website I received the errors below.

In expo.dev I received the error below,

Running 'gradlew :app:bundleRelease' in /home/expo/workingdir/build/androidDownloading https://services.gradle.org/distributions/gradle-8.0.1-all.zip10%20%30%.40%50%.60%70%.80%90%100%Welcome to Gradle 8.0.1!Here are the highlights of this release: - Improvements to the Kotlin DSL- Fine-grained parallelism from the first build with configuration cache- Configurable Gradle user home cache cleanupFor more details see https://docs.gradle.org/8.0.1/release-notes.htmlTo honour the JVM settings for this build a single-use Daemon process will be forked. See https://docs.gradle.org/8.0.1/userguide/gradle_daemon.html#sec:disabling_the_daemon.Daemon will be stopped at the end of the build> Task :gradle-plugin:pluginDescriptors> Task :gradle-plugin:processResources> Task :gradle-plugin:compileKotlin> Task :gradle-plugin:compileJava NO-SOURCE> Task :gradle-plugin:classes> Task :gradle-plugin:jar> Task :gradle-plugin:inspectClassesForKotlinICFAILURE: Build completed with 2 failures.1:Task failed with an exception.-----------* Where:Build file '/home/expo/workingdir/build/node_modules/expo-error-recovery/android/build.gradle' line: 40* What went wrong:A problem occurred evaluating project ':expo-error-recovery'.>Could not set unknown property 'classifier' for task ':expo-error-recovery:androidSourcesJar' of type org.gradle.api.tasks.bundling.Jar.* Try:>Run with --stacktrace option to get the stack trace.> Run with--infoor --debug option to get more log output.>Run with--scan to get full insights.2: Task failed with an exception.-----------* What went wrong:A problem occurred configuring project ':expo'.> compileSdkVersion is not specified. Please add it to build.gradle* Try:> Run with --stacktrace option to get the stack trace.>Run with--info or --debug option to get more log output.> Run with--scanto get full insights.Error: Gradle build failed with unknown error. See logs for the "Run gradlew" phase for more information.

How to resolve?

Link repo

Consuming FormData in Express Typescript

$
0
0

I am attempting to send FormData from my React-Native application to my Express (everything in typescript) backend. However, nothing I have attempted has seemed to work.

Client Side:

const handlePickImage = async () => {    try {      const result = await ImagePicker.launchImageLibraryAsync({        mediaTypes: ImagePicker.MediaTypeOptions.Images,        allowsEditing: true,        aspect: [1, 1],        quality: 1,      });      if (!result.canceled) {        const uri = result.assets[0].uri;        setProfileImage(uri);        const formData = new FormData();        const fileName = uri.split('/').pop();        const response = await fetch(uri);        const blob = await response.blob();        formData.append('profileImage', blob, fileName || 'profile_image.jpg');        await updateProfilePicture(formData, user.uid);      }    } catch (error) {      console.error('Error picking image:', error);    }  };

Frontend API:

export const updateProfilePicture = async (profilePicture: FormData, userId: string) => {    try {      console.log("Profile picture: ", profilePicture)      const response = await fetch(`${API_URL}/user/updateprofilepicture/${userId}`, {        method: 'POST',        headers: {          Accept: 'application/json',        },        body: profilePicture,      });      if (!response.ok) {        throw new Error('Failed to update profile picture');      }      const data = await response.json();      console.log('Profile image uploaded successfully:', data);    } catch (error) {      console.error('Error updating profile picture:', error);    }  };

Backend Route:

const multer = require('multer');const express = require('express');const router = express.Router();const storage = multer.memoryStorage();const upload = multer({ storage: storage });router.post('/updateprofilepicture/:userId', upload.single('profileImage'), updateProfilePicture);

Controller:

import { Multer } from 'multer';export const updateProfilePicture = async (req: Request, res: Response) => {  try {    const userId = req.params.userId;    const file = req.file;    if (!file) {      return res.status(400).json({ error: 'No file uploaded' });    }    const imageUrl = await uploadProfileImage(userId, file);    res.status(200).json({ imageUrl });  } catch (error) {    console.error('Error in updateProfilePicture controller:', error);    res.status(500).json({ error: 'Error uploading profile picture' });  }};

Am I missing something simple? I am able to log the data in the frontend api without issue, but I am unable to send the data to the controller and pick up anything.

Thank you


React Native Navigation Typescript Typing

$
0
0

I am having issues typing my props in a component that expects parameters. I have read through the documentation and have followed the guides. This is my component that receives parameters.

type PoiDetailsProps = NativeStackScreenProps<RootStackParamList, 'PoiDetails'>;const PoiDetails = (props: PoiDetailsProps) => {

My RootStackParamList:

export type RootStackParamList = {  Home: undefined;  Profile: undefined;  Drawer: undefined;  Login: undefined;  PoiDetails: {    placeDetails: PlaceDetail | null;  };};

My RootNavigation

const Stack = createStackNavigator();const RootNavigator = () => {  const {user} = useAppContext();  return (<NavigationContainer>      {user ? (<Stack.Navigator initialRouteName="Tabs"><Stack.Screen            name="Tabs"            component={TabNavigator}            options={{headerShown: false}}          /><Stack.Screen name="Home" component={Home} /><Stack.Screen name="PoiDetails" component={PoiDetails}/></Stack.Navigator>      ) : (<AuthStack />      )}</NavigationContainer>  );};

"@react-navigation/bottom-tabs": "^6.5.20","@react-navigation/drawer": "^6.7.2","@react-navigation/native": "^6.1.18","@react-navigation/native-stack": "^6.10.1","@react-navigation/stack": "^6.4.1",

I have tried different ways of typing route and navigation including

type PoiNavigationProps = NativeStackNavigationProp<  RootStackParamList,'PoiDetails'>;type PoiRouteProps = RouteProp<RootStackParamList, 'PoiDetails'>;type PoiDetailsProps = {  navigation: PoiNavigationProps;  route: PoiRouteProps;};

the error I get is

Type '{ route: RouteProp<ParamListBase, "PoiDetails">; navigation: any; }' is not assignable to type 'PoiDetailsProps'.Types of property 'route' are incompatible.Type 'RouteProp<ParamListBase, "PoiDetails">' is not assignable to type 'RootRouteProps<"PoiDetails">'.Type 'RouteProp<ParamListBase, "PoiDetails">' is not assignable to type 'Readonly<{ params: Readonly<{ placeDetails: PlaceDetail; }>; }>'.Types of property 'params' are incompatible.Type 'Readonly<object | undefined>' is not assignable to type 'Readonly<{ placeDetails: PlaceDetail; }>'.Type 'undefined' is not assignable to type 'Readonly<{ placeDetails: PlaceDetail; }>'.

I have also tried to give an initial value to placeDetails in the RootNavigator, I have tried to define the params in RootStackParamList as type PlaceDetail | undefined.I have also tried these solutionsReact native with typescript - how to use the useRoute from @react-navigation/native with typescript

Webpack not generating chunks for React.lazy imports with babel & react native web

$
0
0

I'm working on a React native web project (with custom Webpack config) and using React.lazy with dynamic imports for code splitting. However, Webpack isn't generating separate chunks for these imports as expected. I've tried several solutions, but none have resolved the issue.

Code:

const Apple = React.lazy(() => import(/* webpackChunkName: "Apple" */ './Apple'));

Webpack config:

const path = require('path');const HtmlWebpackPlugin = require('html-webpack-plugin');const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');const config = (_, argv) => ({    target: 'web',    entry: {        main: path.resolve(__dirname, 'src', 'main'),    },    output: {        path: path.resolve(__dirname, 'dist'),        filename: '[name].[contenthash].js',        chunkFilename: '[name].[contenthash].js',    },    resolve: {        extensions: ['.js', '.jsx', '.ts', '.tsx'],        alias: {'react-native$': 'react-native-web',        },    },    module: {        rules: [            {                test: /\.(tsx|jsx|ts|js)?$/,                use: {                    loader: 'babel-loader',                    options: {                        cacheDirectory: true,                        presets: [                            ['@babel/preset-env', { modules: false }], // Transpile modern JS'@babel/preset-react', // JSX support'module:metro-react-native-babel-preset',                        ],                        plugins: ['react-native-web','@babel/plugin-syntax-dynamic-import','@babel/plugin-proposal-export-namespace-from','react-native-reanimated/plugin','@babel/plugin-transform-flow-strip-types',                            ['@babel/plugin-transform-private-methods',                                { loose: true },                            ],                        ],                    },                },            },        ],    },    plugins: [        new HtmlWebpackPlugin({            template: path.resolve(__dirname, 'public/index.html'),        }),        process.env.ANALYZE && new BundleAnalyzerPlugin(),    ].filter(Boolean),    optimization: {        splitChunks: {            chunks: 'all',            cacheGroups: {                vendor: {                    test: /[\\/]node_modules[\\/]/,                    name: 'vendors',                    chunks: 'all',                },            },        },    },});module.exports = config;

My final chunks are:

Assets:   main.d4d5a64793bc974cb170.js (2.05 MiB)  vendors.e9f736b3a31c46ef0971.js (9.08 MiB)Entrypoints:  main (11.1 MiB)      vendors.e9f736b3a31c46ef0971.js      main.d4d5a64793bc974cb170.js

React-Native-Firebase - No Firebase App '[DEFAULT]' has been created - call firebase.initializeApp() (on Android, despite correct setup)

$
0
0

React Native 0.71.3 + Typescript. Project uses ProGuard too, apparently.

I'm trying to integrade push notifications through cloud messaging. I've only tested it on Android.

I did all the steps required. What am I missing?

  1. npm install --save @react-native-firebase/app
  2. placed my google-services.json file in /android/app/ folder
  3. Added classpath 'com.google.gms:google-services:4.4.0' at /android/build.gradle
  4. Added apply plugin: 'com.google.gms.google-services'at /android/app/build.gradle
  5. npm install @react-native-firebase/messaging
  6. npx react-native run-android to rebuild the project7)import messaging from '@react-native-firebase/messaging';in my App.tsx
  7. For testing, I'm adding this function from the documentation
async function requestUserPermission() {  const authStatus = await messaging().requestPermission();  const enabled =    authStatus === messaging.AuthorizationStatus.AUTHORIZED ||    authStatus === messaging.AuthorizationStatus.PROVISIONAL;  if (enabled) {    console.log('Authorization status:', authStatus);  }}
  1. I call the function inside an useEffect() in App.tsx

Yet, despite all these steps, I keep getting

 Possible Unhandled Promise Rejection (id: 0):Error: No Firebase App '[DEFAULT]' has been created - call firebase.initializeApp()

What am I missing? This is driving me insane.

typescript Cannot add headers to a fetch api using react-native

$
0
0

I am using Fetch API from react-native and I am using typescript. My code looks like this:

let responseLogin = await fetch('http://url_example', {        method: 'POST',        headers: {'Content-Type':'application/json'},        body: requestBody    });

But I get the following error where the header is:

 Argument of type '{ method: string; headers: { 'Content-Type': string; }; body: string; }' is not assignable to parameter of type 'RequestInit'.  Types of property 'headers' are incompatible.    Type '{ 'Content-Type': string; }' is not assignable to type 'Headers | string[][]'.      Object literal may only specify known properties, and ''Content-Type'' does not exist in type 'Headers | string[][]'.

I have also tried to create a custom header but without any luck:

    let requestHeaders = new Headers();        requestHeaders.set('Content-Type', 'application/json');        // I have also tried adding this at the end but no luck         // requestHeaders.get('Content-Type');

How could I add a header to this? Because I cannot find any way to make this happen and I don't know what is the problem. If I test these in postman, I get a 200 response, here I get a 401 response.I have also tried this library just to add custom headers: https://www.npmjs.com/package/fetch-headers

I use:Visual studio code 1.81.1"react-native": "0.50.0","typescript": "2.6.1"

React Native blob/file is not getting to the server

$
0
0

I am extremely stuck here. I don't know what I'm doing wrong. I'm trying to send a file from the an expo-image-picker component to the server. The form is sent, but the image is not. The fetch command immediately gives the "Network request failed" error. The server DOES receive the request, but no image is attached.

More information:

  • I am creating the form-data object and append the blob to it. I've also tried doing it with FormData.append("image", {uri, name: 'filename', type: 'image/filetype'}), the way most articles suggest, ignoring the TS error but it fails as well.

  • I'm not submitting to AWS or Firebase, so I'm not using those libraries, I don't see what they are doing any different from me in any case.

  • I haven't set any specific permissions for this. I did see some articles talking about permissions for uploading, but they were over 5 years old and talking about before android 5.0.

Here are the functions I'm using to do the submit. pathToImage is returned from the ImagePicker.

const fetchImageFromUri = async (uri: string) => {  try {    const response = await fetch(uri);    const blob = await response.blob();    return blob;  } catch (error) {    console.log("fetchImageFromUri error:", error);    throw new Error("fetchImageFromUri");  }};const upload = async () => {  setMessage("");  setErrMessage("");  if (pathToImage != null) {    const fileToUpload = await fetchImageFromUri(pathToImage);    const formData = new FormData();    formData.append("action", "Image Upload");    formData.append("image", fileToUpload, "filename");    // from: https://stackoverflow.com/questions/71198201/react-native-unable-to-upload-file-to-server-network-request-failed    // most articles say this is the way to upload the file... Typescript gives an error    // because it only wants type 'string | Blob'    // let uriParts = pathToImage.split(".");    // let fileType = uriParts[uriParts.length - 1];    // formData.append("image", {    //   uri: pathToImage,    //   name: `photo.${fileType}`,    //   type: `image/${fileType}`,    // });    // create the header options    const options: RequestInit = {      method: "POST",      body: formData,      headers: {"Content-Type": "multipart/form-data",        Accept: "image/jpeg, image/png",      },    };    try {      const res = await fetch(URL, options);      console.log("fetch returned"); // this line is never reached      if (!res.ok) {        throw new Error("Something went wrong");      }      const body = (await res.json()) as any;      if (body.code > 200) {        setErrMessage(body.msg);      } else {        setMessage(body.msg);      }    } catch (err: any) {      setErrMessage("There was an error in upload");      console.log("upload catch error:", err.message);    }  }};

The full code can be found in my GitHub repository.

Project in react native + expo with issue when i execute the same psn-api auth request at second time

$
0
0

I'm having problems when trying to execute the request that gets the code value that is used to obtain the token to connect to the PlayStation API. I've tried everything and I can't understand what it could be. Where I tested the correct value of the SSO token, it reaches the function, but when it tries to communicate with Sony's servers to get the value, it only works on the first attempt in the app on my cell phone. From the second attempt onwards, the request stops working and returns the status code 200, the expected code was 302. I can only make the request return 302 again by clearing the app cache in my cell phone's settings. So I imagine that somehow React Native or Expo may be storing some value or section that is interfering with the requests.

async exchangeNpssoForCode(npssoToken: string): Promise {console.log("Token NPSSO atual:", npssoToken);

    const queryString = new URLSearchParams({        access_type: "offline",        client_id: "09515159-7237-4370-9b40-3806e67c0891",        redirect_uri: "com.scee.psxandroid.scecompcall://redirect",        response_type: "code",        scope: "psn:mobile.v2.core psn:clientapp"    }).toString();    const requestUrl = `https://ca.account.sony.com/api/authz/v3/oauth/authorize?${queryString}`;    const response = await fetch(requestUrl, {        headers: {'Cookie': `npsso=${npssoToken}`        },        redirect: "manual"      });    const responseHeaders = response.headers;    if (!responseHeaders.has("location") || !responseHeaders.get("location")?.includes("?code=")) {        throw new Error(`            There was a problem retrieving your PSN access code. Is your NPSSO code valid?            To get a new NPSSO code, visit https://ca.account.sony.com/api/v1/ssocookie.        `);    }    // Captura o código de autorização da URL de redirecionamento    const redirectLocation = responseHeaders.get("location") as string;    const redirectParams = new URLSearchParams(redirectLocation.split("redirect/")[1]);    return redirectParams.get("code") as string;}

I've also tested the exact same code being executed on my PC in isolation in TypeScript and JavaScript, and both methods worked using Axio and Fetch. However, the problem only occurs in my React Native app. I haven't found anything about it on the internet and I can't find another solution.

I tried to debug the code, I already know that the SSO value is correct and valid, if the SSO is invalid then obviously it will return an error and status code 200, but something happens only from the second request onwards that even receiving the correct value, the request always returns 200 and not 302I have also tried to leave a fixed value for the valid token and the result is the same, on the first attempt it works, on the second attempt the error starts

expo: getDefaultEnhancers is not a function (it is Object), js engine: hermes

$
0
0

I'm trying to use redux-devtools-expo-dev-plugin
but I can't get it to work properly

import devToolsEnhancer from 'redux-devtools-expo-dev-plugin';  export const store = configureStore({reducer: rootPersistReducers,middleware: getDefaultMiddleware => getDefaultMiddleware({    serializableCheck: false,}).concat(firestoreApi.middleware).concat(categoryApiSlice.middleware).concat(thunk).concat(logger),//// here is the problem with the getDefaultEnhancersdevTools: false,enhancers: getDefaultEnhancers => getDefaultEnhancers().concat(devToolsEnhancer()),

})A SCREENSHOT OF THE ERROR

a capture of the typeScript warning


Formatting chat message array to add protocol and date

$
0
0

I'm attempting to add two objects according to the message state.

When is the first message from that day, it will add an object before that message that contains the date. The protocol will be added if the next message has a different protocol, or if the chat has ended.

  1. Protocol object will always be added before the change of the protocol
  2. Dates will be added before the first message from that day

This is the object:

const chatMessages = [    {"protocol": "9313478261","msgDate": "2024-01-30T12:58:32"  },  {"protocol": "9313478261","msgDate": "2024-01-30T12:58:44"  },  {"protocol": "9313478261","msgDate": "2024-01-30T13:18:25"  },  {"protocol": "9313478261","msgDate": "2024-01-30T13:18:35"  },  {"protocol": "9313478261","msgDate": "2024-01-31T10: 50: 26"  },  {"protocol": "9313478261","msgDate": "2024-02-02T11: 18: 21"  },  {"protocol": "39128391231","msgDate": "2024-02-03T12:58:32"  },  {"protocol": "39128391231","msgDate": "2024-02-03T13:58:32"  },  {"protocol": "87382713812","msgDate": "2024-02-03T14:58:32"  },  {"protocol": "87382713812","msgDate": "2024-02-03T15:58:32"  },]

This is what I'm attempting to achieve:

const chatMessages = [    {"type": "info","date": "2024-01-30"  },  {"protocol": "9313478261","msgDate": "2024-01-30T12:58:44"  },  {"protocol": "9313478261","msgDate": "2024-01-30T13:18:25"  },  {"protocol": "9313478261","msgDate": "2024-01-30T13:18:35"  },  {"type": "info","date": "2024-01-31"  },  {"protocol": "9313478261","msgDate": "2024-01-31T10: 50: 26"  },  {"type": "info","date": "2024-02-02"  },  {"protocol": "9313478261","msgDate": "2024-02-02T11: 18: 21"  },  {"type": "info","date": "Protocol is 9313478261"  },  {"type": "info","date": "2024-02-03"  },  {"protocol": "39128391231","msgDate": "2024-02-03T12:58:32"  },  {"protocol": "39128391231","msgDate": "2024-02-03T13:58:32"  },  {"type": "info","date": "Protocol is 39128391231"  },  {"protocol": "87382713812","msgDate": "2024-02-03T14:58:32"  },  {"type": "info","date": "2024-02-04"  },  {"protocol": "87382713812","msgDate": "2024-02-04T15:58:32"  },]

The code I have:

let lastProtocol = null;let currentDate = null;const modifiedMessages = chatMessages.reduce((acc, message) => {  const messageDate = new Date(message.msgDate);  const messageProtocol = message.protocol;  if (!currentDate || messageDate.toDateString() !== currentDate.toDateString()) {    acc.push({ info: true });    currentDate = messageDate;  }  if (lastProtocol !== messageProtocol) {    acc.push({ protocol: lastProtocol });    lastProtocol = messageProtocol;  }  acc.push(message);  return acc;}, []);modifiedMessages.push({ protocol: lastProtocol });console.log(modifiedMessages);

But the protocol and dates are not correctly added:

[{"info": true}, {"protocol": null}, {"protocol": "9313478261","msgDate": "2024-01-30T12:58:32"}, {"protocol": "9313478261","msgDate": "2024-01-30T12:58:44"}, {"protocol": "9313478261","msgDate": "2024-01-30T13:18:25"}, {"protocol": "9313478261","msgDate": "2024-01-30T13:18:35"}, {"info": true}, {"protocol": "9313478261","msgDate": "2024-01-31T10: 50: 26"}, {"protocol": "9313478261","msgDate": "2024-02-02T11: 18: 21"}, {"info": true}, {"protocol": "9313478261"}, {"protocol": "39128391231","msgDate": "2024-02-03T12:58:32"}, {"protocol": "39128391231","msgDate": "2024-02-03T13:58:32"}, {"protocol": "39128391231"}, {"protocol": "87382713812","msgDate": "2024-02-03T14:58:32"}, {"protocol": "87382713812","msgDate": "2024-02-03T15:58:32"}, {"protocol": "87382713812"}] 

Wordle clone with microphone integration: Why is addWord() not working while addKey() functions as expected?

$
0
0

I have a Wordle clone made with Expo React-Native with TypeScript. I followed a tutorial on YouTube Building a Wordle Game with React Native - Simon Grimm which explained the basic logic of the game, adding the rows and columns, managing the keyboard keys pressing, etc. All this worked great, but my professor wanted me to add a microphone input so that the words could be dictated, instead of typed.

After some tinkering I managed to make the mic work perfectly; it registers a word as a string only if it is a 5 letter word (since the game only works with these types of words anyway) and the word gets passed through as a prop from the OnScreenKeyboard component to the main Game component, where it can then be processed or added to the game's current state.

Here's game.tsx, which handles the game logic, as I said, the words received by the mic in the OnScreenKeyboard.tsx enters the addWord() function, but somehow, it fails to respect the rows that it should insert the words into, it just inserts the word received by mic at the first row every time.

game.tsx

const [rows, setRows] = useState<string[][]>(new Array(ROWS).fill(new Array(5).fill('')));const [curRow, setCurRow] = useState(0);const [curCol, _setCurCol] = useState(0);const [blueLetters, setBlueLetters] = useState<string[]>([]);const [yellowLetters, setYellowLetters] = useState<string[]>([]);const [grayLetters, setGrayLetters] = useState<string[]>([]);// Random word gets generatedconst [word, setWord] = useState<string>(words[Math.floor(Math.random() * words.length)]);const wordLetters = word.split('');const colStateRef = useRef(curCol);const setCurCol = (col: number) => {  colStateRef.current = col;  _setCurCol(col);};// Checks the word, does the flip animation, paints the tiles and keyboardconst checkWord = () => {  const currentWord = rows[curRow].join('');  if (currentWord.length < word.length) {    shakeRow();    return;  }  if (!allWords.includes(currentWord)) {    shakeRow();    return;  }  flipRow();  const newBlue: string[] = [];  const newYellow: string[] = [];  const newGray: string[] = [];  currentWord.split('').forEach((letter, index) => {    if (letter === wordLetters[index]) {      newBlue.push(letter);    } else if (wordLetters.includes(letter)) {      newYellow.push(letter);    } else {      newGray.push(letter);    }  });  setBlueLetters([...blueLetters, ...newBlue]);  setYellowLetters([...yellowLetters, ...newYellow]);  setGrayLetters([...grayLetters, ...newGray]);  setTimeout(() => {    if (currentWord === word) {      router.push(`/end?win=true&word=${word}&gameField=${JSON.stringify(rows)}`);    } else if (curRow + 1 >= rows.length) {      router.push(`/end?win=false&word=${word}&gameField=${JSON.stringify(rows)}`);    }  }, 1500);  setCurRow(curRow + 1);  setCurCol(0);};// Evaluates each keyboard key pulsationconst addKey = (key: string) => {  console.log('addKey', key);  const newRows = [...rows.map((row) => [...row])];  if (key === 'ENTER') {    checkWord();  } else if (key === 'BACKSPACE') {    if (colStateRef.current === 0) {      newRows[curRow][0] = '';      setRows(newRows);      return;    }    newRows[curRow][colStateRef.current - 1] = '';    setCurCol(colStateRef.current - 1);    setRows(newRows);    return;  } else if (colStateRef.current >= newRows[curRow].length) {    // End of line    return;  } else {    newRows[curRow][colStateRef.current] = key;    setRows(newRows);    setCurCol(colStateRef.current + 1);  }};// Receives the word by mic (Here's the problem)const addWord = (word: string) => {  const letters = word.split('');  const newRows = [...rows.map((row) => [...row])];  letters.forEach((letter, index) => {    if (index < 5) {      newRows[curRow][index] = letter;    }  });  setRows(newRows);  setCurCol(Math.min(letters.length, 5));  setTimeout(()=>checkWord(),1000);};/* More code, not related to the game logic */return (<View style={styles.container}>            {keys.map((row, rowIndex) => (<View key={`row-${rowIndex}`} style={styles.row}>                    {row.map((key, keyIndex) => (<Pressable                            key={`key=${key}`}                            onPress={() => (key === MICROPHONE ? handleMicrophonePress() : onKeyPressed(key))}                            style={({pressed}) => [                                styles.key,                                {                                    width: keyWidth,                                    height: keyHeight,                                    backgroundColor: '#DDD',                                },                                isSpecialKey(key) && {width: keyWidth * 1.5},                                {                                    backgroundColor: blueLetters.includes(key)                                        ? '#6ABDED'                                        : yellowLetters.includes(key)                                          ? '#FFE44D'                                          : grayLetters.includes(key)                                            ? '#808080'                                            : key === MICROPHONE && isRecording                                              ? '#FF4444'                                              : '#DDD',                                },                                pressed && {backgroundColor: '#868686'},                            ]}><Text style={[styles.keyText, key === 'ENTER'&& {fontSize: 12}, isInLetters(key) && {color: '#FFFFFF'}]}>                                {isSpecialKey(key) ? (                                    key === ENTER ? ('Enter'                                    ) : (<Ionicons name="backspace-outline" size={24} color={'black'} />                                    )                                ) : key === MICROPHONE ? (<Ionicons name="mic-outline" size={24} color={isRecording ? 'white' : 'black'} />                                ) : (                                    key                                )}</Text></Pressable>                    ))}</View>            ))}</View>    );};export default game.tsx;/* Styling code */

OnScreenKeyboard.tsx

import {Platform, Pressable, StyleSheet, Text, useWindowDimensions, View} from 'react-native';import React, {useState} from 'react';import {Ionicons} from '@expo/vector-icons';import Voice from '@react-native-voice/voice';type OnScreenKeyboardProps = {    onKeyPressed: (key: string) => void;    onWordRecognized: (word: string) => void;    blueLetters: string[];    yellowLetters: string[];    grayLetters: string[];};export const ENTER = 'ENTER';export const BACKSPACE = 'BACKSPACE';export const MICROPHONE = 'MICROPHONE';const keys = [    ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],    ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', MICROPHONE],    [ENTER, 'z', 'x', 'c', 'v', 'b', 'n', 'm', BACKSPACE],];const OnScreenKeyboard = ({onKeyPressed, onWordRecognized, blueLetters, yellowLetters, grayLetters}: OnScreenKeyboardProps) => {    const {width} = useWindowDimensions();    const keyWidth = Platform.OS === 'web' ? 58 : (width - 60) / keys[0].length;    const keyHeight = 55;    const [isRecording, setIsRecording] = useState(false);    const isSpecialKey = (key: string) => [ENTER, BACKSPACE].includes(key);    const isInLetters = (key: string) => [...blueLetters, ...yellowLetters, ...grayLetters].includes(key);    React.useEffect(() => {        Voice.onSpeechResults = onSpeechResults;        return () => {            Voice.destroy().then(Voice.removeAllListeners);        };    }, []);    const removeAccents = (word: string) => {        return word.replace(/á/g, 'a').replace(/é/g, 'e').replace(/í/g, 'i').replace(/ó/g, 'o').replace(/ú/g, 'u');    };    const onSpeechResults = (e: any) => {        if (e.value && e.value[0]) {            let word = e.value[0].toLowerCase().trim();            word = removeAccents(word);            if (word.length === 5) {                onWordRecognized(word);            }        }        setIsRecording(false);    };    const handleMicrophonePress = async () => {        try {            if (isRecording) {                await Voice.stop();                setIsRecording(false);            } else {                setIsRecording(true);                await Voice.start('es-ES');            }        } catch (error) {            console.error(error);            setIsRecording(false);        }    };    return (<View style={styles.container}>            {keys.map((row, rowIndex) => (<View key={`row-${rowIndex}`} style={styles.row}>                    {row.map((key, keyIndex) => (<Pressable                            key={`key=${key}`}                            onPress={() => (key === MICROPHONE ? handleMicrophonePress() : onKeyPressed(key))}                            style={({pressed}) => [                                styles.key,                                {                                    width: keyWidth,                                    height: keyHeight,                                    backgroundColor: '#DDD',                                },                                isSpecialKey(key) && {width: keyWidth * 1.5},                                {                                    backgroundColor: blueLetters.includes(key)                                        ? '#6ABDED'                                        : yellowLetters.includes(key)                                          ? '#FFE44D'                                          : grayLetters.includes(key)                                            ? '#808080'                                            : key === MICROPHONE && isRecording                                              ? '#FF4444'                                              : '#DDD',                                },                                pressed && {backgroundColor: '#868686'},                            ]}><Text style={[styles.keyText, key === 'ENTER'&& {fontSize: 12}, isInLetters(key) && {color: '#FFFFFF'}]}>                                {isSpecialKey(key) ? (                                    key === ENTER ? ('Enter'                                    ) : (<Ionicons name="backspace-outline" size={24} color={'black'} />                                    )                                ) : key === MICROPHONE ? (<Ionicons name="mic-outline" size={24} color={isRecording ? 'white' : 'black'} />                                ) : (                                    key                                )}</Text></Pressable>                    ))}</View>            ))}</View>    );};export default OnScreenKeyboard;/* Styling code */

Here's a demo of the bug playing the game (it's in Spanish BTW), the expected behaviour would be that the second word gets inserted at the second row, obviously.

I tried a bit of everything to fix this. I also tried to insert the words picked up by the mic into the addKey() function, letter by letter with .split() but this doesn’t work either, which doesn’t make sense to me.

I think the main problem has to do with how the game handles the curRow state, but again, I tried to fix it and just couldn't do it.

Best way to get random photo from camera roll without loading all of them?

$
0
0

I've been trying a long time to get this working, but no luck. I just want to pull 1 random image from my phone/emulator's camera roll. The issue is that RN and all packages that I've found have to load every image in order, and then randomly choose. This takes forever if you have a large camera roll. Is there a way to just get a list of photo ids and choose from that?

pass a function to a custom react hook

$
0
0

Previously, I was using a graphql query like this. The data is returned from the query and I use setShowFlatListwith the datareturned:

  const [loadUsers, { data }] = useUsersLazyQuery({    onCompleted: () => {      setShowFlatList(data);    },});

Now I am creating a custom react hook where I use this graphql query. It looks like this:

export const useLoadUsers = (onCompleted: any) => {  const [usersQuery, { data }] = useUsersLazyQuery({    onCompleted: () => {      if(onCompleted){        onCompleted();      }    },    onError: onLoadUserError,    fetchPolicy: 'network-only',  });const loadUsers = async (phoneNumber: number,) => {  const data = await usersQuery({    variables: {      where: {        OR: [          { phoneNumber: newPhoneNumber }        ],      },    },    });  return data;};return loadUsers;};

But I cannot figure out how to pass the setShowFlatList function to the onCompleted of the hook, such that I can still use dataas its parameter from within the hook.

REACT NATIVE padding issue white spaces on top and bottom of the IOS screen (notch area) even if the SafeAreaView is not given

$
0
0

I am new to ReactNative development and I couldn't figure out the white spaces that occur on top and bottom of the screen even without the SafeAreaView component. I am testing this on ExpoGo app on my mobile.

import React from "react";import { Text, View, StyleSheet } from "react-native";import Footer from "../components/Footer";import LittleLemonHeader from "../components/LittleLemon";import WelcomeScreen from "../components/WelcomeScreen";import MenuItems from "../components/MenuItems";export default function Index() {  return (    //<View style={{ flex: 1, backgroundColor: "#495E57" }}><View style={styles.container}><LittleLemonHeader /><MenuItems /><Footer /></View>  );}const styles = StyleSheet.create({  container: {    flex: 1,    backgroundColor: "#495E57",    padding: 0, // Ensure no padding in the container    margin: 0,  },});

React Component - Calling a function when a touch occurs but ignoring swipes

$
0
0

I've written a react component that is supposed to distinguish between touches and swipes. The idea is:

  1. If the user swipes on the component, its image changes. This is working fine.
  2. If the user touches the component, it shrinks whilst being touched and expands when released. This is also working fine.
  3. It must only call "onTouch" at the end of a touch gesture, but NEVER when a swipe takes place. This is because I only want the user go the new page when they have pressed the element, not swiped across it. Think of Airbnb and when the user is swiping along an Explore card vs selecting it. This is the behaviour I want.

I can't get Requirement 3 to work, because it is always calling onTouch when a swipe occurs. This occurs when holding, and then swiping whilst being held, or if a quick swipe takes place. But neither of these should trigger onTouch. Please advise how I can fix this? I've tried asking ChatGPT but it fails every time!

import React, { useRef, useState } from "react";import {  Animated,  View,  TouchableWithoutFeedback,  StyleSheet,} from "react-native";interface Props {  children: React.ReactNode;  duration?: number; // Optional prop to control the duration of the animation  onTouch: () => void;}function ShrinkOnTouch({ children, duration, onTouch }: Props) {  if (duration === undefined) duration = 125;  const scale = useRef(new Animated.Value(1)).current; // Initial scale value (1)  const [isTouched, setIsTouched] = useState(false);  // Function to handle the shrink action  const handleTouchStart = () => {    setIsTouched(true);    Animated.timing(scale, {      toValue: 0.95, // Shrink to 90% of the original size      duration: duration, // Set the duration of the shrinking animation      useNativeDriver: true,    }).start();  };  // Function to handle the release action (expand back to original size)  const handleTouchEnd = () => {    if (isTouched) {      setIsTouched(false);      Animated.timing(scale, {        toValue: 1, // Expand back to original size        duration: duration, // Set the duration of the expanding animation        useNativeDriver: true,      }).start();      onTouch();    }  };  // Function to handle swipe detection (expand back to original size if swipe detected)  const handleTouchMove = (e: any) => {    if (      isTouched &&      (Math.abs(e.nativeEvent.pageX) > 5 || Math.abs(e.nativeEvent.pageY) > 5)    ) {      // If swipe detected, expand the component back to original size      setIsTouched(false);      Animated.timing(scale, {        toValue: 1,        duration: duration, // Same duration for the swipe expansion        useNativeDriver: true,      }).start();    }  };  return (<View style={styles.container}><TouchableWithoutFeedback        onPressIn={handleTouchStart}        onPressOut={handleTouchEnd}        onPress={handleTouchEnd}><Animated.View          style={[styles.animatedChild, { transform: [{ scale }] }]}          onStartShouldSetResponder={() => true}          onResponderMove={handleTouchMove}>          {children}</Animated.View></TouchableWithoutFeedback></View>  );}const styles = StyleSheet.create({  container: {    flex: 1,  },  animatedChild: {    justifyContent: "center",    alignItems: "center",  },});export default ShrinkOnTouch;

ReactNative map view, markers won't display without moving the map after load

$
0
0

I have a Functional Component in my React Native app that when loaded, I want to check for user location permissions, and then load a map based on the users location. When that location is loaded I make a Google Maps API call to search for nearby stores and display markers in the MapView.

The issue is, the API call is being made when the map renders, but no markers are being displayed until the map is moved slightly. From my console.logs the API call is being made when the view first loads and I do see results come back and get set to the state object.

My code is below, I'm relatively new to React-Native, any input would be welcome!

import React, { useState, useEffect, useCallback } from 'react';import { View, Platform, StyleSheet } from 'react-native';import Geolocation from 'react-native-geolocation-service';import MapView, { Marker, Region } from 'react-native-maps';import { request, PERMISSIONS } from 'react-native-permissions';import debounce from 'lodash.debounce';interface StoreFinderState {  latitude: number;  longitude: number;  latitudeDelta: number;  longitudeDelta: number;}const StoreFinderView: React.FC = () => {  const [mapRegion, setMapRegion] = useState<StoreFinderState>({    // Start with default location    latitude: 41.88015,    longitude: -87.63515,    latitudeDelta: 0.05,    longitudeDelta: 0.05,  });  const [stores, setStores] = useState<any[]>([]);  const getLocation = useCallback(() => {    Geolocation.getCurrentPosition(      position => {        console.log(position);        setMapRegion({          latitude: position.coords.latitude,          longitude: position.coords.longitude,          latitudeDelta: 0.05,          longitudeDelta: 0.05,        });      },      error => {        console.log(error.code, error.message);      },      { enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 }    );  }, []);  useEffect(() => {    const requestLocationPermission = async () => {      const status = await request(        Platform.OS === 'ios' ? PERMISSIONS.IOS.LOCATION_WHEN_IN_USE : PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION      );      if (status === 'granted') {        getLocation();      }    };    requestLocationPermission();  }, []);  useEffect(() => {    searchStores(mapRegion);  }, [mapRegion]);  const searchStores = useCallback(    debounce(async (region: Region) => {      try {        console.log('Search for stores');        const response = await fetch('https://maps.googleapis.com/maps/api/place/nearbysearch/json?'+            `keyword=KEYWORD_SEARCH&location=${region.latitude}%2C${region.longitude}&radius=1500` +'&type=store&key=API_KEY',          {            method: 'GET',          }        );        const data = await response.json();        console.log(data.results.length);        setStores(data.results);      } catch (error) {        console.log(error);      }    }, 500),    []  );  return (<View style={styles.container}><MapView        style={styles.container}        region={mapRegion}        onRegionChangeComplete={(region) => {setMapRegion(region)}}>        {stores.map((store: any) => (<Marker            key={store.place_id}            coordinate={{              latitude: store.geometry.location.lat,              longitude: store.geometry.location.lng,            }}            title={store.name}            description={store.vicinity}          />        ))}</MapView></View>  );};const styles = StyleSheet.create({  container: {    flex: 1,  },});export default StoreFinderView;

TouchableOpacity in headerRight not working as expected in Expo Router (onPress only fires once or not at all)

$
0
0

Description

I'm experiencing a problem with headerRight configuration in expo-router where TouchableOpacity buttons aren't responding as expected. Specifically, the onPress event fires only once or sometimes does not trigger at all. I’ve tried multiple solutions and verified many possible causes, but I can't seem to resolve the issue. Any help would be appreciated.

Details

Expo SDK Version: 52.0.4expo-router Version: 4.0.2React Navigation Version: 6.0.2Platform: iOS/Android (tested on physical devices)Development Environment: Expo GoLanguage: TypeScript

Problem Description

I am trying to use a TouchableOpacity component in headerRight of a Stack.Screen in expo-router. My expectation is that the button should be clickable multiple times, with each click triggering the onPress event. However, what I observe is:

The onPress event fires only on the first click or sometimes does not fire at all.The button itself is visible in the header, but does not respond as expected to user interaction.

What I’ve Tried

  1. Basic Configuration in RootLayout:Defined headerRight directly within Stack.Screen in RootLayout.js as follows:
<Stack.Screen  name="notes/index"  options={{    title: "Dodo",    headerRight: () => (<TouchableOpacity onPress={() => console.log("Button pressed!")}><Text>Click Me</Text></TouchableOpacity>    ),  }}/>

This works for the first click, but the button becomes unresponsive afterwards.

  1. Using useLayoutEffect to Update Header:Added useLayoutEffect in my screen (notes/index.tsx) to update headerRight dynamically:
useLayoutEffect(() => {  navigation.setOptions({    headerRight: () => (<TouchableOpacity onPress={() => console.log("Pressed in useLayoutEffect!")}><Text>Click Me Again</Text></TouchableOpacity>    ),  });}, [navigation]);

Still, the button works initially but doesn't respond on subsequent clicks.

Expected Behavior

The button in headerRight should be clickable multiple times, with each click triggering the onPress handler.

Observed Behavior

TouchableOpacity in headerRight only responds on the first click or not at all after initial use.The button is visually present in the header, but is not consistently registering interactions.

Steps to Reproduce

Create a stack navigator using expo-router.Add a headerRight button using TouchableOpacity.Run the app and navigate to the screen.Try clicking the button — notice that onPress either fires only once or stops responding.

Questions

Has anyone else experienced this issue with headerRight? Are there known limitations when using expo-router for header buttons?Is there an alternative approach for adding interactive elements to the header that works reliably?



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