I have some issue when I want to upload images to firebase (real-time database & storage), in real-time DB, I have Images object has one image as the default, and I don't want to overwrite when I upload other images so I used spread operators ...,
SO,
when I pick One Image and click to upload them it's work and saved without duplicated, but when I pick two or more I see at least two images duplicate after they uploaded so how can I solve these?
structure
here is my function "_SaveImagesToFirebase"
class GalleryScreen extends Component {
constructor(props) {
super(props);
this.state = {
images: [],
newImages: []
};
}
pickMultiple = () => {
ImagePicker.openPicker({
width: 300,
height: 400,
multiple: true,
cropping: true
})
.then(img => {
this.setState({
newImages: img.map(i => {
return {
uri: i.path,
width: i.width,
height: i.height,
mime: i.mime
};
})
});
})
.then(() => this._SaveImagesToFirebase())
.catch(e => console.log(e));
};
_SaveImagesToFirebase = () => {
const uid = firebase.auth().currentUser.uid; // Provider
const { newImages } = this.state;
const provider = firebase.database().ref(`providers/${uid}`);
let imagesArray = [];
newImages.map(image => {
let file = image.uri;
const path = "Img_" + Math.floor(Math.random() * 1500);
const ref = firebase
.storage()
.ref(`provider/${uid}/ProviderGalary/${path}`);
ref.put(file).then(() => {
ref
.getDownloadURL()
.then(images => {
imagesArray.push({
uri: images
});
console.log("Out-imgArray", imagesArray);
})
.then(() => {
provider
.update({
Images: [...this.state.images, ...imagesArray] // Here is the issue
})
.then(() => console.log("done with imgs"));
});
});
});
setTimeout(() => {
console.log("timeout", this.state.images);
}, 8000);
};
componentDidMount() {
const uid = firebase.auth().currentUser.uid;
firebase
.database()
.ref(`providers/${uid}`)
.on("value", snapshot => {
let uri = snapshot.val().Images;
let images = [];
Object.values(uri).forEach(img => {
images.push({ uri: img.uri });
});
this.setState({ images });
});
}
render() {
return (
<View style={styles.container}>
{this.state.images.length === 0 ? (
<View
style={{
flex: 1,
// alignSelf: "center",
backgroundColor: "#fff"
}}
>
<Text
style={{
alignSelf: "center",
padding: 10,
fontSize: 17,
color: "#000"
}}
>
Please upload images
</Text>
</View>
) : (
<FlatList
numColumns={2}
key={Math.floor(Math.random() * 1000)}
data={this.state.images}
style={{
alignSelf: "center",
marginTop: 10
}}
renderItem={({ item }) => {
return (
// <TouchableOpacity style={{ margin: 5, flexGrow: 1 }}>
// <View>
// <Lightbox underlayColor="#fff" backgroundColor="#000">
// <Image
// key={Math.floor(Math.random() * 100)}
// source={{ uri: item.uri }}
// style={{
// alignSelf: "center",
// borderRadius: 15,
// width: width / 2 - 17,
// height: 200
// }}
// width={180}
// height={200}
// resizeMethod="scale"
// resizeMode="cover"
// />
// </Lightbox>
// </View>
// </TouchableOpacity>
<TouchableOpacity
key={Math.floor(Math.random() * 100)}
style={{
margin: 5,
width: width / 2 - 17,
height: 200
}}
>
<Lightbox
style={{ flex: 1 }}
underlayColor="#fff"
backgroundColor="#000"
>
<Image
source={{ uri: item.uri }}
style={{
borderRadius: 15,
width: "100%",
height: "100%"
}}
resizeMethod="auto"
resizeMode="cover"
/>
</Lightbox>
</TouchableOpacity>
);
}}
keyExtractor={(item, index) => index.toString()}
/>
)}
<TouchableOpacity
onPress={() => this.pickMultiple()}
style={{
alignSelf: "flex-end",
width: 57,
height: 57,
right: 10,
bottom: 80,
justifyContent: "center",
alignItems: "center",
borderRadius: 100,
backgroundColor: "#fff"
}}
>
<Icon name="ios-add-circle" size={70} color="#2F98AE" />
</TouchableOpacity>
</View>
);
}
}
export default GalleryScreen;
First, create a merging function that manipulate the arrays with duplicates by key (in your case uri field)
function extractUniques(key, array) {
const dic = {}
for (const item of array) {
dic[item[key]] = item
}
return Object.values(dic)
}
Now use it where it hearts
provider.update({
Images: extractUniques('uri', [...this.state.images, ...imagesArray]) // Here is the issue
})
Related
Im working on a react-native project and what I'm trying to do is for the user to have the possibility to select phone numbers in his contact list.
When the user selects one or more contacts, the app won't work, and it shows this error on the console: VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc.
ContactList.js
unction ContactList() {
const [refreshing, setRefreshing] = React.useState(false);
const [itemChecked, setItemChecked] = useState([]);
const [checked, setChecked] = useState(new Map());
const [contacts, setContacts] = useState([]);
const [filter, setFilter] = useState([]);
const [search, setSearch] = useState('');
const [data, setData] = useState(filter)
useEffect(() => {
(async () => {
const { status } = await Contacts.requestPermissionsAsync();
if (status === 'granted') {
const { data } = await Contacts.getContactsAsync({
fields: [Contacts.Fields.PhoneNumbers],
// fields: [Contacts.Fields.Name],
});
if (data.length > 0) {
setContacts(data);
setFilter(data);
// console.log('contact', contacts[1]);
// console.log('filter', filter);
}
}
})();
}, []);
const searchFilter = (text) => {
if (text) {
const newData = contacts.filter((item) => {
const itemData = item.name ? item.name.toUpperCase() : ''.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
setFilter(newData);
setSearch(text);
} else {
setFilter(contacts);
setSearch(text);
}
};
const onChangeValue = (item) => {
checked.set(item, true);
};
useEffect(() => {
checked &&
setData((previous) => [...previous, {phone: contacts} ])
}, [checked],
)
const renderItem = ({ item, index }) => {
return (
<SafeAreaView>
<ScrollView>
<TouchableOpacity style={{ flexDirection: 'row', flex: 1 }}>
<View style={{ flex: 1, borderTopWidth: 0.5, borderTopColor: 'grey', marginBottom: 15 }}>
<Text onPress={() => setChecked(true)} style={{ fontSize: 20, marginHorizontal: 10 }}>
{item.name + ' '}
</Text>
<Text style={{ fontSize: 17, marginHorizontal: 10, marginTop: 5, color: 'grey' }}>
{item.phoneNumbers && item.phoneNumbers[0] && item.phoneNumbers[0].number}
</Text>
</View>
<View style={{ flex: 1, borderTopWidth: 0.5, borderTopColor: 'grey' }}>
<CheckBox
style={{ width: 15, height: 15 }}
right={true}
checked={checked.get(index)}
onPress={()=> onChangeValue(index)}
/>
</View>
</TouchableOpacity>
</ScrollView>
</SafeAreaView>
);
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.container}>
<View
style={{
height: 40,
justifyContent: 'center',
backgroundColor: '#EEEEEE',
width: '90%',
marginHorizontal: 20,
marginTop: 15,
borderRadius: 10,
}}
>
<Feather name="search" size={20} color="grey" style={{ position: 'absolute', left: 32 }} />
<TextInput
placeholder="Search"
placeholderTextColor="#949494"
style={{
left: 20,
paddingHorizontal: 35,
fontSize: 20,
}}
value={search}
onChangeText={(text) => {
searchFilter(text);
setSearch(text);
}}
/>
</View>
<FlatList
style={{ marginTop: 15 }}
data={contacts && filter}
keyExtractor={(item) => `key-${item.id.toString()}`}
renderItem={renderItem}
ListEmptyComponent={<Text message="No contacts found." />}
/>
</View>
</SafeAreaView>
);
}
export default ContactList;
How can I solve this bug?
I have an image array where I am updating each value when I click on a button in react and pick an image. However the array is constantly rerendering when I only want it to when I pick an image.
Here is my code:
function OnboardingUploadPhotos() {
const [modalVisible, setModalVisible] = useState(false);
const [permissionStatus, setPermissionStatus] = useState('');
const [files, setFiles] = useState<string[]>([]);
const [fileArray, setFileArray] = useState<string[]>(['', '', '', '', '', '', '']);
const setPermissions = useCallback(val => {
setPermissionStatus(val);
}, [setPermissionStatus]);
const setIsModalVisible = useCallback(val => {
setModalVisible(val);
}, [setModalVisible]);
async function fetchAssets() {
const recentCameraRoll = await MediaLibrary.getAssetsAsync({first: 11});
setFiles(recentCameraRoll.assets.slice(1).map(file => file.uri));
}
function replaceFileState(file: string, index: number) {
let f = [...fileArray];
f[index] = file;
setFileArray(f);
}
useEffect(() => {
fetchAssets();
}, [fetchAssets]);
return (
<View>
{permissionStatus ?
<Modal style={styles.bottomModalView} isVisible={modalVisible} backdropOpacity={0}
onBackdropPress={() => setModalVisible(false)}>
<View style={styles.modal}>
<TouchableOpacity>
<Text style={{
borderBottomWidth: 1,
borderBottomColor: '#FFF',
color: '#FFF',
textDecorationLine: 'underline',
alignSelf: 'flex-end',
justifyContent: 'center',
paddingTop: 40
}}>All photos</Text>
</TouchableOpacity>
<ScrollView horizontal={true} scrollEnabled={true}
contentContainerStyle={{justifyContent: 'center', alignItems: 'center'}}>
{files.map((file, index) => {
return (
<TouchableWithoutFeedback key={index} onPress={() => replaceFileState(file, index)}>
<Image
key={file}
style={{width: 100, height: 100, marginLeft: 10, marginRight: 10, borderRadius: 4}}
source={{uri: file}}
/>
</TouchableWithoutFeedback>
);
})}
</ScrollView>
<Text style={{fontSize: 22, color: '#FFF', marginLeft: 20, marginBottom: 20}}>Your photos</Text>
</View>
</Modal> :
null
}
<Text>Upload your photos</Text>
<View style={{flexDirection: "row", flexWrap: "wrap", justifyContent: 'space-evenly'}}>
{fileArray.slice(0,6).map((image, index) => {
console.log(fileArray)
return (
image != '' ?
<Image
key={image}
style={{width: 100, height: 100, borderRadius: 100}}
source={{uri: image}}
/> :
<OnboardingPhoto key={index} setStatus={setPermissions} setModal={setIsModalVisible}/>
)
})}
</View>
</View>
);
}
It is a modal that has all the users latest images and on click of each image it replaces the empty placeholder image with the users image on the screen. However it constantly rerenders. Id say put it in a useEffect but I do not know where! Any help would be great, thanks!
I think you need to define a key property on the TouchableWithoutFeedback component.
See the guidelines here: https://reactjs.org/docs/lists-and-keys.html
Hooks are ment to be used inside Function Components, that's maybe one of the reasons.
Hello I've been looking through several threads on stackoverflow but I haven't been able to solve my problem. I have an app where you can save movies to a watchlist. On this specific screen I want to display a users watchlist and give them the ability to delete it from the list. Currently the function is indeed deleting the movie from the list and removing it from firebase but i can't get my screen to rerender to visually represent the deletion.
This is the code right now:
export default function MovieWatchlistTab(props: any) {
let { movies } = props;
let movieWatchlist: any[] = [];
const [watchlistSnapshot, setWatchlistSnapshot] = useState();
const user: firebase.User = auth().currentUser;
const { email } = user;
const watchlistRef = firestore().collection("Watchlist");
useEffect(() => {
getWatchlistSnapshot();
}, []);
const getWatchlistSnapshot = async () => {
setWatchlistSnapshot(await watchlistRef.where("userId", "==", email).get());
};
const convertDataToArray = () => {
const convertedMovieList = [];
for (let movie in movies) {
let newMovie = {
backdrop: movies[movie].backdrop,
overview: movies[movie].overview,
release: movies[movie].release,
title: movies[movie].title,
};
convertedMovieList.push(newMovie);
}
movieWatchlist = convertedMovieList;
};
const renderMovieList = () => {
convertDataToArray();
return movieWatchlist.map((m) => {
const handleOnPressDelete = () => {
const documentRef = watchlistRef.doc(watchlistSnapshot.docs[0].id);
const FieldValue = firestore.FieldValue;
documentRef.set(
{
movies: {
[m.title]: FieldValue.delete(),
},
},
{ merge: true }
);
movieWatchlist.splice(
movieWatchlist.indexOf(m),
movieWatchlist.indexOf(m) + 1
);
console.log("movieWatchlist", movieWatchlist);
};
const swipeButtons = [
{
text: "Delete",
color: "white",
backgroundColor: "#b9042c",
onPress: handleOnPressDelete,
},
];
return (
<Swipeout right={swipeButtons} backgroundColor={"#18181b"}>
<View key={m.title} style={{ marginTop: 10, flexDirection: "row" }}>
<Image
style={{ height: 113, width: 150 }}
source={{
uri: m.backdrop,
}}
/>
<View>
<Text
style={{
flex: 1,
color: "white",
marginLeft: 10,
fontSize: 17,
}}
>
{m.title}
</Text>
<Text style={{ flex: 1, color: "white", marginLeft: 10 }}>
Release: {m.release}
</Text>
</View>
</View>
</Swipeout>
);
});
};
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#18181b",
}}
>
<ScrollView
style={{ flex: 1 }}
contentContainerStyle={{
width: Dimensions.get("window").width,
}}
>
{renderMovieList()}
</ScrollView>
</View>
);
}
I've been trying to play around with useStates and I think the answer is in that direction but I just can't seem to get it to work anyway. Any help would be appreciated!
There are a few lines in your code that show a misunderstand of React state. You have a value let movieWatchlist: any[] = []; that you reassign in convertDataToArray() and mutate in handleOnPressDelete. That's just not how we do things in React and it's not going to trigger updates properly. movieWatchlist either needs to be a stateful variable created with useState.
Do the movies passed in through props change? If they do, then you don't need to store them in state here. You could just return an array from convertDataToArray() rather than setting a variable and returning void.
To be honest it's really not clear what convertDataToArray is even doing as it seems like newMovie is either the same or a subset of the original movie object. If the point is to remove the other properties aside from these four, that's not actually needed. If the prop movies is already an array, just delete this whole function and use movies directly. If it's a keyed object, use Object.values(movies) to get it as an array.
I'm thoroughly confused as to what we are getting from props and what we are getting from firebase. It seems like we would want to update the snapshot state after deletion, but you only run your useEffect once on mount.
You may still have errors, but this code should be an improvement:
interface Movie {
backdrop: string;
overview: string;
release: string;
title: string;
}
const MovieThumbnail = (props: Movie) => (
<View key={props.title} style={{ marginTop: 10, flexDirection: "row" }}>
<Image
style={{ height: 113, width: 150 }}
source={{
uri: props.backdrop
}}
/>
<View>
<Text
style={{
flex: 1,
color: "white",
marginLeft: 10,
fontSize: 17
}}
>
{props.title}
</Text>
<Text style={{ flex: 1, color: "white", marginLeft: 10 }}>
Release: {props.release}
</Text>
</View>
</View>
);
export default function MovieWatchlistTab() {
const [watchlistSnapshot, setWatchlistSnapshot] = useState<DocumentSnapshot>();
const user: firebase.User = auth().currentUser;
const { email } = user;
const watchlistRef = firestore().collection("Watchlist");
const getWatchlistSnapshot = async () => {
const results = await watchlistRef.where("userId", "==", email).get();
setWatchlistSnapshot(results.docs[0]);
};
useEffect(() => {
getWatchlistSnapshot();
}, []);
const deleteMovie = async (title: string) => {
if ( ! watchlistSnapshot ) return;
const documentRef = watchlistRef.doc(watchlistSnapshot.id);
const FieldValue = firestore.FieldValue;
await documentRef.set(
{
movies: {
[title]: FieldValue.delete()
}
},
{ merge: true }
);
// reload watch list
getWatchlistSnapshot();
};
// is this right? I'm just guessing
const movies = ( watchlistSnapshot ? watchlistSnapshot.data().movies : []) as Movie[];
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#18181b"
}}
>
<ScrollView
style={{ flex: 1 }}
contentContainerStyle={{
width: Dimensions.get("window").width
}}
>
{movies.map((m) => (
<Swipeout
right={[
{
text: "Delete",
color: "white",
backgroundColor: "#b9042c",
// need to pass the title to the delete handler
onPress: () => deleteMovie(m.title)
}
]}
backgroundColor={"#18181b"}
>
<MovieThumbnail {...m} />
</Swipeout>
))}
</ScrollView>
</View>
);
}
At my example, the function “getData” loading my data, but after the loading, I try to print and show the total sum of the objects that came from JSON in a footer at the bottom of the screen.
and I don't really know how to do it.
I don't understand how to solve this issue coz I have tried many ways.
This is my example:
export default class MainScreen extends Component {
constructor(props) {
super(props);
this.state = { data: [] };
}
getData = () => {
this.setState({ isLoading: true })
axios.get("https://rallycoding.herokuapp.com/api/music_albums")
.then(res => {
this.setState({
isLoading: false,
data: res.data
});
console.log(res.data);
});
}
componentDidMount() {
this.props.navigation.setParams({getData: this.getData}); //Here I set the function to parameter
this.getData()
}
renderItem(item) {
const { title, artist} = item.item;
return (
<TouchableOpacity
onPress={() => this.props.navigation.navigate("Settings")}
>
<Card
containerStyle={{
borderColor: "black",
padding: 20,
height: 100,
backgroundColor: "#e6e6ff",
borderBottomEndRadius: 10,
borderTopRightRadius: 10,
borderBottomStartRadius: 10,
}}
>
<View
style={{
paddingVertical: 15,
paddingHorizontal: 10,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center"
}}
>
<Icon name="chevron-right" size={30} color={"grey"} justifyContent={"space-between"} />
<Text style={styles.name}>
{title+ " " + artist}
</Text>
{/* <Text style={styles.vertical} numberOfLines={2}></Text> */}
</View>
</Card>
</TouchableOpacity>
);
}
render() {
if (this.state.isLoading) {
return (
<View style={{ flex: 1, paddingTop: 230 }}>
<Text
style={{ alignSelf: "center", fontWeight: "bold", fontSize: 20 }}
>
loading data...
</Text>
<ActivityIndicator size={'large'} color={'#08cbfc'} />
</View>
);
}
return (
<View style={styles.container}>
<FlatList
data={this.state.data}
renderItem={this.renderItem.bind(this)}
keyExtractor={item => item.id}
/>
</View>
);
}
}
/////////////////////////////////////////////////////////
MainScreen.navigationOptions = navData => {
return {
headerTitle: 'melon',
headerRight: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title=**"sync button"**
iconName={Platform.OS === "android" ? "md-sync" : "ios-sync"}
onPress={() => {
navData.navigation.navigate("getData");
}}
/>
</HeaderButtons>
)
};
};
If type of data is array you can get total number of elements by this.state.data.length
If type of data is object you can get total number of elements by Object.keys(data).length
I can't figure out what I am doing wrong here.
I added a searchbar but when I write something in the textfield it gives me the error: "undefined is not a function (near '... this.state.books.filter...')"
Below is some of my code.
What am I doing wrong?
state = {
books: {},
};
componentDidMount() {
firebase
.database()
.ref('/Books')
.on('value', snapshot => {
this.setState({ books: snapshot.val() });
});
}
searchBooks = value => {
const { books } = this.state;
const bookArray = Object.values(books);
const filteredBooks = bookArray.filter(book => {
let bookLowercase = (book.bookname).toLowerCase();
let searchTermLowercase = value.toLowerCase();
return bookLowercase.indexOf(searchTermLowercase) > -1;
});
this.setState({ books: filteredBooks });
};
render() {
const { books } = this.state;
if (!books) {
return null;
}
const bookArray = Object.values(books);
const bookKeys = Object.keys(books);
return (
<View style={styles.container}>
{/*Searchbar */}
<View style={{ height: 80, backgroundColor: '#c45653', justifyContent: "center", paddingHorizontal: 5 }}>
<View style={{ height: 50, backgroundColor: 'white', flexDirection: 'row', padding: 5, alignItems: 'center' }}>
<TextInput
style={{
fontSize: 24,
marginLeft: 15
}}
placeholder="Search"
onChangeText={value => this.searchBooks(value)}
/>
</View>
</View>
<FlatList
style={{ backgroundColor: this.state.searchBarFocused ? 'rgba(0,0,0,0.3)' : 'white' }}
data={bookArray}
keyExtractor={(item, index) => bookKeys[index]}
renderItem={({ item, index }) => (
<BookListItem
book={item}
id={bookKeys[index]}
onSelect={this.handleSelectBook}
/>
)}
/>
</View>
);
You're initializing this.state.books as an Object.
state = {
books: {},
};
Objects have no filter function. You probably want to use an Array instead.
state = {
books: [],
};
Then you'll be able to use this.state.books.filter() with the Array's filter function.