How to pass array of objects to another screen and display them react native - javascript

How do i pass the data of selected checkboxes to the previous screen in react native.
The following is what i have done so far:
This is my SelectProducts screen
import React, {useState, useEffect} from 'react'
import { StyleSheet, Text, View, Alert, Image, ScrollView, TouchableOpacity } from 'react-native';
import Checkbox from 'expo-checkbox';
const SelectProducts = ({route}) => {
const [checkedBox, setCheckedBox] = useState([]);
const [selectedBoxesItem, setSelectedBoxesItem] = useState([]);
const [itemList, setItemList] = useState([]);
const includeSelectedItem = (item, index) => {
const newCheckedBox = [...checkedBox];
newCheckedBox[index] = !newCheckedBox[index];
setCheckedBox(newCheckedBox);
setSelectedBoxesItem({
selectedUniqueKey: item.id,
selectedItemName: item.ad_headline,
selectedItemPrice: item.ad_price,
selectedItemPic: item.ad_picture
});
}
This is the function that I'm using to send the data to the RecordASale screen after clicking on the Done button that is below the list of checkboxes.
const handleSelectedSubmit = () => {
navigation.navigate({
name: 'RecordASale',
params: {
post: [selectedBoxesItem],
},
merge: true,
})
}
And this is the checkbox:
return (
{itemList.map((item, index) => (
<DataTable.Row>
<DataTable.Cell>
<View style={styles.checkboxContainer}>
<Checkbox
key={item.id}
value={checkedBox[index]}
onValueChange={() => includeSelectedItem(item, index)}
color={checkedBox ? '#800080' : undefined}
style={styles.checkbox}
/>
</View>
</DataTable.Cell>
<DataTable.Cell>
<Image source = {{uri: "https://cdn.beraty.com/beraty-ads/"+item.ad_picture}} style = {{ width: 30, height: 30 }} />
</DataTable.Cell>
<DataTable.Cell>{item.ad_headline}</DataTable.Cell>
<DataTable.Cell>{item.ad_price}</DataTable.Cell>
</DataTable.Row>
))}
<View style = {styles.submitButton}>
<Text style = {styles.submitButtonText} onPress={() => handleSelectedSubmit()}>Done</Text>
</View>
</DataTable>
);
}
What i want to achieve is to get the following details for every checkbox selected:
item.id,
item.ad_headline,
item.ad_price,
item.ad_picture
All the above data should be passed from this SelectProducts screen to RecordASale screen
To my own understanding, what I did was that I passed objects to the function of the state below:
const [selectedBoxesItem, setSelectedBoxesItem] = useState([]);
setSelectedBoxesItem({
selectedUniqueKey: item.id,
selectedItemName: item.ad_headline,
selectedItemPrice: item.ad_price,
selectedItemPic: item.ad_picture
});
So when i did this, I only get the last selected checkbox details passed to the RecordASale screen even though i selected more than one checkbox.
This is how i'm getting the details into the RecordASale screen:
const RecordASale = ({route}) => {
return (
{(route.params?.post) ? route.params?.post.map((item, index) => (
<View>
<View key={index}>
<Image source = {{uri: "https://cdn.beraty.com/beraty-ads/"+item.selectedItemPic}} style = {{ width: 30, height: 30 }} />
<Text>{item.selectedItemName}</Text>
<Text>{item.selectedItemPrice}</Text>
</View>
</View>
)) : <Text></Text>}
);
}
I want the same details as above for all selected checboxes to be passed to the other screen, and not just one.
I believe I'm quite close to that but some things are missing in my code. Please help. Thanks.
I only shared the parts I'm having problems with.

You can use useRoute hook
const SelectProducts = ({
route
}) => {
const routes = useRoute();
console.log(routes.params ? .post)
}

Related

React Native Firebase Firestore data fetching with empty spaces

I have created 3 documents in Firestore database, each document has different data.
but When I add different data on different document ids then I am getting blank spaces, and those spaces are generated automatically for other document ids which I already created previously.
Document 1 should be shown on First screen and document 2 should show on screen two. I mean each document's data should show on its own screen. please check the image link below-
First Screen
import React, { useState, useEffect } from 'react';
import {View, Button, Text, FlatList, StyleSheet, Pressable, TouchableOpacity} from 'react-native'
import {firebase} from '../config';
const Testing = ({ navigation }) =>{
const [users, setUsers] = useState([]);
const todoRef = firebase.firestore().collection('testing');
useEffect(() => {
todoRef.onSnapshot(
querySnapshot => {
const users = []
querySnapshot.forEach((doc) => {
const { One, Two, Three
} = doc.data()
users.push({
id: doc.id,
One, Two, Three
})
})
setUsers(users)
}
)
}, [])
return (
<View style={{ flex:1,}}>
<FlatList
style={{height: '100%'}}
data={users}
numColumns={1}
renderItem={({item}) => (
<Pressable >
<View style={styles.viewOne}>
<View>
<Text style={[styles.card, styles.title]}>{item.One}</Text>
<Text style={styles.text}>{item.Two}</Text>
<Text style={styles.text}>{item.Three}</Text>
</View>
</View>
</Pressable>
)} />
</View>
);}
export default Testing;
*Second Screen*
import React, { useState, useEffect } from 'react';
import {View, Button, Text, FlatList, StyleSheet, Pressable, TouchableOpacity} from 'react-native'
import {firebase} from '../config';
const TestingDocs = ({ navigation }) =>{
const [users, setUsers] = useState([]);
const todoRef = firebase.firestore().collection('testing');
useEffect(() => {
todoRef.onSnapshot(
querySnapshot => {
const users = []
querySnapshot.forEach((doc) => {
const { DocsOne, DocsTwo, DocsThree,
} = doc.data()
users.push({
id: doc.id,
DocsOne, DocsTwo, DocsThree,
})
})
setUsers(users)
}
)
}, [])
return (
<View style={{ flex:1,}}>
<FlatList
style={{height: '100%'}}
data={users}
numColumns={1}
renderItem={({item}) => (
<Pressable >
<View style={styles.viewOne}>
<View>
<Text style={[styles.card, styles.title]}>{item.DocsOne}</Text>
<Text style={styles.text}>{item.DocsTwo}</Text>
<Text style={styles.text}>{item.DocsThree}</Text>
</View>
</View>
</Pressable>
)} />
</View>
);}
export default TestingDocs;
You must have seen this Answer on your another Post :
It looks like you've incomplete or "optional" data in your backend. If
you don't want to render empty fields you can conditionally render
them.
For the users data that is missing both properties you can filter
the data prop.
Example:
<FlatList
data={users.filter(({ One, five }) => One || five)}
renderItem={({ item }) => (
<View style={{ .... }}>
{item.One && <Text>ID: {item.One}</Text>}
{item.five && <Text>Name: {item.five}</Text>}
</View>
)}
/>
You can also refer to this Answer:
Its as a result of performance issues with the FlatList component but
you can add the following props to your FlatList Component, it would
help solve the problem. They are:
i. removeClippedSubviews.
Set this to true, it comes with a default of false.
ii. windowSize. Set this to a number like say 30
iii. maxToRenderPerBatch. This controls the number of items rendered per batch, which is the next chunk of items rendered on every
scroll.
<FlatList
data={this.state.contacts}
removeClippedSubviews={true}
maxToRenderPerBatch={60}
windowSize={30}
ListHeaderComponent={headerComponent}
contentContainerStyle={{ paddingBottom: 10 }}
renderItem={({ item }) => (
<View>
{item}
</View>
)}
numColumns={1}
keyExtractor={(item, index) => String(index)}
/>
For more information, you can refer to the blog which explains how to use react hooks with firebase firestore.

How to push data in the setState of array-type state?

I am having a state data. I wanted to push a new entry of the form
{ task:'ANy task',key:Math.random().toString() }
in the data array while using setData.
I had tried many ways mentioned here, but don't klnow why its not working.
Here's my code.
import React, { useState } from "react";
import { StyleSheet, Text, View, TextInput, Button } from "react-native";
import { StatusBar } from "expo-status-bar";
const Addtask = ({navigation}) => {
const [data,setData] = useState([]);
console.log("from add = ",data)
const [task, setTask] = useState("");
const handleSubmit = () => {
console.log("submit pressed for task = ", task)
const updatedData = [...data,{
task:task,
key: Math.random().toString(),
}]
//here i am setting the data
setData(prevState => [...prevState,updatedData]);
console.log("data after adding task",data)
navigation.navigate("Tasklist",{data:data})
}
return (
<View style={styles.container}>
<StatusBar style="light" backgroundColor="midnightblue" />
<View>
<Text style={styles.text}>Add Task Here</Text>
</View>
<View>
<TextInput
style={styles.input}
onChangeText={setTask}
value={task}
onChange={setTask}
placeholder="Type your task"
keyboardType="ascii-capable"
/>
</View>
<View style={styles.buttoms}>
<View style={{margin:4}}>
<Button color={'red'} onPress={()=>{navigation.goBack()}} title="Cancel"></Button>
</View>
<View style={{margin:4}}>
<Button color={'lightblue'} onPress={()=>setTask('')} title="Clear"></Button>
</View>
<View style={{margin:4}}>
<Button color={'green'} onPress={handleSubmit} title="Save"></Button>
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
.
.
.
});
export default Addtask;
To debug, I had used console stmts which reveals that the task value is coming in the handleSubmit() correctly but it is not getting pushed.
Log
submit pressed for task = Morning bike ride.
data after adding task Array []
Because You are already have
const updatedData = [...data,{
task:task,
key: Math.random().toString(),
}]
You don't need setData(prevState => [...prevState,updatedData]) you can just assign the updatedData to setData like setData(updatedData)
You can use
setData(current => [...current, {
task:task,
key: Math.random().toString(),
}])
Then you don't need updatedData

Expo React Native, How to pick default local images or user image obtained from expo image picker

I have a form that a user can select a default local image or an image from the user's photo library
Here is an expo snack use android the images can be found in the phone menu in photos
I want to save either the default local image or user's image to the form and to redux, currently able to save default images picked to form and redux.
This is what currently works.
I have a component that gets a selected local image and returns an image path witch is a number. That local image gets saved in form and in redux. currently, the user can change the local image in the form.
ImgSelector Component:
import React, { useState } from "react";
import { List, Selector, View, SelectedImg } from "./styles";
import { FlatList } from "react-native";
import { defaultImages } from "../../data/defaultImages";
const FlatlistItem = ({ image, setImg }) => {
return (
<Selector onPress={() => setImg(image)}>
<View>
<SelectedImg source={image} />
</View>
</Selector>
);
};
const ImgSelector = ({ setImg }) => {
const [selectedId, setSelectedId] = useState(null);
const renderItem = ({ item }) => (
<FlatlistItem setImg={setImg} image={item.image} />
);
return (
<View>
<FlatList
horizontal
data={defaultImages}
renderItem={renderItem}
keyExtractor={(item, index) => index.toString()}
extraData={selectedId}
/>
</View>
);
};
export default ImgSelector;
Default local images are stored like this and the path is the index which is a number this part works fine.
export const defaultImages = [
{
id: “2”,
image: require("../assets/images/singlepane.png"),
}
]
I have an imagePicker component that asks for permissions and returns a uri string that looks like this:
file:/data/data/host.exp.exponent/cache/ExperienceData/%2540anonymous%252FExpoWcPro-a828b17b-dcd7-4a04-93ca-657c8e4e511d/ImagePicker/6106d73f-c886-457d-abe9-1f1232a0d398.jpg
My form component where images are picked and saved:
import React, { useState } from "react";
import { Image } from "react-native";
const CounterForm = ({ navigation, ...props }) => {
// This is current state for default images that works
const [imgUrl, setImgUrl] = useState(props.imgUrl || defaultImage);
const [userImgUri, setUserImgUri] = useState(null);
// This gets the local image from a componnet
const handleEditImg = (newImgUrl) => {
setImgUrl(newImgUrl);
};
// This gets image uri from expo image picker
const handelUserImg = (userUri) => {
setUserImgUri(userUri);
};
// This sends data to a redux action to save
const handleSubmit = () => {
props.onFormSubmit({
id: props.id,
imgUrl,
});
setImgUrl(defaultImage);
};
return (
<FormWrapper>
<Row>
<FormButton onPress={() => handleSubmit()}>
<StyledText title="Save" color={COLORS.appBlue} />
</FormButton>
</Row>
<TopContent>
{/* I tried this to get user image and displays in form */}
<Image
source={{ uri: userImgUri }}
style={{ width: 100, height: 100 }}
/>
{/* This current implementation gets local images
<Image
source={imgUrl}
style={{ width: 100, height: 100 }}
/> */}
{/* I tried this only gets local images
{imgUrl ? (
<Image source={imgUrl} style={{ width: 100, height: 100 }} />
) : (
<Image
source={{ uri: userImgUri }}
style={{ width: 100, height: 100 }}
/>
)} */}
</TopContent>
<Row>
<ImagePicker getUserImg={handelUserImg} />
</Row>
<View>
<ImgSelector setImg={handleEditImg} />
</View>
</FormWrapper>
);
};
export default CounterForm;
if you use the last sdk version of Expo (40) and the right package expo-image-picker you need to follow this instructions.
First you need to ask for permissions :
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (status !== 'granted') {
alert('Sorry, we need camera roll permissions to make this work!');
}
And then call method to select image from library :
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
So you got image uri by accessing result.uri, you need to save this value (e.g in user store) and display it by selecting your store or default value if there is not stored value :
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button title="Pick an image from camera roll" onPress={pickImage} />
/** imgUrl = stored image uri, defaultImages[0].image = default image uri */
<Image source={imgUrl ? { uri: imgUrl } : defaultImages[0].image} />
</View>
I found the answer it's updated in expo snack
import React, { useState } from "react";
import { List, Selector, View, SelectedImg } from "./styles";
import { FlatList } from "react-native";
import { defaultImages } from "../data";
const FlatlistItem = ({ image, setImg }) => {
return (
<Selector onPress={() => setImg(image)}>
<View>
<SelectedImg source={{uri: image}} />
</View>
</Selector>
);
};
const ImgSelector = ({ setImg }) => {
const [selectedId, setSelectedId] = useState(null);
const renderItem = ({ item }) => {
return (
<FlatlistItem setImg={setImg} image={item} />
)
}
return (
<View>
<FlatList
horizontal
data={defaultImages}
renderItem={renderItem}
keyExtractor={(item, index) => index.toString()}
extraData={selectedId}
/>
</View>
);
};
export default ImgSelector;
Form
import React, { useState } from "react";
import {Asset} from 'expo-asset';
import StyledText from "../UiComponents/StyledText";
import { TouchableWithoutFeedback, Keyboard } from "react-native";
import {
FormWrapper,
TextInputWrapper,
TopContent,
NumberWrapper,
Row,
FormButton,
View,
} from "./styles";
import StyledInput from "../UiComponents/StyledInput";
const defaultImage = Asset.fromModule(require('../assets/komloy.jpg')).uri
import WindowSelector from "../ImgSelector";
import StyledButton from "../UiComponents/StyledButton";
import ImagePicker from "../components/imagePicker";
import { Image } from "react-native";
const DismissKeyboard = ({ children }) => (
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
{children}
</TouchableWithoutFeedback>
);
const CounterForm = ({ navigation, ...props }) => {
const [imgUrl, setImgUrl] = useState(props.imgUrl || defaultImage);
const handleEditImg = (newImgUrl) => {
setImgUrl(newImgUrl);
};
const handelUserImg = (userUri) => {
setImgUrl(userUri);
};
const handleSubmit = () => {
props.onFormSubmit({
id: props.id,
imgUrl
});
setImgUrl(defaultImage);
};
return (
<DismissKeyboard>
<FormWrapper>
<TopContent>
<Image
source={{uri: imgUrl}}
style={{ width: 100, height: 100 }}
/>
</TopContent>
<Row>
<StyledText title="Select a image" />
<ImagePicker getUserImg={handelUserImg} />
</Row>
<View>
<WindowSelector setImg={handleEditImg} />
</View>
</FormWrapper>
</DismissKeyboard>
);
};
export default CounterForm;
Data
import {Asset} from 'expo-asset';
const imageURI = Asset.fromModule(require('./assets/islands.jpg')).uri
const imageURI2 = Asset.fromModule(require('./assets/maeYai.jpg')).uri
export const defaultImages = [
imageURI, imageURI2
]

TypeError: undefined is not an object (evaluating ' _this.setState')

i'm a complete beginner to React Native and i'm trying to add items to flatlist using textinput. i keep constantly getting a TypeError: undefined is not an object (evaluating'_this.setState'). i have edited the code many times and tried to research what i can do, but it still is not working properly. could someone help me and tell me what i need to change? below is my code.
thank you in advance!
import React, { useState, setState } from 'react';
import { View, Text, StyleSheet, FlatList, Alert, TouchableOpacity, TextInput } from 'react-native';
export default function FlatlistComponent({ }) {
const array = [{title: 'ONE'}, {title: 'TWO'}];
const [arrayHolder] = React.useState([]);
const [textInputHolder] = React.useState('');
const componentDidMount = () => {
setState({ arrayHolder: [...array] })
}
const joinData = () => {
array.push({ title : textInputHolder });
this.setState({ arrayHolder: [...array] });
}
const FlatListItemSeparator = () => {
return (
<View
style={{
height: 1,
width: "100%",
backgroundColor: "#607D8B",
}} />
);
}
const GetItem = (item) => {
Alert.alert(item);
}
return (
<View style={styles.MainContainer}>
<TextInput
placeholder="Enter Value Here"
onChangeText={data => this.setState({ textInputHolder: data })}
style={styles.textInputStyle}
underlineColorAndroid='transparent'
/>
<TouchableOpacity onPress={joinData} activeOpacity={0.7} style={styles.button} >
<Text style={styles.buttonText}> Add Values To FlatList </Text>
</TouchableOpacity>
<FlatList
data={arrayHolder}
width='100%'
extraData={arrayHolder}
keyExtractor={(index) => index.toString()}
ItemSeparatorComponent={FlatListItemSeparator}
renderItem={({ item }) => <Text style={styles.item} onPress={GetItem.bind(this, item.title)} > {item.title} </Text>}
/>
</View>
);
}
So I see you're trying to use functional components here.
State variables can be rewritten like this
const [arrayHolder, setArrayHolder] = useState([]);
const [textInputHolder, setTextInputHolder] = useState('');
componentDidMount is used in class components and can be rewritten like this for functional components
import React, { useState, useEffect } from 'react';
useEffect(()=>{
setArrayHolder(array)
}, [])
Function joinData can be re-written like this.
const joinData = () => {
array.push({ title : textInputHolder });
setArrayHolder(array)
}
About the text not showing up. You're using this.setState in the onChangeText event. It is a functional component and this won't work in a functional component.state variables are declared and set using the useState hook in a functional component.
You should rewrite the onChangeText event like this.
<TextInput
placeholder="Enter Value Here"
onChangeText={data => setTextInputHolder(data)}
style={styles.textInputStyle}
underlineColorAndroid='transparent'
/>
I think this'll solve your problem

Conditional rendering with React Hooks : loading

I am learning how to use React Hooks and have been stuck for many hours on something that's supposed to be very simple.
I am trying to display a a text if the state variable "loading" is true. If it's false, I want to display something else.
No matter what I try, "loading" is always false or at least, the UI does not appear to reflect its value.
here is the code:
import React, {useState, useEffect} from 'react';
import {View, SafeAreaView, Text} from 'react-native';
const testScreen= (props) => {
const [loading, setLoading ] = useState(true);
useEffect(() => {
setLoading(false);
}, []);
if(loading)
{
return <Text>Hi</Text>;
}
else
{
return<Text.Hey</Text>
}
}
export default testScreen;
Any help will be more than welcome and I am sorry if this is very basic.
UPDATE: Here is the actual code I am working with. SetLoading is supposed to update the state variable to false but never does or at least, the UI des not render.
import React, {useState, useEffect} from 'react';
import {View, SafeAreaView, Text, ActivityIndicator} from 'react-native';
import CategoryTag from '../Components/CategoryTag';
import firestore from '#react-native-firebase/firestore';
const CategoryScreen = (props) => {
const topicCollection = firestore().collection('Topics')
.where("active","==",true);
//hooks for topics
const [topics,setTopics] = useState([]);
const [loading, setLoading ] = useState(true);
//get all active topics
useEffect(() => {
return topicCollection.onSnapshot(querySnapshot => {
const list = [];
querySnapshot.forEach(doc => {
const { active, name } = doc.data();
list.push({
id: doc.id,
active,
name,
});
});
setTopics(list);
setLoading(false);
});
}, []);
const renderTopics = () =>{
return(
topics.map((item) =>{
return(
<CategoryTag key = {item.id}
color={userTopics.includes(item.name) ?"#51c0cc":"#303239"}
name = {item.name}
isSelected = {userTopics.includes(item.name)}
handleClick = {addTopicToUserTopicCollection}
/>
)
})
)
}
if(loading)
{
return (
<SafeAreaView style={{flex:1, backgroundColor:"#455a65"}}>
<View style={{width:200, padding:20, paddingTop:60}}>
<Text style ={{fontSize:25, fontWeight:"bold",
color:"#fff"}}>What are you</Text>
<Text style ={{fontSize:22, color:"#fff"}}>interested in?
</Text>
</View>
<View style={{flex:1, alignItems:"center",
justifyContent:"center", alignSelf:"center"}}>
<ActivityIndicator />
</View>
</SafeAreaView>
)
}
else // this never runs
{
return (
<SafeAreaView style={{flex:1, backgroundColor:"#455a65"}}>
<View>
<View style={{width:200, padding:20, paddingTop:60}}>
<Text style ={{fontSize:25, fontWeight:"bold",
color:"#fff"}}>What are you</Text>
<Text style ={{fontSize:22, color:"#fff"}}>interested in?
</Text>
</View>
<View style ={{flexDirection:"column", paddingTop:20}}>
<View style ={{padding:15, paddingTop:15,
marginBottom:15,
flexWrap:"wrap", flexDirection:"row"}}>
{renderTopics(topics)}
</View>
</View>
</View>
</SafeAreaView>
);
}
}
export default CategoryScreen;
You are immediately setting your setLoading state to false and therefore loading text might be rendering for fraction of second, or not at all, like a glitch. Try setting setLoading with a timeout and then you will see the intended behaviour.
const TestScreen= (props) => {
const [loading, setLoading ] = useState(true);
useEffect(() => {
setTimeout(()=>setLoading(false), 3000);
}, []);
if(loading)
{
return <Text>Hi</Text>;
}
else
{
return<Text>hey</Text>
}
}

Categories