so I am just digging into building apps with React Native/Expo and I have come across a weird behavior that I am not sure why is happening. I have a dashboard component that returns a 'UsersFlatList' component
const DashBoard = () => {
return (
<>
<View style={styles.dashboard__wrapper}>
<TextInput
placeholder="why is this working??
"
style={styles.form__input}
></TextInput>
<UsersFlatList />
</View>
</>
)
}
If I change that text - I see the changes happening in expo go immediately. However if I make changes to the data in the flatlist, I don't see any changes at all and I have to refresh the app to see any of the updates changes.
import { useEffect, useState } from "react"
import { Text, View, SafeAreaView, FlatList, StyleSheet } from "react-native"
import UserCard from "./UserCard"
import { collection, getDocs } from "firebase/firestore"
import db from "../firebaseConfig"
const UsersFlatList = () => {
const [onlineUsers, setOnlineUsers] = useState([])
useEffect(() => {
const fetchAllData = async () => {
const foundUsers = await getDocs(collection(db, "users"))
const currentUsers = []
foundUsers.forEach((user) => currentUsers.push(user.data()))
setOnlineUsers(() => currentUsers)
}
const matchedUsers = fetchAllData()
}, [])
return (
<FlatList
data={onlineUsers}
renderItem={UserCard}
keyExtractor={(item) => item.email}
></FlatList>
)
}
export default UsersFlatList
import { Text, View, StyleSheet } from "react-native"
const UserCard = ({ item }) => {
console.log(item)
return (
<View style={styles.userCard}>
<View style={styles.online__icon}></View>
<Text>{item.username} is a</Text>
</View>
)
}
const styles = StyleSheet.create({})
export default UserCard
Appreciate this is a long one to read but any help or a pointer would be amazing thanks
Related
I have a react native appication. It is a mockup of zillow. I created a useContext object that stores several of the different useState to store different information I want the home page and all of its children components to have access to.
From the home page, the user can click on the sort button and it redirects the user to a different page so that he can change how the information is sorted.
when the user clicks on a sort option, I have it os that it goes back to the home page by using navigation.goBack(). The issue is that every time the screen changes, the entire useContect object reverts to the original state that is set by default.
I need to figure out how to get the state in useContect to not revert to default when I change screens.
I tried scoping the useContext.provider to wrap the whole navigation stack to see if that would change anything but it did not work. Does anyone know a way to do this so that I can use different screens without reseting the entire useContext
Here is the HomeScreen:
import React, { useState, createContext, useEffect} from 'react'
import {View, Text, StyleSheet} from 'react-native'
import SearchAndFilterComponent from '../components/HomeScreen/SearchAndFilterComponent';
import ResultsComponent from '../components/HomeScreen/ResultsComponent';
import { SearchFilterSortContextProvider } from '../context/SearchFilterSortContext';
const HomeScreen = () => {
return (
<SearchFilterSortContextProvider>
<View style={styles.screen}>
<SearchAndFilterComponent/>
<View style={styles.fullSplit}></View>
<ResultsComponent />
</View>
</SearchFilterSortContextProvider>
)
}
const styles = StyleSheet.create({
fullSplit: {
width: '100%',
height: 2,
backgroundColor: 'black'
}
})
export default HomeScreen
SearchFilterSortContext.js:
import { useState, createContext, useEffect } from "react";
export const SearchFilterSortContext = createContext(null)
export const SearchFilterSortContextProvider = ({children}) => {
// General Search State
const [currentSearch, setCurrentSearch] = useState('')
const [activeSearch, setActiveSearch] = useState('')
const [sort, setSort] = useState('')
const [filter, sestFilter] = useState()
const [results, setResults] = useState([])
const [resultCounter, setResultCounter] = useState(0)
const [page, setPage] = useState(1)
const [totalPages, setTotalPages] = useState(0)
const [resultView, setResultView] = useState('List')
// Filter Home Type States
const [house, setHouse] = useState(true)
const [apartment, setApartment] = useState(false)
const [condos, setCondos] = useState(false)
const [townHouse, setTownHouse] = useState(false)
const [multiFamily, setMultiFamily] = useState(false)
const [manufactured, setManufactured] = useState(false)
const [land, setLand] = useState(false)
// Filter Home Details States
const [priceMin, setPriceMin] = useState(0)
...
// data management state
const [loadingMain, setLoadingMain] = useState(false)
return (
<SearchFilterSortContext.Provider value={{house, setHouse,
apartment, setApartment,
condos, setCondos,
townHouse, setTownHouse,
multiFamily, setMultiFamily,
manufactured, setManufactured,
land, setLand,
...
loadingMain, setLoadingMain}}>
{children}
</SearchFilterSortContext.Provider>
)
}
SortSelectionScreen.js:
import React, { useContext } from 'react'
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'
import { Feather } from '#expo/vector-icons'
import { useNavigation } from '#react-navigation/native'
import { SearchFilterSortContext } from '../../context/SearchFilterSortContext'
const SortOptionsComponent = () => {
const {sort, setSort} = useContext(SearchFilterSortContext)
const navigation = useNavigation()
return (
<View>
<TouchableOpacity onPress={() => {setSort('Homes_for_You'); navigation.goBack()}} style={styles.row}>
<Text style={styles.text}>Homes For You</Text>
{
sort === 'Homes_for_You' ? <Feather name='check' color={'black'} size={20}/> : null
}
</TouchableOpacity>
<TouchableOpacity onPress={() => {setSort('Newest'); navigation.goBack()}} style={styles.row}>
<Text style={styles.text}>Newest</Text>
{
sort === 'Newest' ? <Feather name='check' color={'black'} size={20}/> : null
}
</TouchableOpacity>
...
</View>
)
}
Component that renders the results
ResultsComponent.js:
import React, { useEffect, useContext } from 'react'
import { View, Text, StyleSheet, TouchableOpacity, ScrollView } from 'react-native'
import axios from 'axios'
import { useNavigation } from '#react-navigation/native'
import { extendedPropertOptions } from '../../../zillow'
import { SearchFilterSortContext } from '../../context/SearchFilterSortContext'
import LoadingComponent from '../GeneralComponents.js/LoadingComponent'
import PropertyTile from './PropertyTile'
const ResultsComponent = () => {
const navigation = useNavigation()
const {results, setResults} = useContext(SearchFilterSortContext)
const {setResultCounter} = useContext(SearchFilterSortContext)
const {setTotalPages} = useContext(SearchFilterSortContext)
const {sort, setCurrentSearch} = useContext(SearchFilterSortContext)
const {loadingMain, setLoadingMain} = useContext(SearchFilterSortContext)
const {currentSearch, activeSearch} = useContext(SearchFilterSortContext)
const getDefaultResults = () => {
setLoadingMain(true)
extendedPropertOptions.params.location = 'Los Angeles, CA'
axios.request(extendedPropertOptions)
.then((response) => {
setResults(response.data.props)
setResultCounter(response.data.totalResultCount)
setTotalPages(response.data.totalPages)
setLoadingMain(false)
})
.catch((error) => {
console.error(error)
})
}
// apply search with all the filters
const getFilteredResults = () => {
console.log('fire search')
console.log(sort)
setLoadingMain(true)
{
activeSearch === '' ?
currentSearch === '' ?
extendedPropertOptions.params.location = 'Los Angeles, CA'
: extendedPropertOptions.params.location = currentSearch
: extendedPropertOptions.params.location = activeSearch
}
extendedPropertOptions.params.sort = sort
console.log(extendedPropertOptions)
axios.request(extendedPropertOptions)
.then((response) => {
setResults(response.data.props)
setResultCounter(response.data.totalResultCount)
setTotalPages(response.data.totalPages)
setCurrentSearch('')
setLoadingMain(false)
})
.catch((error) => {
console.error(error)
})
}
useEffect(() => {
getDefaultResults()
}, [])
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
getFilteredResults()
})
return unsubscribe
}, [navigation])
return (
<ScrollView style={styles.scroll}>
{
loadingMain === true ? <View><LoadingComponent /></View> : <View>{results.map((item) => {return(<View key={item.zpid}><PropertyTile item={item}/></View>)})}</View>
}
</ScrollView>
)
}
when I go back to the results component, and it runs getFilteredResults the sort has reverted back to ''. That is what I need to fix.
Need help converting RNLocation.requestPermission and _startUpdatingLocation to work with function components in react native.
Not sure how to approach this
LatLon Component File
import React, {useState} from "react";
import { SafeAreaView, StyleSheet, Text, } from "react-native";
import RNLocation from "react-native-location";
import moment from "moment";
import styled from 'styled-components/native';
function LatLonBox(){
const [currentLocation, setLocation] = useState(null);
const styles = StyleSheet.create({
Custom_Text:{
textAlign:'center',
fontSize:15
}
});
const LocationContainer = styled.View`
marginTop: 10;
marginBottom: 10;
`;
//How to make this work in functional style component
RNLocation.requestPermission({
ios: "whenInUse"
}).then(granted => {
if (granted) {
this._startUpdatingLocation();
}
});
}
//How to make this work in functional style component
_startUpdatingLocation = () => {
this.locationSubscription = RNLocation.subscribeToLocationUpdates(
locations => {
this.setState({ location: locations[0] });
}
);
};
return(
<SafeAreaView>
<React.Fragment>
<LocationContainer>
<Text style = {styles.CustomText}>{currentLocation.longitude}</Text>
<Text style = {styles.CustomText}>{currentLocation.latitude}</Text>
</LocationContainer>
</React.Fragment>
</SafeAreaView>
);
}
export default LatLonBox;
Screen File //Just extra if needed
import React from 'react';
import { SafeAreaView } from 'react-native';
import { StackParamList } from '../types';
import { BottomSheetModalProvider } from '#gorhom/bottom-sheet';
import { NativeStackScreenProps } from '#react-navigation/native-stack';
import LatLonBox from '../components/LatLonBox';
export default ({}: NativeStackScreenProps<StackParamList, 'Main'>): JSX.Element => {
return (
<BottomSheetModalProvider>
<SafeAreaView style={{ flex: 1}}>
<LatLonBox/> //where LatLon will be placed
</SafeAreaView>
</BottomSheetModalProvider>
);
};
Link to RNLocation Library
You can check this
Hey hope this helps, feel free for doubts:
const _startUpdatingLocation = () => {
const locationSubscription = RNLocation.subscribeToLocationUpdates(
locations => {
setLocation(locations[0])
}
);
};
const requestPermission = useCallback(() => {
RNLocation.requestPermission({
ios: "whenInUse"
}).then(granted => {
if (granted) {
_startUpdatingLocation();
}
});
}
},[_startUpdatingLocation])
useEffect(() => {
requestPermission()
},[requestPermission])
//How to make this work in functional style component
I have two screens:
Screen A
import React, { useState } from "react";
import { Text, View, Button } from "react-native";
const ViewA = ({ navigation }) => {
const [val, setVal] = useState(null);
const [val2, setVal2] = useState(null);
const callBack = (value1,value2) => {
setVal(value1);
setVal2(value2);
};
const onNextPress = () => {
navigation.navigate("Second Screen", { callBack: callBack });
};
return (
<View>
<Text>{val}{val2}</Text>
<Button title="Next" onPress={onNextPress} />
</View>
);
};
export default ViewA;
Screen B
import React from "react";
import { View, Button } from "react-native";
const ViewB = ({ route, navigation }) => {
const onBackPress = () => {
const { callBack } = route.params;
callBack(5,6); // Your new value to set
navigation.goBack();
};
return (
<View>
<Button title="back" onPress={onBackPress} />
</View>
);
};
export default ViewB;
when I enter screen B a warning appears: non-serializable values were found in the navigation state. how can I solve it?
According to the docs for I get the warning "Non-serializable values were found in the navigation state"
This can happen if you are passing non-serializable values such as class instances, functions etc. in params. React Navigation warns you in this case because this can break other functionality such state persistence, deep linking etc.
...
If you don't use state persistence or deep link to the screen which accepts functions in params, then the warning doesn't affect you and you can safely ignore it. To ignore the warning, you can use LogBox.ignoreWarnings.
import { LogBox } from 'react-native';
LogBox.ignoreLogs([
'Non-serializable values were found in the navigation state',
]);
An alternative would be to move the state into route params instead of local state of ViewA (which means you can set it when navigating):
import React, {useState, useEffect} from 'react';
import { Text, View, Button } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
const Stack = createStackNavigator();
const ViewA = ({ route, navigation }) => {
const onNextPress = () => {
navigation.navigate("ViewB", {
previousScreen: route.name
});
};
return (
<View>
<Text>ViewA</Text>
<Text>Params: {JSON.stringify(route.params)}</Text>
<Button title="Next" onPress={onNextPress} />
</View>
);
};
const ViewB = ({ route, navigation }) => {
const onBackPress = () => {
navigation.navigate(route.params.previousScreen, {
val: 5,
val2: 6,
})
};
return (
<View>
<Text>ViewB</Text>
<Text>Params: {JSON.stringify(route.params)}</Text>
<Button title="back" onPress={onBackPress} />
</View>
);
};
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator mode="modal">
<Stack.Screen name="ViewA" component={ViewA} />
<Stack.Screen name="ViewB" component={ViewB} />
</Stack.Navigator>
</NavigationContainer>
);
}
Snack
anyway, if you still need to run that callback from another screen, you can make a custom class to subscribe to events and store the callback like so:
class SetNewPropsListener {
constructor(){
this.listeners = []
this.subscribe = ({ id, cb }) => {
this.listeners = [...this.listeners.filter((x)=> x.id !== id), { id, cb }]
}
this.unsubscribe = ({ id }) => {
this.listeners = this.listeners.filter((x)=> x.id !== id)
}
this.propogate = (id, newProps) => {
this.listeners.forEach((x)=> x.id === id && x.cb(newProps))
}
}
}
export const SetNewProps = new SetNewPropsListener()
and then, in the first screen you're navigating from, you can register callback function with unique id like so:
import { SetNewProps } from '../utils/EventListeners'
const callbackToInvokeFromOtherScreen = (newParamsFromNextScreen)=>{
// do whatever with new values
}
componentDidMount(){
SetNewProps.subscribe({ id: 'your_unique_id', cb: callbackToInvokeFromOtherScreen })
}
and then in the next screen you navigate to, you can access the stored callback from the SetNewProps class instance and execute it with your custom params like so:
import { SetNewProps } from '../utils/EventListeners'
const propsToPassToCallback = {}
SetNewProps.propogate('your_unique_id', propsToPassToCallback)
with right logic, this subscriber class method can solve many problems, like invoking peer components methods (like when you have swipe to delete interaction, you don't want any other ListView item to be left open when you swiped another one)
the error always occurred when i pass ref to a renderItem Element which passed to flatList component, what i want here is to manipulate the item itself with refs but i always get this error
here is my code snippet
import React, { useState, useRef, useEffect } from 'react';
import { View, Text, FlatList, Button } from 'react-native';
import Animated, { Transitioning, Transition } from 'react-native-reanimated';
import PropTypes from 'prop-types';
import styles from './styles';
const AnimatedHorizontalList = ({ data }) => {
const [list, setList] = useState([]);
const ref = useRef();
const transition = (
<Transition.Sequence>
<Transition.Out delayMs={200} type="scale" />
<Transition.Change delayMs={200} interpolation="easeInOut" />
<Transition.In delayMs={2000} type="fade" />
</Transition.Sequence>
);
useEffect(() => {
ref.current.animateNextTransition();
setList(data);
});
const renderItem = ({ item }) => {
return (
<Transitioning.View ref={ref} transition={transition}>
<View>
<Text>{item}</Text>
</View>
</Transitioning.View>
);
};
return (
<View style={styles.container}>
<FlatList horizontal data={list} renderItem={renderItem} />
</View>
);
};
AnimatedHorizontalList.propTypes = {};
export default AnimatedHorizontalList;
When you use useRef() on line const ref = useRef();, you can start ref with a default value, so you can write const ref = useRef({ animateNextTransition: () => {} });, that way it dont break on line ref.current.animateNextTransition();
Im using this package to add credit cards to my app, how would you åccess the refs as in the example when using this in a functional component?
This is how they show to update values:
this.refs.CCInput.setValues({ number: "4242" });
I don't know how to access that inside a functional component?
This is my component to Edit a card, and I want to add the current values to the inputs.
import React, {useContext, useState, useRef} from 'react';
import {Text, View, StyleSheet, TouchableOpacity} from 'react-native';
import {CreditCardInput} from 'react-native-input-credit-card';
import Store from '../../store/context';
const styles = StyleSheet.create({
});
export default function EditCard(props) {
const {navigate} = props.navigation;
const {cardNumber} = props.navigation.state.params;
const {state, dispatch} = useContext(Store);
const [card, setCard] = useState(
state.cards.find(card => {
return card.values.number === cardNumber;
}),
);
const _onChange = form => {
if (form.valid) {
setCard(form);
}
};
const updateCard = () => {
dispatch({
type: 'ADD_CARD',
payload: card,
});
navigate.goBack();
};
return (
<View style={styles.container}>
<CreditCardInput
validColor={'#47B278'}
invalidColor={'#E23C3C'}
placeholderColor={'#efefef'}
onChange={_onChange}
requiresName
/>
<TouchableOpacity
style={
card
? card.valid
? styles.button
: styles.buttonDisabled
: styles.buttonDisabled
}
disabled={card ? (card.valid ? false : true) : true}
onPress={updateCard}>
<Text style={styles.buttonText}>Update Credit Card</Text>
</TouchableOpacity>
</View>
);
}
You need to use useRef hook:
const cciRef = useRef();
<CreditCardInput ref={cciRef}/>;
// cciRef.current holds the reference
export default function EditCard(props) {
const cciRef = useRef();
useEffect(() => {
console.log(cciRef.current);
cciRef.current.setValues({ number: "4242" });
}, []);
return (
<View style={styles.container}>
<CreditCardInput ref={cciRef} />
</View>
);
}