Edit todo from list in React - reactjs

I don't know why I can't make it work but I just want to edit the todo from the list and save it onBlur (when I press outside the box). I've made this work before but I think I got brain freeze or something. I deleted my attempts so the functions are empty now. Can someone nudge me in the right direction or just fill in the blanks? Thank you
UPDATE: so I want to press the todo that I've added to the list (the textinput) and then EDIT the already present todo, THEN save it to the list again!
const TEST = () => {
const [todo, setTodo] = useState("")
const [todoList, setTodoList] = useState([])
const onChangeHandler = (text) =>{
setTodo(text)
}
const saveTodo = () =>{
setTodoList([...todoList , {title: todo, key:`${Math.random()}`}])
setTodo("")
}
const onChangeTodo = () =>{
//UPDATE HERE
}
const saveonChangeTodo = () =>{
//SAVE HERE
}
return(
<View style={{flex:1, backgroundColor: "beige", justifyContent: "center", alignItems: "center", paddingTop: 300,}}>
<TextInput
placeholder="Write todo here"
style={{backgroundColor:"white", padding: 20, width: 300,}}
value={todo}
onChangeText={text=>onChangeHandler(text)}
onBlur={saveTodo}
/>
<FlatList
data={todoList}
renderItem={({item}) => {
return(<TextInput style={{borderColor: "black", borderWidth: 2, width: 200, padding: 20, margin: 10}}
value={item.title}
onChangeText={text=>onChangeTodo(text, item)}
onBlur={saveonChangeTodo}
/>
)
}}/>

Change your onChangeTodo as below. No need to have saveonChangeTodo as it is already supported by onChangeTodo.
const onChangeTodo = (text, item) => {
setTodoList((todos) =>
todos.map((todo) =>
todo.key === item.key ? { ...todo, title: text } : todo
)
);
};
Code Sandbox

Related

React Native Image Render Flatlist and Use State hook

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);
}

REACT NATIVE: fetching array of image from firestore database collection to carousel

Good day.
I have managed to fetch an array of images from firestore database collection, the only problem is that it seems that I cannot loop over the array that I am retrieving from the database.
I need to dynamically display my images on the carousel.
import React, { useState, useEffect, useCallback } from 'react';
import { Text, Dimensions, StyleSheet, View, Image } from 'react-native';
import { SwiperFlatList } from 'react-native-swiper-flatlist';
import firestore from '#react-native-firebase/firestore';
import { white } from 'react-native-paper/lib/typescript/styles/colors';
import { color } from 'react-native-reanimated';
import Carousel from 'react-native-reanimated-carousel';
//const colors = ["tomato", "thistle", "skyblue", "teal"];
const names = [{"AdImage": "https://firebasestorage.googleapis.com/v0/b/pindot-65e7c.appspot.com/o/Ads%2FJB.png?alt=media&token=377f1629-6807-4343-b826-93d1c2bc5de6"}, {"AdImage": "https://firebasestorage.googleapis.com/v0/b/pindot-65e7c.appspot.com/o/Ads%2Fgymtarp.png?alt=media&token=b2d1c923-2b5c-4066-bf8f-dc12de399059"}, {"AdImage": "https://firebasestorage.googleapis.com/v0/b/pindot-65e7c.appspot.com/o/photos%2Fb75d0848-b37f-4584-ab46-f355ca838e83.jpg?alt=media&token=adaa8559-de91-4ced-b056-84300123102e"}, {"AdImage": "https://firebasestorage.googleapis.com/v0/b/pindot-65e7c.appspot.com/o/Ads%2FSANDO.png?alt=media&token=274c946b-7f20-4c07-8545-431a0558257c"}];
const App = () => {
const [ads, setAds] = useState([]); // Initial empty array of ads
useEffect(() => {
const subscriber = firestore()
.collection('AdsDB')
//.orderBy('Menu', 'asc')
.onSnapshot(querySnapshot => {
//const ads = [];
/*
querySnapshot.forEach(documentSnapshot => {
ads.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
*/
querySnapshot.forEach(doc => {
const { AdImage } = doc.data();
ads.push({
//id: doc.id,
AdImage,
//Price,
});
});
setAds(ads);
console.log(ads);
//console.log(Object.entries(ads));
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}, []);
return (
<View style={styles.container}>
<SwiperFlatList
autoplay
autoplayDelay={2}
autoplayLoop
index={2}
showPagination
data={ads}
renderItem={({ item }) => (
<View style={[styles.child, { backgroundColor: item }]}>
<Text style={styles.text}>{item.AdImage}</Text>
<Image
style={{
width: "100%",
height: "30%",
position: 'absolute',
top:0,
alignItems: 'center',
justifyContent: 'center'}}
source={{uri : item.AdImage}}
resizeMode={'stretch'} // cover or contain its upto you view look
/>
</View>
)}
/>
</View>
)
};
const { width } = Dimensions.get('window');
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: 'white', },
child: { width, justifyContent: 'center', height: '100%' },
text: { fontSize: width * 0.1, textAlign: 'center' },
});
export default App;
Thank you for answering my question. Mabuhay! I'm from the Philippines.
The problem I am seeing is that probably meanwhile you are receiving asynchronously the snapshots, the setState did not ended running (a race condition between the velocity receiving snapshots and the exact time needed to persist the state)
I would try to delay the state change and use the previous state value in the setter, like this:
useEffect(() => {.
const subscriber = firestore()
.collection('AdsDB')
.onSnapshot(querySnapshot => {
querySnapshot.forEach(doc => {
const { AdImage } = doc.data();
setTimeout(() => {
setAds((prevAds) => [...prevAds, AdImage]);
}, 500);
});
console.log(ads);
//console.log(Object.entries(adsFromFirebase));
});
These are my code, Sir. It gives an empty array.
import React, { useState, useEffect, useCallback } from 'react';
import { Text, Dimensions, StyleSheet, View, Image } from 'react-native';
import { SwiperFlatList } from 'react-native-swiper-flatlist';
import firestore from '#react-native-firebase/firestore';
import { white } from 'react-native-paper/lib/typescript/styles/colors';
import { color } from 'react-native-reanimated';
const colors = ["tomato", "thistle", "skyblue", "teal"];
const names = [{"AdImage": "https://firebasestorage.googleapis.com/v0/b/pindot-65e7c.appspot.com/o/Ads%2FJB.png?alt=media&token=377f1629-6807-4343-b826-93d1c2bc5de6"}, {"AdImage": "https://firebasestorage.googleapis.com/v0/b/pindot-65e7c.appspot.com/o/Ads%2Fgymtarp.png?alt=media&token=b2d1c923-2b5c-4066-bf8f-dc12de399059"}, {"AdImage": "https://firebasestorage.googleapis.com/v0/b/pindot-65e7c.appspot.com/o/photos%2Fb75d0848-b37f-4584-ab46-f355ca838e83.jpg?alt=media&token=adaa8559-de91-4ced-b056-84300123102e"}, {"AdImage": "https://firebasestorage.googleapis.com/v0/b/pindot-65e7c.appspot.com/o/Ads%2FSANDO.png?alt=media&token=274c946b-7f20-4c07-8545-431a0558257c"}];
const App = () => {
const [ads, setAds] = useState([]); // Initial empty array of ads
useEffect(() => {
const subscriber = firestore()
.collection('AdsDB')
.onSnapshot(querySnapshot => {
querySnapshot.forEach(doc => {
const { AdImage } = doc.data();
setTimeout(() => {
setAds((prevAds) => [...prevAds, AdImage]);
}, 500);
});
console.log(ads);
//console.log(Object.entries(adsFromFirebase));
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}, []);
/*
useEffect(() => {
const subscriber = firestore()
.collection('AdsDB')
//.orderBy('Menu', 'asc')
.onSnapshot(querySnapshot => {
//const ads = [];
/*
querySnapshot.forEach(documentSnapshot => {
ads.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
querySnapshot.forEach(doc => {
const { AdImage } = doc.data();
ads.push({
//id: doc.id,
AdImage,
//Price,
});
});
setAds(ads);
//console.log(ads);
//console.log(Object.entries(ads));
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}, []);
*/
return (
<View style={styles.container}>
<SwiperFlatList
autoplay
autoplayDelay={2}
autoplayLoop
index={2}
data={ads}
renderItem={({ item }) => (
<View style={[styles.child, { backgroundColor: item.colors }]}>
<Text style={styles.text}>{item.AdImage}</Text>
<Image
style={{
width: "100%",
height: "30%",
position: 'absolute',
top:0,
alignItems: 'center',
justifyContent: 'center'}}
source={{uri : item.AdImage}}
resizeMode={'stretch'} // cover or contain its upto you view look
/>
{/*}*/}
</View>
)}
/>
</View>
)
};
const { width } = Dimensions.get('window');
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: 'white', },
child: { width, justifyContent: 'center', height: '100%' },
text: { fontSize: width * 0.1, textAlign: 'center' },
});
export default App;
This is the output.
LOG Running "myApp" with {"rootTag":21}
LOG []

React query invalidation is not working in react native

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.

Keep getting "Changing onViewableItemsChanged on the fly is not supported"

In my project I have a (horizontal) FlatList that I want to trigger a function when a new page is visible. I've understood that I need to specify viewabilityConfig and onViewableItemsChanged. However, when I do I keep getting this error msg: "Changing onViewableItemsChanged on the fly is not supported".
I'm a newbie, so looking at the other posts about the same error didnt make me any smarter.
Any ideas?
const DATA = [
20201202 = {
id: "20201202",
data: day1,
},
2020120 = {
id: "2020120",
data: day2,
},
20201204 = {
id: "20201204",
data: day3,
},
]; */
const LiveScreen = (props, { navigation }) => {
const FlatListItem = ({ title }) => (
<View style={styles.item}>
<Text style={styles.title}>{title.day}</Text>
</View>
);
// FLATLIST
const renderFlatList = ({ item }) => <FlatListItem title={item} />;
// VIEWABILITY
const viewabilityConfig = {
waitForInteraction: true,
// At least one of the viewAreaCoveragePercentThreshold or itemVisiblePercentThreshold is required.
viewAreaCoveragePercentThreshold: 95,
itemVisiblePercentThreshold: 75,
};
return (
<View style={{ flex: 1, width: "100%", backgroundColor: "#F4F5F5" }}>
<View style={{ flex: 1 }}>
<SafeAreaView>
<FlatList
horizontal
pagingEnabled
data={DATA}
renderItem={renderFlatList}
keyExtractor={(item) => item.id}
initialScrollIndex={5}
viewabilityConfig={viewabilityConfig}
onViewableItemsChanged={(viewableItems, changed) => {
console.log("Visible items are", viewableItems);
console.log("Changed in this iteration", changed);
}}
/>
</SafeAreaView>
</View>
</View>
);
};
const styles = StyleSheet.create({});
export default LiveScreen;
I have removed the excess code..
Thanks in advance
viewabilityConfig needs to be done in the constructor to avoid this error.
constructor (props) {
super(props)
this.viewabilityConfig = {
waitForInteraction: true,
// At least one of the viewAreaCoveragePercentThreshold or itemVisiblePercentThreshold is required.
viewAreaCoveragePercentThreshold: 95,
itemVisiblePercentThreshold: 75,
}
}
<FlatList
viewabilityConfig={this.viewabilityConfig}
...
/>
For functional components, it needs to be done with Ref hook.
const viewabilityConfig = useRef({
waitForInteraction: true,
// At least one of the viewAreaCoveragePercentThreshold or itemVisiblePercentThreshold is required.
viewAreaCoveragePercentThreshold: 95,
itemVisiblePercentThreshold: 75,
})
Late to the party, but the issue isn't the viewabilityConfig, it's the onViewableItemsChanged.
In a function component, you need to separate out the handler for onViewableItemsChanged and place it in a ref like so:
const handleViewableItemsChanged = useRef(({viewableItems, changed})=>{
console.log("Visible items are", viewableItems);
console.log("Changed in this iteration", changed)
});
<FlatList
...
viewabilityConfig={viewabilityConfig}
onViewableItemsChanged={handleViewableItemsChanged.current}
/>
The same effect can be done with a useCallback hook with an empty dependency array, but this way is a bit more clean.

React Native: I press text input and keyboard is appears and is automatically dismissed once when using Keyboard.addListener

I was trying to remove a view from the screen when the keyboard appears so everything can fit on the screen.
After the keyboard is initially dismissed the first time - with the keyboardDidHide code commented out - it will not automatically dismiss it again.
Here is the code part of the code. I am using Formik for the form.
What am I doing wrong?
const [keyboardUp, setKeyboardUp] = React.useState(false);
Keyboard.addListener("keyboardDidShow", _keyboardDidShow);
Keyboard.addListener("keyboardDidHide", _keyboardDidHide);
React.useEffect(() => {
Keyboard.addListener("keyboardDidShow", _keyboardDidShow);
Keyboard.addListener("keyboardDidHide", _keyboardDidHide);
// cleanup function
return () => {
Keyboard.removeListener("keyboardDidShow", _keyboardDidShow);
Keyboard.removeListener("keyboardDidHide", _keyboardDidHide);
};
}, []);
const _keyboardDidShow = () => {
setKeyboardUp(true);
};
const _keyboardDidHide = () => {
setKeyboardUp(false);
};
const LoginForm=()=>{
let input_height=40;
return (
<Formik
style={{ backgroundColor:"blue" }}
initialValues={{email:'',password:''}}
onSubmit={(values)=> {
let username = values.email;
let p = values.password;
signIn({ username,p })
}}
>
{(props)=>(
<View style={{width:"100%", height:"45%", justifyContent:"center",alignItems:"center"}}>
<TextInput style={{width:"75%", borderColor: "#1d3557", borderWidth:1, height:input_height, margin:"2%", paddingLeft:"3%", backgroundColor:"white", borderColor:"grey", borderRadius:8}}
placeholder='email' onChangeText={props.handleChange('email')} value={props.values.title} />
<TextInput style={{width:"75%", height:input_height, borderColor: "#1d3557", borderWidth:1, margin:"2%", paddingLeft:"3%", backgroundColor:"white", borderColor:"grey", borderRadius:8}}
placeholder='password' onChangeText={props.handleChange('password')} value={props.values.password} />
<TouchableOpacity style={{backgroundColor:"#008b9a", justifyContent:"center", marginTop:"8%", alignItems:"center", width:"75%", height:40, borderRadius:16}}
onPress={props.handleSubmit}>
<Text style={{fontSize:16, fontWeight:"bold"}}>LOGIN</Text>
</TouchableOpacity>
</View>
)}
</Formik>
);
}
I'm seeing on the docs that they add the listeners inside of useEffect from react. Can u try this way?
import React, { useEffect } from "react";
import { Keyboard, TextInput, StyleSheet } from "react-native";
const Example = () => {
useEffect(() => {
Keyboard.addListener("keyboardDidShow", _keyboardDidShow);
Keyboard.addListener("keyboardDidHide", _keyboardDidHide);
// cleanup function
return () => {
Keyboard.removeListener("keyboardDidShow", _keyboardDidShow);
Keyboard.removeListener("keyboardDidHide", _keyboardDidHide);
};
}, []);
const _keyboardDidShow = () => {
alert("Keyboard Shown");
};
const _keyboardDidHide = () => {
alert("Keyboard Hidden");
};
return <TextInput style={s.input} placeholder='Click here ...' onSubmitEditing={Keyboard.dismiss} />;
}
const s = StyleSheet.create({
input:{
margin:60,
padding: 10,
borderWidth: 0.5,
borderRadius: 4,
backgroundColor: "#fff"
}
})
export default Example;
If this doesn't work,
Keyboard.removeListener("keyboardDidShow", _keyboardDidShow);
Keyboard.removeListener("keyboardDidHide", _keyboardDidHide);
Do this
const a = Keyboard.removeListener("keyboardDidShow", _keyboardDidShow);
const b = Keyboard.removeListener("keyboardDidHide", _keyboardDidHide);
return a.remove(),b.remove();

Resources