(React Native && RTK Query) How to make sure the data has been returned when use conditional fetching - react-native

export function Login() {
const [skip, setSkip] = useState(true);
const { data, isFetching } = useVerifyUserQuery(userState, {
skip,
});
const LoginButton = () => (
<Button
title="Login"
onPress={() => {
setSkip((prev) => !prev);
}}
/>
);
return (
…
)
}
The requirement is to make a request when the button is pressed, and then store the returned data in a constant. Is there a good way to make sure data is returned before I store it.
Here is one of my solutions. Obviously it may cause some problems.
onPress={() => {
setSkip((prev) => !prev);
while(isFetching){}
// save data
}}
And with the code below, storeData will be called multiple times.
export function Login() {
const [skip, setSkip] = useState(true);
const { data, isFetching } = useVerifyUserQuery(userState, {
skip,
});
if (!isFetching && IsNotEmpty(data)){
storeData();
}
const LoginButton = () => (
<Button
title="Login"
onPress={() => {
setSkip((prev) => !prev);
}}
/>
);
return (
…
)
}

It looks like you just want to use the lazy version - useLazyVerifyUserQuery instead of common. It will be like:
export function Login() {
const [ verifyUser ] = useLazyVerifyUserQuery();
const handleLogin = async () => {
const data = await verifyUser(userState).unwrap();
// Probably you would want to use `storeData` somehow here?
}
const LoginButton = () => (
<Button
title="Login"
onPress={handleLogin}
/>
);
return (
...
)
}
PS: just a warning - using a nested component definition, like LoginButton inside Login - is a known antipattern that may cause significant performance issues.

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 Hooks sync UseState in 2 diferent files

I want to sync the value of a useState in 2 different files from a useHook
I have a file named useChangeScreen witch I use to set when I want to show the diferent Views:
export const useChangeScreen = () => {
...
const [homeActivo, setHomeActivo] = useState(false);
const [searchActivo, setSearchActivo] = useState(true);
const [profileActivo, setProfileActivo] = useState(false);
...
const irAHome = () => {
setHomeActivo(true);
setSearchActivo(false);
setProfileActivo(false);
};
const irASearch = () => {
setHomeActivo(false);
setSearchActivo(true);
setProfileActivo(false);
};
const irAProfile = () => {
setHomeActivo(false);
setSearchActivo(false);
setProfileActivo(true);
};
...
return {
homeActivo,
searchActivo,
profileActivo,
irAHome,
irASearch,
irAProfile
}
}
This hook is called in the navigation component:
export const Nav = () => {
const {
irAHome,
irANotifi,
irAProfile,
irASearch
} = useChangeScreen();
...
return (
...
<TouchableOpacity onPress={irAHome}>
...
<TouchableOpacity onPress={irASearch}>
...
<TouchableOpacity onPress={irAProfile}>
...
)
}
and in the screen controller I have this:
export const ScreenController =() => {
const {
homeActivo,
searchActivo,
profileActivo,
} = useChangeScreen();
...
return(
...
{homeActivo ? (
<HomeScreen />
) : searchActivo ? (
<SearchShopsScreen />
) : profileActivo ? null : null}
...
)
}
when I press the buttons in the nav I want the views in ScreenController to change from Home to Profile or Search, but when I press the buttons, the state dont change
You can lift up the state to the parent component and pass it down to it's children, use React Context API or Redux.
If you chose to lift up the state:
Then you would have a parent component that looks like this:
// ...
const Parent = () => {
const {
irAHome,
irANotifi,
irAProfile,
irASearch,
homeActivo,
searchActivo,
profileActivo
} = useChangeScreen();
return (
<>
<Nav
irAHome={irAHome}
irANotifi={irANotifi}
irAProfile={irAProfile}
irASearch={irASearch}
/>
<ScreenController
homeActivo={homeActivo}
searchActivo={searchActivo}
profileActivo={profileActivo}
/>
</>
);
};
// ...
Then use the values passed from props like that:
export const ScreenController =({ homeActivo, searchActivo, profileActivo }) => {
// ...
return (
// ...
{homeActivo ? (
<HomeScreen />
) : searchActivo ? (
<SearchShopsScreen />
) : profileActivo ? null : null}
// ...
);
};
and:
export const Nav = ({
irAHome,
irANotifi,
irAProfile,
irASearch
}) => {
// ...
return (
// ...
<TouchableOpacity onPress={irAHome} />
// ...
<TouchableOpacity onPress={irASearch} />
// ...
<TouchableOpacity onPress={irAProfile} />
// ...
)
}
Note:
You should've actually used only one state which stores the current screen and checked for the current screen using comparison operators.
Checkout these for more details:
Lifting State Up
React Context API
Get Started with Redux

Refresh Container/Screen with useState AND map() in React Native

in my application, after receiving the updated data from the API I use useState, but this does not reflect on the information on the screen, I need to goBack and forward to change the information.
When I click on the button I save the information and on the return I need to update that the task has already been done, showing an "OK", but this is not updated, even though the "schedules" variable is right.
Where am I going wrong? What do I need to do to "return" and run again?
Thanks a lot!
import React, { useState, useEffect, useCallback } from "react";
...
const Pdvs = () => {
const [scheduletasks, setScheduletasks] = useState([]);
...
onSave = async (id) => {.....
const responseTask = await api.post("/schedules/fulldetails",{id});
setScheduletasks(responseTask.data);
...
return (
<Container>
{scheduletasks.map((keys) => (
{keys.done ? "OK"
) : ""}
<Button title="Done"
onPress={() =>
handleSave(keys.id)
}
/>
Your codes a little broken, especially in return(). Please revise it to get better help.
Sorry, I put just a piece of the code, to filter where is not the problem. Below more compleat code:
My "onSubmit" should be where you update the variable for useState
The big problem is in the Return, variable {keys.done}, is just that I want! :)
Thanks a lot
onSubmit = async (inputText) => {
try {
const { schedule_id, product_id, task_id } = scheduletasksResult;
const gpsPosition = await getLocationAsync();
const response = await api.post(`/scheduletasks/add`, {
schedule_id,
product_id,
task_id,
result: inputText,
gpsPosition,
});
if (response.status === 200) {
setScheduletasksID(response.data.id);
} else {
setScheduletasksID(0);
}
return response.data.id;
} catch (error) {
console.log(error.response.data);
}
};
useEffect(() => {
async function loadPdvs() {
setCompany_id(company_id[1]);
const response = await api.post("/schedules", {
company_id: company_id[1],
id,
});
setSchedules(response.data[0]);
const responseTask = await api.post("/schedules/fulldetails", {
company_id: company_id[1],
id,
});
setScheduletasks(responseTask.data);
}
loadPdvs();
}, []);
return (
<Container>
<RouteTitle>{schedules.tradeName}</RouteTitle>
<ScrollView>
{scheduletasks.map((keys) => (
<RoutesContainer key={keys.task_id}>
<RouteDetail>
{keys.task_name}
{keys.done ? (
<FontAwesomeIcon
icon={faCheckCircle}
/>
) : null}
</RouteDetail>
{keys.textRequired ? (
<Button
title="Observações"
onPress={() =>
showDialog(keys.schedule_id, keys.product_id, keys.task_id)
}
/>
) : null}
</RoutesContainer>
))}
</ScrollView>
</Container>
);

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