remove duplicate error from JSON value React Native - react-native

I am trying to pass filtered value from JSON to the parent component, however I've tried using Set but seems the output is still the same. The component that I'm using to render the JSON is picker from native-base. I want to filter out the repeated value in my picker. Greatly appreciated if anyone can help me.
enter image description here
Here's my code.
Picker.js
const DefaultPicker = ({labelItem, pickerWidth, onHandleValue, ...rest}) => {
const context = useContext(WindowContext);
const [selectedValue, setSelectedValue] = useState('-Select-');
const [data, setData] = useState([]);
const {user, setUser} = useContext(AuthContext);
function onNewData() {
if (user) {
user.getIdToken().then((idToken) => {
Axios.get('URL_ENDPOINT', {
headers: {
Authorization: 'Bearer' + idToken,
},
})
.then(({data}) => {
setData(data.features);
// console.log(data.features);
})
.catch((error) => {
console.error(error);
});
});
}
}
useEffect(() => {
const form = onNewData(onNewData);
return form;
}, []);
return (
<PickerWrapper>
<PickerItem
width={pickerWidth}
height="60"
mode="dropdown"
selectedValue={selectedValue}
onValueChange={(itemValue, itemIndex) => {
setSelectedValue({itemValue});
}}>
{Array.from(
new Set(
data.map((value, index) => (
<PickerItem.Item
key={index}
label={value.properties[labelItem]}
value={value.properties[labelItem]}
{...rest}
/>
)),
),
)}
</PickerItem>
</PickerWrapper>
);
};
And here is my parent component
SiteData.js
const SiteData = () => {
const [values, setValues] = useState([]);
const onHandleValue = (params) => {
setValues(params);
console.log(params);
};
return (
<ScrollableView>
<DetailContainer>
<DetailWrapper>
<DetailTitle>Site Data</DetailTitle>
<DetailSubtitle marginTop="10">
Insert new data found during your audit or observation session
</DetailSubtitle>
<DetailSubcontainer>
<DefaultPicker
labelItem={'category'} <-- receive value from child
pickerWidth="100%"
onHandleValue={onHandleValue}
/>
</DetailSubcontainer>
</DetailWrapper>
</DetailContainer>
</ScrollableView>
);
};
UPDATE 1:
I'm using the filter() method so i can create a new array but it returns only one value in the picker list.
const indexData = data.filter(
({category}, index) => {
return (
data.findIndex(
(item) =>
item.category === category,
) === index
);
},
);
The output
enter image description here

I fixed my code by adding this on child component
var setObj = new Set();
var result = data.reduce((acc,item)=>{
if(!setObj.has(item.category)){
setObj.add(item.category,item)
acc.push(item)
}
return acc;
},[]);

Related

making several api calls slows down react native app

So I am calling getUserProducts() (to show the updated list) whenever a product is added to the list and whenever a product is deleted from the list. But I've noticed when I add several items to the list through the dropdown, some times the product doesn't show in the list/getUserProducts isn't called (and then if I add another product it'll then show the previous added product) I'm assuming its because I'm calling it every time I add and that's making it slow? Is there a way I can work around this to optimize it?
const App = () => {
const [products, setProducts] = useState<ProductType[] | []>([]);
const [userProducts, setUserProducts] = useState<ProductType[] | []>([]);
const [toggleCheckBox, setToggleCheckBox] = useState(false);
const [value, setValue] = useState(' ');
const [isFocus, setIsFocus] = useState(false);
const [visible, setVisible] = useState(false);
const [productId, setProductId] = useState('');
const [product, setProduct] = useState('');
const [num, setNum] = useState('');
const [amount, setAmount] = useState('');
const submitForm = async () => {
let body;
body = {
product_id: productId,
product: product,
num: num,
amount: amount,
};
const response = await postProduct(body);
if (response == undefined) {
return;
}
};
const getProducts = async () => {
try {
const response = await axios.get('http://192.168.1.32:3000/api/products');
setProducts(response.data);
} catch (error) {
// handle error
alert('no');
}
};
const getUserProducts = async () => {
try {
const response = await axios.get(
'http://192.168.1.32:3000/api/user_products',
);
setUserProducts(response.data);
} catch (error) {
// handle error
alert('no');
}
};
React.useEffect(() => {
getProducts();
getUserProducts();
console.log(userProducts);
}, []);
return (
<>
<Provider>
<Dialog visible={visible} onDismiss={() => setVisible(false)}>
<DialogHeader title="Add to your list" />
<DialogContent>
<Dropdown
style={[styles.dropdown, isFocus && {borderColor: 'blue'}]}
data={products}
search
maxHeight={300}
labelField="product"
valueField="num"
placeholder={!isFocus ? 'Select item' : '...'}
searchPlaceholder="Search..."
value={value}
onFocus={() => setIsFocus(true)}
onBlur={() => setIsFocus(false)}
onChange={item => {
setValue(item.num);
setProductId(item.product_id);
setProduct(item.product);
setNum(item.num);
setIsFocus(false);
}}
/>
<TextInput
label="quantity"
variant="standard"
onChangeText={text => {
setAmount(text);
console.log(text);
}}
/>
</DialogContent>
<DialogActions>
<Button
title="Cancel"
compact
variant="text"
onPress={() => setVisible(false)}
/>
<Button
title="Add"
compact
variant="text"
onPress={() => {
setVisible(false);
submitForm();
console.log('added');
getUserProducts();
}}
/>
</DialogActions>
</Dialog>
{userProducts.length > 0 ? (
userProducts.map(userProduct => (
<ListItem
title={
userProduct.product +
' x' +
userProduct.amount +
' num: ' +
userProduct.num
}
onPress={async () => {
await deleteProduct(userProduct.product_id);
console.log('deleted');
getUserProducts();
ToastAndroid.show('Done', ToastAndroid.SHORT);
}}
trailing={
<CheckBox
disabled={false}
value={toggleCheckBox}
onValueChange={newValue => setToggleCheckBox(newValue)}
/>
}
/>
))
) : (
<Text>Nothing in your list yet</Text>
)}
</Provider>
</>
);
};
export default App;
I'm pretty certain that you aren't experiencing "lag" but race conditions.
See, when you create an item, you call submitForm() and getuserProducts() and both are async functions. Depending on how long the individual requests take, or how their execution gets scheduled getuserProducts() may very well finish before submitForm(). The new data then only reaches the server after you fetched the (not so) new data.
Consider the following code (it's just a simplified version of your app):
import React, { useState } from 'react';
interface ProductType {
id: number;
name: string;
}
export default function NotWorking() {
const [products, setProducts] = useState<ProductType[]>([]);
const createProduct = async () => {
await serverCreateProduct(`product: ${products.length}`);
console.log('product created');
};
const getProducts = async () => {
setProducts(await serverGetProducts());
console.log('products loaded...');
};
return (
<div>
<button
onClick={() => {
createProduct();
getProducts();
}}
>
Add
</button>
<h2>List:</h2>
<ul>
{products.map((product) => (
<li key={String(product.id)}>{product.name}</li>
))}
</ul>
</div>
);
}
const _userProducts: ProductType[] = [];
async function serverGetProducts() {
return new Promise<ProductType[]>((resolve) => {
setTimeout(() => {
resolve([..._userProducts]);
}, 300);
});
}
async function serverCreateProduct(name: string) {
return new Promise<void>((resolve) => {
setTimeout(() => {
_userProducts.push({ id: Math.random(), name });
resolve();
}, 500);
});
}
If you execute it, you will see that getProducts() finishes before createProduct(), so that result cannot include the new data.
You should await both of them, in order to get what you want, for example:
const createProduct = async () => {
await serverCreateProduct(`product: ${products.length}`);
console.log('product created');
setProducts(await serverGetProducts());
console.log('products loaded');
};
// ...
<button onClick={() => createProduct()}>Add</button>
See the code working here.

Display all posts from database

I have a Firestore collection, schemed as follows:
posts{
uid{
userPosts{
postID{
creation:
postText:
}
}
}
}
I want to display all of the posts, so I've made the corresponding queries and saved them in posts - an array of all the posts that I later iterate through.
The problem with the way I do it is that it keeps adding the same posts every render. So I've tried to set the array each time, but that way the code never passes through these posts && posts.length > 0 condition.
I'm really new to RN and JS in general, but what I was expecting is
Nothing to show here
at first, and then the list of posts.
The complete component:
import { Text, Pressable, FlatList, SafeAreaView } from "react-native";
import { globalStyles } from "../../styles/global";
import React, { useState, useEffect } from "react";
import { db } from "../../../firebase";
import Post from "../../API/Post";
import { collection, getDocs } from "firebase/firestore";
const FeedScreen = ({ navigation }) => {
const [posts, setPosts] = useState([]);
useEffect(() => {
const getPostData = async () => {
setPosts([]); // ---> Without this line the posts keeps adding each render
const q = collection(db, "posts");
const docSnap = await getDocs(q);
docSnap.docs.map(async (item) => {
const tmp = collection(db, "posts", item.id, "userPosts");
const tmpSnap = await getDocs(tmp);
tmpSnap.docs.map(async (element) => {
setPosts((prev) => {
prev.push(element.data());
return prev;
});
});
});
};
getPostData().catch(console.error);
return;
}, []);
return (
<SafeAreaView style={globalStyles.global}>
{posts && posts.length > 0 ? (
<FlatList
data={posts}
renderItem={({ item }) => (
<Post
post={item}
navigation={navigation}
style={globalStyles.list_of_posts}
/>
)}
keyExtractor={(item, index) => index.toString()}
/>
) : (
<Text>Nothing to show here</Text>
)}
<Pressable
title="edit"
onPress={() => {
navigation.navigate("CreatePost", { navigation });
}}
style={globalStyles.plus_btn}
>
<Text style={globalStyles.plus_btn_text}>+</Text>
</Pressable>
</SafeAreaView>
);
};
export default FeedScreen;
As said, I'm new to this so I'd love an explanation of what actually happens and how to do it properly.
I think the prev value of setPosts will always be [] since it does not immediately update if you call it. A standard way to do it is to call setPosts at the end of your function. Can you try this one?
useEffect(() => {
const getPostData = async () => {
const q = collection(db, "posts");
const docSnap = await getDocs(q);
const promises = docSnap.docs.map(async (item) => {
const tmp = collection(db, "posts", item.id, "userPosts");
const tmpSnap = await getDocs(tmp);
return tmpSnap.docs.map((element) => element.data());
});
const arrayOfPosts = await Promise.all(promises);
let newPosts = [];
arrayOfPosts.forEach((posts) => {
newPosts = [...newPosts, ...posts];
});
setPosts(newPosts);
};
getPostData().catch(console.error);
return;
}, []);

React Native useState Render Error : [Too many re-renders.]

This is my code:
export default function App() {
const [onProcess, setOnProcess] = useState("normal")
var myid = "123"
const [data, setData] = useState([]);
useEffect(() => {
fetch('https://api.npoint.io/0294bea2185268c9ac70')
.then((response) => response.json())
.then((json) => setData(json))
.catch((error) => console.log('ERR :', error))
},[]);
for (let x in data) {
if (data[x].client_id == myid) {
var set = data[x].situation
setOnProcess(set)
console.log(data[x].situation)
break
}
}
const rt_normal = (
<View style={styles.container}>
<Text> This is normal view </Text>
</View>
)
const rt_process = (
<View style={styles.container}>
<Text> This is process view </Text>
</View>
)
if (onProcess == "normal") {
return rt_normal
}
else if (onProcess == "_on_process") {
return rt_process
}
}
The error I got is:
:[Render Error. Too many re-renders. React limits the number of renders to prevent an infinite loop.]
This happens because of setOnProcess(set) code. How can I solve this?
You should remove your for...in loop and refactor to utilise useEffect.
useEffect(() => {
// Get a specific entry where client_id matches myId.
const filteredItem = data.find(item => item.client_id === myId);
// Perform a check as .find() can return undefined.
if(filteredItem.situation) {
setSituation(filteredItem.situation);
}
}, [data]);
Put the for loop inside a useEffect
(Untested) example:
useEffect(() => {
for (let x in data) {
if (data[x].client_id == myid) {
var set = data[x].situation;
setOnProcess(set);
console.log(data[x].situation);
break;
}
}
}, [data]);

React Admin Change field based on related record field

Let's say I have a record called 'assets' which has a column called deducitble. An asset can have one Insurer. The insurer has a boolean field 'allowOtherDeductible'.
When editing the asset, I want the ability to first check if the associated insurer has allowOtherDeductible set to true. If so I'll allow a TextInput for deductible, if false, a SelectInput.
How can I achieve this? I cannot see a way to fetch related record's fields when conditionally rendering fields.
I ended up pulling in all the insurers and loading the asset when the component loaded. Seems a bit inefficient, but I couldn't come up with a better way:
export const AssetEdit = props => {
const dataProvider = useDataProvider();
const [insurers, setInsurers] = useState([]);
const [asset, setAsset] = useState(null);
const [otherDeductible, setOtherDeductible] = useState(false);
useEffect(() => {
dataProvider.getOne('assets', { id: props.id })
.then(({ data }) => {
setAsset(data);
return dataProvider.getList('insurers', { pagination: { page: 1, perPage: 100 } });
})
.then(({ data }) => setInsurers(data))
.catch(error => {
console.log(error)
})
}, [])
useEffect(() => {
if (asset && insurers) {
const selectedInsurer = insurers.find(insurer => insurer.id === asset?.insurer_id);
setOtherDeductible(selectedInsurer?.other_deductible);
}
}, [insurers])
return (
<Edit {...props}>
<SimpleForm>
{otherDeductible &&
<NumberInput
source={'deductible'}
parse={val => dollarsToCents(val)}
format={val => centsToDollars(val)}
/>
}
<FormDataConsumer>
{({ formData, ...rest }) => {
if(!otherDeductible){
return <SelectInput
source="deductible"
parse={val => dollarsToCents(val)}
format={val => centsToDollars(val)}
choices={getDeductibleChoices(formData.insured_value)}
/>
}
}}
</FormDataConsumer>
</SimpleForm>
</Edit>
)
}
I'd write a custom Input taking advantage of the fact that SimpleForm injects the record to all its children:
const DeductibleInput = ({ record }) => {
if (!record) return null;
const { data, loaded } = useGetOne('insurers', record.insured_id);
if (!loaded) return null; // or a loader component
return data.otherDeductible
? <NumberInput
source="deductible"
parse={val => dollarsToCents(val)}
format={val => centsToDollars(val)}
/>
: <SelectInput
source="deductible"
parse={val => dollarsToCents(val)}
format={val => centsToDollars(val)}
choices={getDeductibleChoices(record.insured_value)}
/>
}

How to call a variable with data in React Native

Sometghing really basic but I didn't understant.
Once I get the contacts how can I use them to populate the Flatlist?
I always get Can't find variable: contacts
import * as Contacts from "expo-contacts";
const ContactsScreen = props => {
useEffect(() => {
(async () => {
const { status } = await Contacts.requestPermissionsAsync();
if (status === "granted") {
const { data } = await Contacts.getContactsAsync({
fields: [Contacts.Fields.Emails]
});
if (data.length > 0) {
const contact = data[0];
console.log(contact);
}
}
})();
}, []);
return (
<View >
<Text>Contacts Module</Text>
<FlatList
data={contact}
keyExtractor={contact.id}
renderItem={({ item }) => (
<ContactItem
firstName={item.firstName}
/>
</View>
);
};
export default ContactsScreen;
I think it's really simple, I just don't understand
You need to keep your contacts in the component's state. So every time you change your state, your component will render itself and you will see the updated data.
Change your code with the following. Don't forget to import useState.
import * as Contacts from "expo-contacts";
const ContactsScreen = props => {
const [myContacts, setMyContacts] = useState([]);
useEffect(() => {
(async () => {
const { status } = await Contacts.requestPermissionsAsync();
if (status === "granted") {
const { data } = await Contacts.getContactsAsync({
fields: [Contacts.Fields.Emails]
});
if (data.length > 0) {
setMyContacts(data);
}
}
})();
}, []);
return (
<View >
<Text>Contacts Module</Text>
<FlatList
data={myContacts}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<Text>{item.firstName}</Text>
)}
/>
</View>
);
};
export default ContactsScreen;
Answer from my comment:
I think that might be because of the scope of the variable , it could be that RN doenst know it exists because it only lives inside the function. I guess you could set up a State and then assign the values from contact to the state and in ur flatlist call data ={ this.state.contact}.
or by using hooks like you do :
if (data.length > 0) {
setContact(data);
}
and call it in flatlist:
data={myContact} // if named so in state declaration