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.
Related
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;
}, []);
i have a big data list of products thats paginate, in every page it load 10 item, but when i add new items to itemlist,flatlist gets very slow,As the number of pages increases, so does the loading time of new products,The function of the choose button is also slowed down.
How to speed up loading I tried all the best methods but it still did not work. Did not React Native really solve this problem?
export default function Products(props) {
const toast = useToast();
const [isLoading, setSetIsLoading] = useState(true);
const [items, setItems] = useState([]);
const [fetchStatus, setFetchStatus] = useState(false);
const [page, setPage] = useState(1);
const [sending, setSending] = useState(false);
async function getProducts() {
let token = await AsyncStorage.getItem('#token');
let data = {
token: token,
page: page,
};
await get_products(data)
.then(res => {
setItems([...items, ...res.data.data.docs]);
setPage(res.data.data.nextPage);
})
.catch(err => {
console.log(err);
});
}
async function getNextPage() {
let token = await AsyncStorage.getItem('#token');
let data = {
token: token,
page: page,
};
await get_products(data)
.then(res => {
setItems([...items, ...res.data.data.docs]);
setPage(res.data.data.nextPage);
})
.catch(err => {
console.log(err);
});
}
async function selectProduct(id) {
setSending(true);
console.log({id});
let token = await AsyncStorage.getItem('#token');
let data = {
product_id: id
};
await select_products(data,token).then(res => {
toast.show({
description:res.data.message
})
setSending(false);
}).catch(rej => {
console.log({rej})
toast.show({
description:rej?.response?.data.message,
})
setSending(false);
})
}
useFocusEffect(
React.useCallback(() => {
getProducts();
return () => {
setItems([]);
setPage();
};
}, []),
);
renderItem =({item}) => (
<Card
selectProduct={id => selectProduct(id)}
sending={sending}
obj={item}
/>
)
return (
<View mb={20}>
<FlatList
data={items}
extraData={items}
removeClippedSubviews={true}
renderItem={renderItem}
keyExtractor={(item) => `${item._id}-item`}
onEndReached={getNextPage}
maxToRenderPerBatch="13"
ListFooterComponent={() => {
return <ActivityIndicator color="orange" size="large" />;
}}></FlatList>
</View>
);
}
Did you use **map method **?
It can help you for more easily loading data
In React-Native I´ve two TextInputs and an add-button
My add function works i.e it creates a list. But each time I fill out the form, it adds a new listpost. I want to check if action.payload.number exist, and if true, increase state.points with action.payload.points.
as yet everything I tried with have failed i.e it didn't recognize state.number === action.payload.number and adds a new row in the list
Please help me solving this issue
Thanks in advance
Pierre
const InputScreen = () => {
const [number, setNumber] = useState("");
const [points, setPoints] = useState("");
const {addScorer} = useContext(Context);
const onPress = () => {
addScorer(number, points);
setnumber("");
setPoints("");
};
return (
<View>
<Text style={styles.label}>enter Scorer Number:</Text>
<TextInput style={styles.input} value={number} onChangeText={setNumber} />
<Text style={styles.label}>enter Points Scored:</Text>
<TextInput style={styles.input} value={points} onChangeText={setPoints} />
<TouchableOpacity onPress={onPress}>
<FontAwesome5 name="plus-circle" size={44} color="coral" />
</TouchableOpacity>
</View>
);
};
export default InputScreen;
const scorerReducer = (state, action) => {
const id = Date.now().toString().slice(-4);
switch (action.type) {
case "add_scorer":
// if (state.number === action.payload.nummer) {
if (state.find((scorer) => scorer.id === action.payload.id)) {
return [
...state,
{
points: state.points + +action.payload.points,
},
];
} else {
return [
...state,
{
id: id,
number: action.payload.number,
points: +action.payload.points,
},
];
}
default:
return state;
}
};
const addScorer = (dispatch) => {
return (number, points, id) => {
dispatch({type: "add_scorer", payload: {number, points, id}});
};
};
export const {Context, Provider} = createDataContext(
scorerReducer,
{addScorer},
[]
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I solved it without using Reducer:)
export const ScoredProvider = ({children}) => {
const [playerList, setPlayerList] = useState([]);
const [number, setNumber] = useState("");
const [points, setPoints] = useState("");
const addScorer = () => {
const players = [...playerList];
if (number.trim().length === 0) {
return;
}
const posit = players.map((player) => player.number).indexOf(number);
if (posit !== -1) {
setPlayerList((playerList) =>
playerList.map((scorer, index) =>
index === posit
? {
...scorer,
points: scorer.points + +points,
}
: scorer
)
);
} else {
const newScorer = {
id: Date.now(),
number: number,
points: +points,
};
setPlayerList([...playerList, newScorer]);
setPoints(points);
}
};
return (
<ScoredContext.Provider
value={{number, setNumber, points, setPoints, playerList, addScorer}}
>
{children}
</ScoredContext.Provider>
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
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;
},[]);
Im working on simply COVID-19 tracker and i have a problem.
Is there any option in Apollo for React to fetch graphql data once per button press?
Now i have TextInput and Button but when i fetch data once i can't type another country in input because i have immediately error.
const Tile = () => {
const [country, setCountry] = useState('Poland');
const [cases, setCases] = useState(0);
const MY_QUERY = gql`
query getCountryStats($country: String!) {
country(name: $country) {
todayCases
}
}
`;
const [getCountryStats, {data, loading, error}] = useLazyQuery(MY_QUERY, {
variables: {
country: country,
},
onCompleted: (data) => {
setCases(data.country.todayCases);
},
});
if (loading) return <Text>LOADING...</Text>;
if (error) return <Text>Error!</Text>;
return (
<View>
<CasesNumber>{cases}</CasesNumber>
<FinderWrapper>
<FinderInput
onChangeText={(text) => {
setCountry(text);
}}
/>
<FinderButton
onPress={() => {
getCountryStats();
}}>
<Text>FIND</Text>
</FinderButton>
</FinderWrapper>
</View>
);
};
export default Tile;
Try using this
const Tile = () => {
const [country, setCountry] = useState('Poland');
const [cases, setCases] = useState(0);
let inputValue = ‘’;
const MY_QUERY = gql`
query getCountryStats($country: String!) {
country(name: $country) {
todayCases
}
}
`;
const [getCountryStats, {data, loading, error}] = useLazyQuery(MY_QUERY, {
variables: {
country: country,
},
onCompleted: (data) => {
setCases(data.country.todayCases);
},
});
const onGetCountryStats = () => {
setCountry(inputValue);
getCountryStats();
}
if (loading) return <Text>LOADING...</Text>;
if (error) return <Text>Error!</Text>;
return (
<View>
<CasesNumber>{cases}</CasesNumber>
<FinderWrapper>
<FinderInput
onChangeText={(text) => {
inputValue = text;
}}
/>
<FinderButton
onPress={() => {
onGetCountryStats();
}}>
<Text>FIND</Text>
</FinderButton>
</FinderWrapper>
</View>
);
};
export default Tile;