one place I seem to be stuck is on is being able to populate an array of objects, which are used for a FlatList later on.
I get this data from my FireStore – for each document, it will push the objects into ‘const DATA = []’
But when I run the ‘getUsers()’ inside of UseEffect, it only updates ‘DATA’ inside of the method, it doesn’t update the global variable.
Im new to react native, so im probably missing something important within my structure. Thanks for any assistance tho!
I need the format of DATA to look like this example:
My Code:
const MainScreen = () => {
const DATA = [];
const navigation = useNavigation()
const [selectedId, setSelectedId] = useState(null);
const usersCollectionRef = collection(db, "Posts");
const [Data, setData]= useState([]);
LogBox.ignoreLogs(['Setting a timer']);
LogBox.ignoreLogs(['AsyncStorage has been extracted']);
LogBox.ignoreAllLogs(true);
useEffect(() => {
getUsers();
console.log(DATA);
}, []);
const getUsers = async () => {
const data = await getDocs(usersCollectionRef);
data.forEach(doc =>{
const dataG = (doc.id, "=>", doc.data());
DATA.push({
id: doc.id,
title: dataG.status +" "+ dataG.Type,
status: dataG.status,
location: dataG.Location,
type: dataG.Type,
injured: dataG.Injured,
collar: dataG.Collar,
color: dataG.Colour,
username: dataG.username,
description: dataG.Description,
imagesrc: dataG.picture });
})
};
const Item = ({ item, onPress, backgroundColor, textColor }) => (
<View style={styles.ContentBox}>
<TouchableOpacity onPress={onPress} style={[styles.item, backgroundColor]}>
<Text style={[styles.title, textColor]}>{item.title}</Text>
<Text style={styles.ContentText}>By: {item.username}</Text>
</TouchableOpacity>
<Image source = {{uri: item.imagesrc}}
style = {{ width: 200, height: 200, alignSelf:'center' }}/>
<Text style={styles.ContentText}>Animal: {item.type}</Text>
<Text style={styles.ContentText}>Location: {item.location}</Text>
<Text style={styles.ContentText}>Injured: {item.injured}</Text>
<Text style={styles.ContentText}>Colour: {item.color}</Text>
<Text style={styles.ContentText}>Has a Collar: {item.collar}</Text>
<Text style={styles.ContentText}>Description: {item.description}</Text>
</View>
);
const renderItem = ({ item }) => {
const backgroundColor = item.status === "lost" ? '#b80909' : '#098231';
const color = item.id === selectedId ? 'white' : 'white';
return (
<Item
item={item}
onPress={() => setSelectedId(item.id)}
backgroundColor={{ backgroundColor }}
textColor={{ color }}
/>
);
};
const PostScreen = () =>{
navigation.navigate('PostScreen');
}
return (
<SafeAreaView style={styles.container}>
<View style={styles.MainFeed}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
extraData={selectedId}
/>
</View>
)
instead of pushing data in a variable and then updating the state, you can do it like this directly -
setData([...Data,{
id: doc.id,
title: dataG.status +" "+ dataG.Type,
status: dataG.status,
location: dataG.Location,
type: dataG.Type,
injured: dataG.Injured,
collar: dataG.Collar,
color: dataG.Colour,
username: dataG.username,
description: dataG.Description,
imagesrc: dataG.picture
}])
The answer is that after doing DATA.push(...), I needed to setData(DATA).
Related
I have a screen in my ap, that's users upload photos. In this screen, photo can be selected from gallery or taken from camera. I have an array state where I keep the uri of photos. I show the photos in flatlist with this array state. Next to each photo there is an icon to delete. But the function called by this icon does not update the array state and delete the photo. Probably state updates asynchronously. Please help me!
const [images, setImages] = useState([]); //images array
const selectFile = async() => { //image from gallery
await launchImageLibrary(options, res => {
console.log('Response = ', res);
if (res.didCancel) {
console.log('User cancelled image picker');
}else {
let source = res;
setImages(prevImages=>([...images,source.assets[0].uri]));
console.log(images);
}
});
};
const takePicture = async () => { //take picture save cameraroll and get picture
try {
const options = {quality: 0.5, base64: false};
let imageUri;
const imageData = await camera.takePictureAsync(options).then(data => {
imageUri=data.uri;
});
await CameraRoll.save(imageUri, {type: 'photo'});
CameraRoll.getPhotos({
first:1,
assetType: 'Photos',
}).then((r)=>{
let imageUri=r.edges[0].node.image.uri;
setImages([...images,imageUri]);
});
console.log(images);
}catch (e) {
console.log(e);
}
};
const removeImage=async(item)=>{ //delete function
setImages(prevImages=>([...prevImages,images.filter(x=>x!==item)]));
alert(images);
}
return(
<FlatList
data={images}
keyExtractor={(item,index)=>item}
numColumns={2}
renderItem={({item,index})=>{
return (
<View style={{margin:2, borderWidth:2, borderColor:'rgba(255,255,255,0.42)',shadowColor: 'black',
shadowOpacity: 0.25,
shadowOffset: {width: 0, height: 2},
shadowRadius: 8,
overflow: Platform.OS === 'android' ? 'hidden' : 'visible',width:160,height:165,backgroundColor:'transparent'}}>
<Image
resizeMode="cover"
style={{
flex:1,
}}
source={{uri: item}}
/>
<TouchableOpacity
key={index}
underlayColor='transparent'
onPress={()=>removeImage(item)}
>
<AntDesign name={'minuscircle'} size={24} color={Colors.molekulRed} style={{marginLeft:'85%', marginTop:'-100%'}}/>
</TouchableOpacity>
</View>
}}
/>
)
try this,
const removeImage=async(item)=>{ //delete function
setImages(prevImages=>([...prevImages.filter(x=>x!==item)]));
alert(images);
}
now i use react native, expo project.
and i apply realtime database, iframe, FlatList.
and i use videoID instead of the 'scr= URL'.
i hope to autoplay by auto changing the videoId in my realtime database.
i think it can be possible by using index number.
and when i console.log selected index, it presented on terminal.
but i don't know how to apply that path for my code.
this is my realtime database.
enter image description here
and this is my codes.
import YoutubePlayer from "react-native-youtube-iframe";
const [state, setState] = useState([])
const [cardID, setCardID] = useState(["i4S5hvPG9ZY"])
const [playing, setPlaying] = useState(true);
const onStateChange = useCallback((state) => {
if (state === "ended") {
setPlaying(true)
}
}, []);
const onPress = ({ item, index }) => {
console.log({ index })
return (
setCardID(item.id.videoId)
)
}
...
useEffect(() => {
setLoading(true);
firebase_db.ref('/TGBSitems')
.once('value')
.then((snapshot) => {
console.log("TGBS에서 데이터 가져왔습니다!!")
let TGBSitems = snapshot.val()
setState(TGBSitems)
setTotalDataSource(TGBSitems);
setLoading(false);
})
.catch(err => { setLoading(false); setError(err); })
}, []);
...
return (
...
<View>
<YoutubePlayer
height={200}
play={playing}
videoId={cardID}
onChangeState={onStateChange}
// playList
/>
</View>
...
<FlatList
data={state}
// ItemSeparatorComponent={ItemSeparatorView}
keyExtractor={(index) => index.toString()}
renderItem={({ item, index }) => (
<View style={styles.cardContainer}>
<TouchableOpacity style={styles.card} onPress={() => onPress({ item, index })}>
<Image style={styles.cardImage} source={{ uri: item.snippet.thumbnails.medium.url }} />
<View style={styles.cardText}>
<Text style={styles.cardTitle} numberOfLines={1}>{item.snippet.title}</Text>
<Text style={styles.cardDesc} numberOfLines={3}>{item.snippet.description}</Text>
<Text style={styles.cardDate}>{item.snippet.publishedAt}</Text>
<Text style={styles.cardDate}>{item.id.videoId}</Text>
<Text style={styles.cardDate}>{index}</Text>
</View>
...
I am using react-native to build my app.
The main screen stores an object in the state:
const [menu, setMenu] = React.useState({name: "Pizza", toppings: {chicken: 2}})
and I display this state to the user as:
<Text>Qty: {menu.toppings.chicken}</Text>
I have a counter to update the state the loaginc to update the state is:
const handleChange = (action: string) => {
if (action === 'add') {
setMenu((prev) => {
prev.toppings.chicken += 1
return prev
})
} else if (action === 'subtract') {
setMenu((prev) => {
prev.calendar.booking -= 1
return prev
})
}
}
My function works correctly and changes the state to according to my needs(verified by console logging). However these changes are not reflexted in the <Text> where I am showing the quantity.
You should research more about Shallow compare:
How does shallow compare work in react
In your case, you can try this code:
const handleChange = (action: string) => {
if (action === 'add') {
setMenu((prev: any) => {
prev.toppings.chicken += 1;
return { ...prev };
});
} else if (action === 'subtract') {
setMenu((prev: any) => {
prev.calendar.booking -= 1;
return {...prev};
});
}
};
This is your solution, it gets complicated with nested objects :
const handleChange = () => {
setMenu((prevState) => ({
...prevState,
toppings: { chicken: prevState.toppings.chicken + 1 }
}));
};
Hope This Solution Help:
import React from 'react';
import {View, Text, TouchableOpacity} from 'react-native';
export default App = () => {
const [menu, setMenu] = React.useState({
name: 'Pizza',
toppings: {
value: 2,
},
});
React.useEffect(() => {
console.log('menu', menu);
}, [menu]);
const handleIncrement = () => {
setMenu(prev => ({
...prev,
toppings: {...prev.toppings, value: prev.toppings.value + 1},
}));
};
const handleDecrement = () => {
setMenu(prev => ({
...prev,
toppings: {...prev.toppings, value: prev.toppings.value - 1},
}));
};
return (
<View style={{flex: 1}}>
<View
style={{
alignItems: 'center',
flex: 1,
flexDirection: 'row',
justifyContent: 'space-evenly',
}}>
<TouchableOpacity onPress={handleDecrement}>
<Text style={{fontSize: 44}}>-</Text>
</TouchableOpacity>
<Text>{menu.toppings.value}</Text>
<TouchableOpacity onPress={handleIncrement}>
<Text style={{fontSize: 44}}>+</Text>
</TouchableOpacity>
</View>
</View>
);
};
How do I invalidate data after refreshing the page? It doesn't seem to invalidate while it is supposed to. It still displays the old data even though something changed on the server-side.
I have this same problem when I use useMutation when posting data to the backend, the UI doesn't update even after using the QueryClient.
Below is my code:
const IncomeManager: React.FC<any> = (props) => {
const queryClient = new QueryClient();
const {isLoading, isError, isFetching, data}: QueryObserverResult = useQuery('typeIncomes', () => typesApi.getAllTypes());
const [refresh, setRefresh] = useState<boolean>(false);
const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
//#ts-ignore
const handleClose = (): void => {
setIsModalVisible(false);
}
const refreshContent = async () => {
await queryClient.invalidateQueries('typeIncomes');
console.log("Content has been refreshed!!!");
}
return (
<View style={style.container}>
<View>
<AppText style={style.title}>{data ? data.length : 0} income types available</AppText>
</View>
<FixedButton
title={"plus"}
onPress={() => props.navigation.navigate(navConstants.ADDTYPE, {type: "incomes"})}
/>
{
isLoading || isFetching ? <PageActivityIndicator visible={isLoading || isFetching}/> :
<FlatList style={{width: "100%"}}
data={data}
renderItem={
({item}) => <CategoryItem
id={item.type_id}
title={item.title}
subTitle={item.description}
onLongPress={() => console.log("Very long press!")}
onPress={() => props.navigation.navigate(navConstants.EDITTYPE,
{
item: {
id: item.type_id,
title: item.title,
description: item.description
}
})
}
/>
}
keyExtractor={item => item.type_id}
refreshing={refresh}
onRefresh={async () => refreshContent()}
/>
}
</View>
);
}
export default IncomeManager;
const style = StyleSheet.create({
container: {
flex: 1,
width: "100%",
backgroundColor: constants.COLORS.secondary,
alignItems: "center"
},
title: {
color: constant.COLORS.lightGray,
paddingVertical: 10,
fontSize: 17,
marginBottom: 0
},
});
You are creating a new QueryClient every time your component renders by doing:
const queryClient = new QueryClient()
The queryClient holds your cache, which holds your data. There should be only one (like a redux store) - the one you create initially and then pass to the QueryClientProvider. To retrieve this Singleton instance, you can do:
const queryClient = useQueryClient()
it will give you the instance via React context. Invalidation on that queryClient should work. This is also how everything in the docs and all the example are set up.
I have a Delete on Swipe function that returns TypeError: undefined is not an object(evaluating 'singleValue.StopTracking')
Im using expo to run the project
After I swipe, expo returns the error, I click dismiss and the product disappears from my cart
I don't know what any more information to give
const rowTranslateAnimatedValues = {};
Array(20)
.fill('')
.forEach((_, i) => {
rowTranslateAnimatedValues[${i}] = new Animated.Value(1);
});
const [cart, setCart] = useState([]);
const [products, setProducts] = useState([]);
const [valueInput, setValueInput] = useState(1);
const [listData, setListData] = useState(
Array(4)
.fill('')
.map((_, i) => ({ key: `${i}`, text: `item #${i}` }))
);
const onSwipeValueChange = swipeData => {
const { key, value } = swipeData;
if (
value < -Dimensions.get('window').width &&
!this.animationIsRunning
) {
this.animationIsRunning = true;
Animated.timing(rowTranslateAnimatedValues[key], {
toValue: 0,
duration: 500,
})
.start(() => {
const newData = [...listData];
const prevIndex = listData.findIndex(item => item.key === key);
newData.splice(prevIndex, 1);
setListData(newData);
this.animationIsRunning = false;
});
}
};
const renderItem = data => (
<Animated.View
>
<TouchableHighlight
onPress={() => console.log('You touched me')}
style={styles.rowFront}
underlayColor={'#AAA'}
>
<View>
<Text>I am {data.item.text} in a SwipeListView</Text>
</View>
</TouchableHighlight>
</Animated.View>
);
const renderHiddenItem = () => (
<View style={styles.rowBack}>
<View style={[styles.backRightBtn, styles.backRightBtnRight]}>
<Text style={styles.backTextWhite}>Delete</Text>
</View>
</View>
);
return (
<Block flex center style={styles.cart}>
<View style={styles.container}>
<SwipeListView
disableRightSwipe
data={cart}
renderItem={renderProduct}
showsVerticalScrollIndicator={false}
keyExtractor={(item, index) => `${index}-${item.title}`}
ListEmptyComponent={renderEmpty()}
ListHeaderComponent={renderHeader()}
ListFooterComponent={renderFooter()}
renderHiddenItem={renderHiddenItem}
rightOpenValue={-Dimensions.get('window').width}
onSwipeValueChange={onSwipeValueChange}
/>
</View>
</Block>
);
}
For anyone who faces this problem,
you can just think this.animationIsRunning of a variable that manages the start and the end of animation.
So you can just use const animationIsRunning = useRef(false) to manage the animation state.
You can use animationIsRunning.current instead of this.animationIsRunning.
Also, this.animationIsRunning = true or false can be changed into animationIsRunning.current = true or false
See code at
https://github.com/jemise111/react-native-swipe-list-view/pull/521/commits/2946e3a123f26ef5292a3884e0aec58436386e18