too many re-renders when data moved to a separate component - javascript

Currently, I am using this logic to render data on the basis of results from a grapqhl query. This works fine:
const contacts = () => {
const { loading, error, data } = useUsersQuery({
variables: {
where: { id: 1 },
},
});
if (data) {
console.log('DATA COMING', data);
const contactName = data.users.nodes[0].userRelations[0].relatedUser.firstName
.concat(' ')
.concat(data.users.nodes[0].userRelations[0].relatedUser.lastName);
return (
<View style={styles.users}>
<View style={styles.item} key={data.users.nodes[0].id}>
<Thumbnail
style={styles.thumbnail}
source={{
uri:
'https://cdn4.iconfinder.com/data/icons/avatars-xmas-giveaway/128/girl_avatar_child_kid-512.png',
}}></Thumbnail>
<Text style={styles.userName}>{contactName}</Text>
</View>
</View>
);
}
};
return (
<SafeAreaView style={{ flex: 1 }}>
<Container style={{ flex: 1, alignItems: 'center' }}>
<Item style={styles.addToWhitelist}>
<Icon name="add" onPress={() => navigation.navigate('AddContact')} />
<Text style={styles.addToContactTitle}>Add contact</Text>
</Item>
<Text onPress={() => navigation.navigate('Home')}>Zurück</Text>
<View style={{ width: moderateScale(350) }}>
<Text>Keine Kontacte</Text>
</View>
{contacts()}
{/* <ContactList data={userData}></ContactList> */}
</Container>
</SafeAreaView>
);
};
However, when I make a separate component :
export const ContactList: React.FunctionComponent<UserProps> = ({
data,
}) => {
console.log('user called');
if (!data) return null;
console.log('DATA COMING', data);
const contactName = data.users.nodes[0].userRelations[0].relatedUser.firstName
.concat(' ')
.concat(data.users.nodes[0].userRelations[0].relatedUser.lastName);
return (
<View style={styles.users}>
<View style={styles.item} key={data.users.nodes[0].id}>
<Thumbnail
style={styles.thumbnail}
source={{
uri:
'https://cdn4.iconfinder.com/data/icons/avatars-xmas-giveaway/128/girl_avatar_child_kid-512.png',
}}></Thumbnail>
<Text style={styles.userName}>{contactName}</Text>
</View>
</View>
);
};
and call it like this:
const [userData, setUserData] = useState<UsersQueryHookResult>('');
const contacts = () => {
console.log('running');
const { loading, error, data } = useUsersQuery({
variables: {
where: { id: 1 },
},
});
if (data) {
setUserData(data);
}
};
return (
<SafeAreaView style={{ flex: 1 }}>
<Container style={{ flex: 1, alignItems: 'center' }}>
<Item style={styles.addToWhitelist}>
<Icon name="add" onPress={() => navigation.navigate('AddContact')} />
<Text style={styles.addToContactTitle}>Add contact</Text>
</Item>
<Text onPress={() => navigation.navigate('Home')}>Zurück</Text>
<View style={{ width: moderateScale(350) }}>
<Text>Keine Kontacte</Text>
</View>
{/* {contacts()} */}
<ContactList data={userData}></ContactList>
</Container>
</SafeAreaView>
);
};
However, this gives me a too many re-renders issue. What am I missing? Probably something basic. I also tried using useEffects but I can't run a graphql query hook inside it. That gives me an invalid hook call error.

It seems your running in an endless recursion.
If you call contacts in you render block it causes a setState (through setUserData) which renders, so contacts is called once again and so on till infinite (or till the error).

Related

Send props to another screen

help, so lets say i got a bunch of data from an API (in Homescreen.js), which then i send to a component called "Artikel.js", how should i send the data in each of these articles to a screen called "DetailScreen.js". please someone help me, i'd appreciate it very very much, thanks in advance and sorry for bad english
const Homescreen = () => {
const [articles, setArticles] = useState([]);
const getArticles = () => {
axios
.get(
"https://newsapi.org/v2/top-headlines?country=us&apiKey=API_KEY",
{
params: {
category: "technology",
},
}
)
.then((response) => {
setArticles(response.data.articles);
})
.catch(function (error) {
console.log(error);
})
.then(function () {});
};
useEffect(() => {
getArticles();
}, []);
return (
<SafeAreaView style={styles.container}>
<FlatList
data={articles}
renderItem={({ item }) => (
<Artikel
urlToImage={item.urlToImage}
title={item.title}
description={item.description}
author={item.author}
publishedAt={item.publishedAt}
sourceName={item.source.name}
url={item.url}
/>
)}
keyExtractor={(item) => item.title}
/>
</SafeAreaView>
);
};
export default Homescreen;
Artikel.js
const Artikel = (props) => {
const navigation = useNavigation();
const goToDetail = () => {
navigation.navigate("Detail", {judul: 'Asu'});
};
// const goToSource = () => {
// WebBrowser.openBrowserAsync(props.url);
// };
return (
<SafeAreaView style={styles.container}>
<Pressable onPress={goToDetail}>
<Image
style={styles.image}
on
source={{
uri: props.urlToImage,
}}
/>
</Pressable>
<View style={{ paddingHorizontal: 20, paddingBottom: 10 }}>
<Text style={styles.title}>{props.title}</Text>
<Text style={styles.deskripsi} numberOfLines={3}>
{props.description}
</Text>
<View style={styles.data}>
<Text style={styles.h2}>
source:<Text style={styles.sumber}> {props.sourceName}</Text>
</Text>
<Text style={styles.tanggal}>
{moment(props.publishedAt).format("MMM Do YY")}
</Text>
</View>
</View>
</SafeAreaView>
);
};
export default Artikel;
"DetailScreen.js"
const DetailScreen = (props) => {
return (
<SafeAreaView style={styles.container}>
<Header />
<View style={styles.image}>
<Image
source={{
uri: props.thumbnail,
}}
style={styles.image}
/>
</View>
<View style={styles.bodyartikel}>
<Text style={styles.judul}>PROPS TITLE</Text>
<Text style={styles.artikel}>
PROPS.ARTICLE
</Text>
<View style={styles.footer}>
<Text style={styles.h1}>
By: <Text style={styles.sumber}>Salman</Text>
</Text>
<Text>12 Okt 2020</Text>
</View>
</View>
</SafeAreaView>
);
};
export default DetailScreen;
i tried to make a list of the datas i need in Artikel.js and made it into a list, but it didnt work
So in your Article.js you have called an method to navigate to DetailScreen.js. You have can do like this.
In Article.js:
<Pressable onPress={() => goToDetail(props)}> // pass props as argument
<Image
style={styles.image}
source={{
uri: props.urlToImage,
}}
/>
</Pressable>
now in your goToDetail method:
// Catch passed arguments as props
const goToDetail = (props) => {
navigation.navigate('Details', {
title: props.title,
description: props.description,
})
// As for now just passing title and description from props
};
Now to access Passed data in Detail.js:
import { useRoute } from '#react-navigation/native';
const DetailScreen = () => {
let route = useRoute(); // using route hooks
let {title, data} = route.params
return (
<YourComponent/>
);
};
In this way you can pass data from one screen to another. For more detail you always can visit react native navigation docs: https://reactnavigation.org/docs/params

How to save route.params with asyncstorage?

Srry if the title makes no sense. Don't know a better title.
How can I save route.params items that I pass to my second screen using AsyncStorage?
In my first screen i have a bunch of data in a FlatList that can be opened with a Modal. Inside that Modal I have a TouchableOpacity that can send the data thats inside the Modal to my second screen. The data that has been passed to the second screen is passed to a FlatList. The data in the FlatList should be saved to AsyncStorage. Tried alot of things getting this to work, but only getting warning message
undefined. Code below is the most recent progress.
Using React Navigation V5.
FIRST SCREEN
const [masterDataSource, setMasterDataSource] = useState(DataBase);
const [details, setDetails] = useState('');
<TouchableOpacity
onPress={() => {
const updated = [...masterDataSource];
updated.find(
(item) => item.id === details.id,
).selected = true;
setMasterDataSource(updated);
navigation.navigate('cart', {
screen: 'cart',
params: {
items: updated.filter((item) => item.selected),
},
});
setModalVisible(false);
}}>
<Text>Add to cart</Text>
</TouchableOpacity>
SECOND SCREEN
import React, { useEffect, useState } from 'react';
import { View, Text, FlatList, TouchableOpacity } from 'react-native';
import { useTheme } from '../Data/ThemeContext';
import AsyncStorage from '#react-native-async-storage/async-storage';
import Ionicons from 'react-native-vector-icons/Ionicons';
export default function ShoppingList({ route, navigation }) {
const [shoppingList, setShoppingList] = useState([]);
const { colors } = useTheme();
const todo = () => {
alert('Todo');
};
useEffect(() => {
restoreShoppingListAsync();
}, []);
const shoppingListAsync = () => {
const shoppingList = route.params && route.params.items;
setShoppingList(list);
storeShoppingList(list);
};
const asyncStorageKey = '#ShoppingList';
const storeShoppingListAsync = (list) => {
const stringifiedList = JSON.stringify(list);
AsyncStorage.setItem(asyncStorageKey, stringifiedList).catch((err) => {
console.warn(err);
});
};
const restoreShoppingListAsync = () => {
AsyncStorage.getItem(asyncStorageKey)
.then((stringifiedList) => {
console.log(stringifiedList);
const parsedShoppingList = JSON.parse(stringifiedList);
if (!parsedShoppingList || typeof parsedShoppingList !== 'object')
return;
setShoppingList(parsedShoppingList);
})
.then((err) => {
console.warn(err);
});
};
const RenderItem = ({ item }) => {
return (
<View>
<TouchableOpacity
style={{
marginLeft: 20,
marginRight: 20,
elevation: 3,
backgroundColor: colors.card,
borderRadius: 10,
}}>
<View style={{ margin: 10 }}>
<Text style={{ color: colors.text, fontWeight: '700' }}>
{item.name}
</Text>
<Text style={{ color: colors.text }}>{item.gluten}</Text>
<Text style={{ color: colors.text }}>{item.id}</Text>
</View>
</TouchableOpacity>
</View>
);
};
const emptyComponent = () => {
return (
<View style={{ alignItems: 'center' }}>
<Text style={{ color: colors.text }}>Listan är tom</Text>
</View>
);
};
const itemSeparatorComponent = () => {
return (
<View
style={{
margin: 3,
}}></View>
);
};
return (
<View
style={{
flex: 1,
}}>
<View
style={{
padding: 30,
backgroundColor: colors.Textinput,
elevation: 12,
}}>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<TouchableOpacity onPress={() => navigation.goBack()}>
<Ionicons name="arrow-back-outline" size={25} color="#fff" />
</TouchableOpacity>
<Text style={{ color: '#fff', fontSize: 20 }}>Inköpslista</Text>
<TouchableOpacity>
<Ionicons
name="trash-outline"
size={25}
color="#fff"
onPress={() => todo()}
/>
</TouchableOpacity>
</View>
</View>
<View style={{ flex: 1, marginTop: 30 }}>
<FlatList
data={shoppingList}
renderItem={RenderItem}
ListEmptyComponent={emptyComponent}
ItemSeparatorComponent={itemSeparatorComponent}
initialNumToRender={4}
maxToRenderPerBatch={5}
windowSize={10}
removeClippedSubviews={true}
updateCellsBatchingPeriod={100}
showsVerticalScrollIndicator={true}
contentContainerStyle={{ paddingBottom: 20 }}
/>
</View>
</View>
);
}
As you are using async storage to maintain the cart.
I would suggest an approach as below
Update the asyn storage when new items are added to or removed from the cart
Retrieve the items from the cart screen and show the items there
Before you navigate store the items like below
AsyncStorage.setItem(
'Items',
JSON.stringify(updated.filter((item) => item.selected))
).then(() => {
navigation.navigate('Cart', {
items: updated.filter((item) => item.selected),
});
});
The cart screen would be something like below
function Cart({ navigation, route }) {
const [data,setData]=useState([]);
React.useEffect(() => {
async function fetchMyAPI() {
const value = await AsyncStorage.getItem('Items');
setData(JSON.parse(value));
}
fetchMyAPI();
}, []);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button title="Go back" onPress={() => navigation.goBack()} />
<FlatList
data={data}
renderItem={RenderItem}
/>
</View>
);
}
Working Example
https://snack.expo.io/#guruparan/cartexample

React Native, Calling a Function from Another one in Render

I have this SMN() function and I've created inside it Site function as Const. So I need to call Site function in Render() function. This is main function code:
SMN() {
const Site = () => {
return (
<View style={{ height: 400 }}>
<WebView source={{ uri: 'https://www.google.com' }} style={{ marginTop: 20 }} />
</View>
);
}
});
This is Render() function where I want to call Site function from it, I've used:
this.SMN().Site , this does not throw errors but does not display any.
render() {
return (
</View>
<View>{this.SMN().Site}</View>
</View>
)
}
Make your Site as a component just like this:
const Site = () => {
return (
<View style={{ height: 400 }}>
<WebView
source={{ uri: 'https://www.google.com' }}
style={{ marginTop: 20 }} />
</View>
);
}
And in your render function use it like this:
render() {
return (
</View>
<View><Site /></View>
</View>
)
}
I think you want to get the return you want from SMN(). So this is what I thought your intended solution was.
SMN() {
return (
{
Site : () => (
<View style={{ height: 400 }}>
<WebView source={{ uri: 'https://www.google.com' }}
style={{marginTop:20 }} />
</View>
)
}
);
});
render() {
return (
</View>
<View>{this.SMN().Site}</View>
</View>
)
}

React Native Swiper ref element scrollTo does not work on first render

I am implementing an Introduction feature in my app. Where the screens show after splash screen. I used react-native-swiper component for guiding the user to step by step tutorial in my app.
Here is my Slider.tsx component code below.
type SliderProps = {
// swiperEl: RefObject<Swiper>;
// onIndexChanged: (index: number) => void;
index: number;
};
const Slider = ({ index }: SliderProps) => {
const swiperEl = useRef(null);
useEffect(() => {
swiperEl.current!.scrollTo(index);
}, [index, swiperEl]);
return (
<View style={styles.sliderContainer}>
<Swiper
scrollEnabled={false}
index={index}
ref={swiperEl}
style={styles.wrapper}
>
<View style={styles.slide1}>
<View style={[styles.circle, { backgroundColor: '#FF7F50' }]} />
</View>
<View style={styles.slide2}>
<View style={[styles.circle, { backgroundColor: '#FF6347' }]} />
</View>
<View style={styles.slide3}>
<View style={[styles.circle, { backgroundColor: '#FF4500' }]} />
</View>
</Swiper>
</View>
);
};
and here is my WelcomeScreenContainer.tsx
const WelcomeScreen = () => {
const [currentPage, setPage] = useState(0);
const navigation = useNavigation();
const handleNextClick = () => {
if (currentPage === 2) {
navigation.navigate('LoginScreen');
return;
}
setPage((prevPage) => prevPage + 1);
};
const handleSkipPress = () => navigation.navigate('LoginScreen');
console.log('Parent', currentPage);
return (
<View style={styles.parentContainer}>
<View style={styles.headerContainer}>
{/* <Text style={{ fontSize: 35 }}>WelcomeScreen</Text> */}
</View>
<Slider index={currentPage} />
<View style={{ flex: 1 }} />
<ButtonContainer
onNextPress={handleNextClick}
onSkipPress={handleSkipPress}
/>
</View>
);
};
Whenever I click the next button it should automatically slide the swiper to the next component or screen. However, on the first render when the user clicks the next button the swiperEl.current!.scrollTo(index); on the useEffect block of Slider.tsx does not work. But when the user clicks for the second time not it suddenly works.
I am a beginner in using React but I follow the documentation carefully on how to use the hooks of useRef and useEffect. Maybe I am missing something?
Appreciate it if someone could help.
Thanks
https://www.npmjs.com/package/react-native-swiper now supports scrollTo feature.
I havent seen this in official documentation but maybe i wasnt careful enough, Here is what worked for me in year 2022
...
const milestoneData = [1, 2, 3, 4, 5] //your data
const mileStoneSwiperRef = useRef(null);
const scrollMileStoneTo = (index) => {
mileStoneSwiperRef?.current?.scrollTo(index);
}
return (<View>
<View style={{padding: 10, backgroundColor: BaseColor.mainColor2}}>
<ScrollView showsHorizontalScrollIndicator={false} alwaysBounceHorizontal={true} horizontal={true} >
{milestoneData?.map((item, index) => <>
<Pressable onPress={() => scrollMileStoneTo(index)} style={{padding: 5, marginRight: 10, alignItems: 'center'}}>
<Image source={Images.math} style={[styles.contact, {width: 60, height: 60}]}/>
</Pressable></>)}
</ScrollView>
</View>
<Swiper
ref={mileStoneSwiperRef}
loop={false}
centeredSlides={false}
showsPagination={false}
bounces={true}
removeClippedSubviews={false}
>
{milestoneData?.map((item, index) => <View>
<View style={styles.groupHeadingViews}>
<Text fontResizeable bold style={styles.escrowGroupHeading}>{(`${tradeMethod} Name`).toUpperCase()}</Text>
<View style={styles.subTitleAndWordCount}>
<Text fontResizeable style={styles.escrowGroupSubHeading}>Briefly Name Your Country</Text>
<Text fontResizeable style={styles.escrowGroupSubHeading}>{milestoneData?.[index]?.name?.length}/30</Text>
</View>
</View>
</View>)}
</Swiper>
</View>)
...
The trick to get the scrollTo() an index done is the following method
const scrollMileStoneTo = (index) => {
mileStoneSwiperRef?.current?.scrollTo(index);
}

how to render a different component after every iteration of map function

I am fetching data from http://retailsolution.pk/api/allhome I want to display the title of the product and then all the child products below it, I am getting this output: Here's my code:
class App extends Component {
constructor(props) {
super(props);
this.state = {
Deals: []
};
}
componentWillMount() {
axios
.get("https://retailsolution.pk/api/allhome")
.then(response => this.setState({ Deals: response.data.Deals }));
}
_renderItem(item) {
return (
<View style={{ width: 100, height: 130 }}>
<Image
style={{ width: 100, height: 100 }}
source={{ uri: item.image }}
/>
<Text numberOfLines={1} style={{ flex: 1 }}>
{" "}
{item.name}
</Text>
</View>
);
}
renderTitle() {
return this.state.Deals.map(deal => (
<Text key={deal.parent.id} style={styles.text}>
{deal.parent.name}
</Text>
));
}
renderImage() {
return this.state.Deals.map(deal => (
<FlatList
key={deal.child.product_id}
style={{ marginTop: 5 }}
horizontal
ItemSeparatorComponent={() => <View style={{ width: 5 }} />}
renderItem={({ item }) => this._renderItem(item)}
data={[deal.child]}
/>
));
}
render() {
console.log(this.state.Deals);
return (
<View style={{ flex: 1, marginLeft: 8, marginRight: 8, marginTop: 10 }}>
{this.renderTitle()}
{this.renderImage()}
</View>
);
}
}
In my case {this.renderTitle()} gets execute first and maps every value from the api to the app and then {this.renderImage()} maps all flatlists to the app.
Is there any way I can run this.renderImage() after every iteration of rhis.renderTitle()?
You will have to do it using nested loop.
Try something like this -
{this.state.Deals.map(deal => {
return (
<div>
<Text key={deal.parent.id} style={styles.text}>
{deal.parent.name}
</Text>
{deal.child.map(item => {
return (
<FlatList
key={item.product_id}
style={{ marginTop: 5 }}
horizontal
ItemSeparatorComponent={() => <View style={{ width: 5 }} />}
renderItem={({ item }) => this._renderItem(item)}
data={[item]}
/>
);
})}}
</div>
);
})}
A way to do it is to call a function after the fetch that will create the section data for the Flatlist with the correct format:
sections = [{title: 'Latest', data:["The products data array"]}, {title: 'second section', data : ["Other products"]}, and so on...]`

Categories

Resources