how to set UI component to state in background in react-native? - react-native

I am new in react-native, in my application I am generating QRCode by one library and it working but in android it is taking time to show on UI, when I set that component to hook to show on UI then it stuck for while and every thing getting non-responsive. After some time it shows and everything work well.
So how can put that setWalletQR in background so that I can show loader until it show to UI?
Here is my code where I am generating the QR in InteractionManager to show
const PozReceive = ({ onClose }: ReceiveProps) => {
const [walletQR, setWalletQR] = useState<ConentQR>(null);
const generateWalletQrCode = () => {
const interactionPromise = InteractionManager.runAfterInteractions(() => {
const qrCode = ConentQR(user?.walletAddress || '', walletImg, 50);
setWalletQR(qrCode);
});
return () => interactionPromise.cancel();
};
useEffect(() => {
if (!pouchQR) {
generatePouchQrCode();
}
}, []);
return (
<Modal
coverScreen={true}
isVisible={true}
onBackdropPress={onClose}
onBackButtonPress={onClose}
backdropColor={Colors.DARK_PURPLE}
backdropOpacity={0.7}
style={styles.modal}>
<>
<BlurView
style={styles.blurView}
blurType="dark"
blurAmount={20}
reducedTransparencyFallbackColor="white"
/>
<VStack style={[styles.modalContainer]}>
{!walletQR ? (
<Image style={styles.qrLoader} source={loaderGif} />
) : (
walletQR
)}
</VStack>
</>
</Modal>
);
};
and here is QR code generator code :-
const ContentQR = (
content: string,
logo: Image.propTypes.source,
logoSize: number,
backgroundColor: string = 'transparent',
) => {
return (
<QRCode
color={Colors.DARK_PURPLE}
content={content}
codeStyle={'dot'}
outerEyeStyle={'diamond'}
logo={logo}
logoSize={logoSize}
backgroundColor={backgroundColor}
/>
);
};
Someone please help me I getting stuck here for while.

You can introduce a variable isLoading and render the loader based on this variable instead of qr value.
const PozReceive = ({ onClose }: ReceiveProps) => {
const [walletQR, setWalletQR] = useState<ConentQR>(null);
const [isLoading, setIsLoading] = useState<Boolean>(false);
const generateWalletQrCode = () => {
setIsLoading(true)
const interactionPromise = InteractionManager.runAfterInteractions(() => {
const qrCode = ConentQR(user?.walletAddress || '', walletImg, 50);
setWalletQR(qrCode);
setIsLoading(false)
});
return () => interactionPromise.cancel();
};
....
<VStack style={[styles.modalContainer]}>
{isLoading && <Image style={styles.qrLoader} source={loaderGif} />}
{!isLoaing && walletQR && walletQR}
</VStack>

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.

Flatlist inside tab navigator is scrolling to top on state change in react native

Here you can see the gif
Here is my whole Navigator functional component. I'm trying to implement two tabs using Tab Navigator. One to display the cryptos and the other to display the forex data.
The problem is, when I try to load more data on reaching the flatlist's end, the flatlist is scrolling to the top since I'm making a state change [page+1].
const Navigator = () => {
const Tab = createMaterialTopTabNavigator();
const renderItems = ({ item }) => (
<Text>{item.name}<Text>
);
const fetchMarketData = async () => {
console.log("Fetching");
const marketData = await getCryptoMarketData({ page });
if (marketData != "Network Error") {
const ids = data.map((item) => item.id);
let newData = marketData.filter((item) => !ids.includes(item.id));
setData([...data, ...newData]);
setFetching(false);
} else {
setFetching(false);
Alert.alert(marketData, "Sorry for the inconvenience");
}
};
useEffect(() => {
setFetching(true);
const data = async () => {
await fetchMarketData();
};
}, [page]);
const handleLoadMore = async () => {
setFetching(true);
setPage((page) => page + 1);
};
const ScreenA = () => (
<FlatList
data={data}
style={{ backgroundColor: "white" }}
keyExtractor={(item) => item.id}
renderItem={renderItems}
scrollEventThrottle={16}
onEndReached={handleLoadMore}
onEndReachedThreshold={0}
/>
);
return (
<Tab.Navigator
screenOptions={({ route }) => screenOptions(route)}
keyboardDismissMode="auto"
>
<Tab.Screen name="Crypto" component={ScreenA} />
<Tab.Screen name="Forex" component={ScreenC} />
</Tab.Navigator>
);
};
export default Navigator;
OnEndReached is firing the handleLoadMore function and after the state change on data, the Flatlist is scrolling to the top.
1st reason
you have typo in "fetchMarketData", how exactly u get "newData" because i cant see it anywhere, maybe it should be "marketData" if not then u adding SAME old data PLUS undefined[...data, ...undefined]
2nd reason
reason why is that u call setPage(page + 1) and then "fetchMarketData" this is bad why ? because setState is async and it can be changed instant or after 5 secound, so u dont know when its changed and this is why we have hooks, you can use "useEffect" to handle this
change your "handleLoadMore" for example like this
const handleLoadMore = () => {
setPage(page + 1);
};
add useEffect hook that runs when "page" state changes
React.useEffect(() => {
(async() => {
setFetching(true)
const marketData = await getCryptoMarketData({ page });
if (marketData != "Network Error") {
setData([...data, ...marketData]);
} else {
Alert.alert(marketData, "Sorry for the inconvenience");
}
setFetching(false)
})()
}, [page])

remove duplicate error from JSON value 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;
},[]);

react-native re-render negates filter

To learn myself react-native I am building an app that contains a FlatList filled with tickets with help of redux. When I try to filter through the tickets by typing in a number, the list gets filtered but only for 1 second. After that it gives a list of all tickets again. I have trouble finding the the logical error behind my beginner code. Any help would be appreciated.
I pasted the list below:
const AllTicketList = ({ navigation, ticket: { allTickets }, getTickets }) => {
useEffect(() => {
getTickets();
}, []);
const [enteredValue, setEnteredValue] = useState();
const [selectedNumber, setSelectedNumber] = useState(false);
const [displayedTickets, setDisplayedTickets] = useState();
const [confirmed, setConfirmed] = useState(false);
useEffect(() => {
setDisplayedTickets(allTickets);
});
const confirmInputHandler = () => {
const chosenNumber = parseInt(enteredValue);
if (isNaN(chosenNumber) || chosenNumber <= 0) {
Alert.alert(
'Invalid number',
'The number of upvotes has to be greater than 0.',
[{ text: 'Ok', style: 'destructive', onPress: resetInputHandler }]
);
return;
}
setConfirmed(true);
setSelectedNumber(chosenNumber);
Keyboard.dismiss();
};
const resetInputHandler = () => {
setEnteredValue('');
setConfirmed(false);
};
const numberInputHandler = inputText => {
setEnteredValue(inputText.replace(/[^0-9]/g, ''));
};
if (confirmed) {
const foundTickets = displayedTickets.filter(t => t.numberOfVotes >= selectedNumber);
setDisplayedTickets(foundTickets);
setConfirmed(false);
}
return (
<View>
<SearchBarUpvotes
numberInputHandler={numberInputHandler}
confirmInputHandler={confirmInputHandler}
enteredValue={enteredValue}
/>
<FlatList
removeClippedSubviews={false}
data={displayedTickets}
renderItem={({ item }) => (
<TicketItem ticket={item} navigation={navigation} />
)}
keyExtractor={item => item.id}
/>
</View>
);
};
const mapStateToProps = state => ({
ticket: state.ticket
});
export default connect(mapStateToProps, {
getTickets
})(AllTicketList);
The problem is in your second useEffect hook:
useEffect(() => {
setDisplayedTickets(allTickets);
});
This effect, will set the displayedTickets to allTickets on every re-render.
So here's what happens:
1. When you filter the tickets, you're changing the state, and you're setting the displatedTickets to be the filtered tickets: setDisplayedTickets(foundTickets);.
2. The displayedTickets is updated, the component is re-rendered, you see the new tickets for a second, and as soon as it is re-rendered, that effect is executing again and it sets the displayedTickets to allTickets again: setDisplayedTickets(allTickets);.
So here's my advice:
1. Remove the second useEffect - that will prevent the displayedTickets to be set again to allTickets on every re-render .
2. In your flatlist change the data to displayedTickets || allTickets. In this way, when the tickets will be unfiltered - the list will display the allTickets and as soon as you filter them, the list will display the displayedTickets.
So here's how your final code should look like:
const AllTicketList = ({ navigation, ticket: { allTickets }, getTickets }) => {
useEffect(() => {
getTickets();
}, []);
const [enteredValue, setEnteredValue] = useState();
const [selectedNumber, setSelectedNumber] = useState(false);
const [displayedTickets, setDisplayedTickets] = useState();
const [confirmed, setConfirmed] = useState(false);
// Remove this effect
//useEffect(() => {
// setDisplayedTickets(allTickets);
//});
const confirmInputHandler = () => {
const chosenNumber = parseInt(enteredValue);
if (isNaN(chosenNumber) || chosenNumber <= 0) {
Alert.alert(
'Invalid number',
'The number of upvotes has to be greater than 0.',
[{ text: 'Ok', style: 'destructive', onPress: resetInputHandler }]
);
return;
}
setConfirmed(true);
setSelectedNumber(chosenNumber);
Keyboard.dismiss();
};
const resetInputHandler = () => {
setEnteredValue('');
setConfirmed(false);
};
const numberInputHandler = inputText => {
setEnteredValue(inputText.replace(/[^0-9]/g, ''));
};
if (confirmed) {
const foundTickets = displayedTickets.filter(t => t.numberOfVotes >= selectedNumber);
setDisplayedTickets(foundTickets);
setConfirmed(false);
}
return (
<View>
<SearchBarUpvotes
numberInputHandler={numberInputHandler}
confirmInputHandler={confirmInputHandler}
enteredValue={enteredValue}
/>
<FlatList
removeClippedSubviews={false}
data={displayedTickets || allTickets} /* <-- displayedTickets || allTickets instead of displayedTickets */
renderItem={({ item }) => (
<TicketItem ticket={item} navigation={navigation} />
)}
keyExtractor={item => item.id}
/>
</View>
);
};
const mapStateToProps = state => ({
ticket: state.ticket
});
export default connect(mapStateToProps, {
getTickets
})(AllTicketList);

How can I fire a function in headerRight using Formik?

I'm new to react-native and formik and I encountered this problem that I'm trying to build up.
How can I fire a function in headerRight using Formik? I have updateCorporation function that will do fire api, and formik will do the job to fire this function and after I press the Update button, but the results are undefined
I didn`t understand why its happening.
File_1.js
const CorporationContainer = (props) => {
const {
navigation,
} = props;
const updateCorporation = (values) => {
// do patch stuff with values
// but its undefined
};
useEffect(() => {
navigation.setParams({ updateCorporation: updateCorporation.bind() });
}, []);
return (
<Corporation
updateCorporation={updateCorporation} />
);
};
CorporationContainer.navigationOptions = ({ navigation }) => ({
headerRight: (
<EditBtn
onPress={() => navigation.state.params.updateCorporation()}
>
<EditText>Update</EditText>
</EditBtn>
),
});
export default CorporationContainer;
File_2.js
const Corporation = (props) => {
const {
updateCorporation,
} = props;
const emailField = useRef(null);
const validationSchema = yup.object().shape({
email: yup.string()
.ensure()
.email('Email must be valid')
.required('Email'),
});
return (
<Formik
initialValues={{
email,
}}
onSubmit={values => updateCorporation(values)}
validateOnBlur={false}
validateOnChange={false}
validationSchema={validationSchema}
>
{(formProps) => {
const {
errors,
setFieldValue,
values,
} = formProps;
return (
<Container>
<Input
name="email"
placeholder="Email Corporation"
textContentType="emailAddress"
keyboardType="email-address"
returnKeyType="done"
autoCapitalize="none"
autoCorrect={false}
ref={emailField}
value={values.email}
onChangeText={setFieldValue}
editable={!email}
error={errors.email}}
/>
</Container>
);
}}
</Formik>
);
};
export default Corporation;
In File_1.js I had to use withForm and remove all Formik things in File_2.js and use the props instead.
const CorporationContainer = (props) => {
const {
navigation,
handleSubmit,
errors,
setFieldValue,
values,
} = props;
useEffect(() => {
navigation.setParams({ updateCorporation: handleSubmit.bind() });
}, []);
return (
<ProfileProfessional
errors={errors}
setFieldValue={setFieldValue}
values={values}
/>
);
};
CorporationContainer.navigationOptions = ({ navigation }) => ({
headerRight: (
<EditBtn
onPress={() => navigation.state.params.updateCorporation()}
>
<EditText>Editar</EditText>
</EditBtn>
),
});
export default withFormik({
// ...
})(CorporationContainer);
Formik author here...
Haven't tried this out, and idk exactly how navigation binding works, but you want to bind Formik's submitForm() prop to the navigation and not the updateCorporation function. However, You will need to do this where you have access to Formik props/context (i.e. as a child of <Formik>).
import React from 'react'
import { connect } from 'formik'
const updateCorporation = (values) => {
// do patch stuff with values
// but its undefined
};
const BindSubmit = connect(({ formik, navigation }) => {
useEffect(() => {
navigation.setParams({ updateCorporation: submitForm.bind() });
}, []);
return null
})
// ... and then just render it somewhere under Formik
const Corporation = () => {
return (
<Formik>
<BindSubmit />
{/* ... same */}
</Formik>
)
}