How to Update state in return()? - react-native

I want to change the state when I get id of 5/6 or 7, after TouchableOpacity. Because I want to show the "Other Things" button one time whether id is 5/6 or 7.
export function HomePage({ navigation }) {
const [parentServiceCheck, setParentServiceCheck] = useState({ check: '0' });
return (
<View>
<FlatList numColumns={2} contentContainerStyle={{ margin: 7, flex: 1 }} keyExtractor={(item, index) => item.id} data={allService} renderItem={itemData => (
<View style={{ flexDirection: 'column', flex: 1 }}>
{
(itemData.item.id == 5 || itemData.item.id == 6 || itemData.item.id == 7) && parentServiceCheck.check == '0' ?
<TouchableOpacity onPress={() => pressHandler(itemData.item.id)} style={{ margin: 7 }}>
<Card titlebtn="Other Things" src={itemData.item.service_image} sty={{ height: 180, }} />
</TouchableOpacity>
// Now here I want to setParentServiceChildCheck({check: '1'}) when itemData.item.id has 5/6/7
:
<TouchableOpacity onPress={() => pressHandler(itemData.item.id)} style={{ margin: 7 }}>
<Card titlebtn={itemData.item.service_name} src={itemData.item.service_image} sty={{ height: 180, }} />
</TouchableOpacity>
}
</View>
)}
/>
</View>
)
}

You can't really update or execute any JS code like that. I would suggest to go through few training to learn about how things work in React JSX.
For now, if you want to update the state then you should be good to use it in useEffect as shown below
export default function HomePage({ navigation }) {
const [parentServiceCheck, setParentServiceCheck] = useState({ check: '0' });
useEffect(() => {
const hasValidId = allService.find(_hasValidId);
// This will get called only once after first render
// This will execute only when id's are 5, 6 or 7 which is your requirement
if (hasValidId) {
setParentServiceChildCheck({ check: '1' });
}
}, []);
const _hasValidId = itemData => {
const itemId = itemData?.item?.id;
const validItemIds = [5, 6, 7];
return validItemIds.includes(itemId);
};
const _renderItem = itemData => {
const itemId = itemData?.item?.id;
let content = null;
if (_hasValidId(itemData)) {
if (parentServiceCheck.check === '0') {
content = (
<TouchableOpacity onPress={() => pressHandler(itemId)} style={{ margin: 7 }}>
<Card titlebtn="Other Things" src={itemData.item.service_image} sty={{ height: 180 }} />
</TouchableOpacity>
);
} else {
content = (
<TouchableOpacity onPress={() => pressHandler(itemId)} style={{ margin: 7 }}>
<Card
titlebtn={itemData.item.service_name}
src={itemData.item.service_image}
sty={{ height: 180 }}
/>
</TouchableOpacity>
);
}
}
return <View style={{ flexDirection: 'column', flex: 1 }}>{content}</View>;
};
return (
<View>
<FlatList
numColumns={2}
contentContainerStyle={{ margin: 7, flex: 1 }}
keyExtractor={(item, index) => item.id}
data={allService}
renderItem={_renderItem}
/>
</View>
);
}
FYI, you might need to change few lines as the whole component code is not shared and I don't know about your business logic.

You should never update state in return / render function. It will make infinite rendering loop.

Related

How can I get current index through FlatList?

I'm passing data to details screen with Navigation params, everything works correct, the only thing that I want to pass clicked current level index to Object.entries[currentIndex], I'm not sure how can I get that.
<FlatList
data={data}
keyExtractor={(item) => item.index}
renderItem={({ item, index }) => (
<View>
{item.sTask?.map((levels, i) => (
<View>
<TouchableOpacity
onPress={() => navigation.navigate('Tasks', { item })}
style={{
backgroundColor: '#33333325',
paddingHorizontal: 20,
paddingVertical: 10,
borderRadius: 10,
marginVertical: 20,
marginHorizontal: 20,
}}
>
<Text>{Level 1}</Text>
</TouchableOpacity>
<TouchableOpacity>
{Another level...}
</TouchableOpacity>
</View>
))}
</View>
)}
/>
Details screen:
const [currentTaskGroupIndex, setCurrentTaskGroupIndex] = useState(0); <---- We need to set current index somehow to current clicked index
//Test item params data
useEffect(() => {
const mapData = item?.sTask?.map((tasks) => {
return Object.entries(tasks)[currentTaskGroupIndex].map((level) => {
// console.log(level);
});
});
}, [item]);
No matter which level i clicked, i'm getting Level 1 details
You need to do like :
onPress={() => navigation.navigate('Tasks', { item:item, index:i })}
and then in details screen:
const indexParam = props?.route?.params?.index ?? 0
const [currentTaskGroupIndex, setCurrentTaskGroupIndex] = useState(indexParam); <---- We need to set current index somehow to current clicked index
//Test item params data
useEffect(() => {
const mapData = item?.sTask?.map((tasks) => {
return Object.entries(tasks)[currentTaskGroupIndex].map((level) => {
// console.log(level);
});
});
}, [item]);
Hope it helps. feel free for doubts
You can pass index of the task in navigation param
onPress={() => navigation.navigate('Tasks', { item, index: i })}
Now, you no need map() to find your task
const mapData = item?.sTask[index]
you can pass your whole object in the navigation params
Thank you guys for you answers, you were right, and plus i jusd added another Object.key().map and it worked
{item.sTask?.map((levels, i) => (
<View key={i}>
{Object.keys(levels).map((key, i) => (
<View key={i}>
{console.log(key)}
<TouchableOpacity
onPress={() =>
navigation.navigate('Tasks', { item: item, index: i })
}
style={{
backgroundColor: '#33333325',
paddingHorizontal: 20,
paddingVertical: 10,
borderRadius: 10,
marginVertical: 20,
marginHorizontal: 20,
}}
>
<Text>{key}</Text>
</TouchableOpacity>
</View>
))}
```

app crashes because of Flat-List API calling

I'm calling API and seeing the respective API values in the log, it shows me correct values, but when I try to set API in Flat list with the help of hooks my app crashes. I don't know the reason as I'm new in react native, so any help regarding this would be really appreciated.
NOTE( If I'm displaying the values directly without flat list it won't cause any error)
function Item({ item }) {
const navigation = useNavigation();
return (
<TouchableOpacity style={styles.listItemBox}
onPress={() => navigation.navigate('PatientDemographics')}
>
<View style={{flex:1}}>
<Text numberOfLines={1} style={{ textAlign: 'left', fontSize: 25, color:"#075430", textAlign: 'center',fontFamily:"Montserrat-Regular"}}>{item.firstName}</Text>
<TouchableOpacity style={[styles.smallRoundedBlueRoundedNoMargin,{marginTop:10,marginBottom:40}]}
onPress={() => navigation.navigate('PatientDemographics')} >
<Text style={[styles.cardText,{fontSize: 18},{color: 'white'}]}>SELECT </Text>
</TouchableOpacity>
</View>
</TouchableOpacity>
);
}
const SelectPatient = () => {
let numColumns = 4;
const formatData = (data, numColumns) => {
const numberOfFullRows = Math.floor(data.length / numColumns);
let numberOfElementsLastRow = 8 - (numberOfFullRows * numColumns);
while (numberOfElementsLastRow !== numColumns && numberOfElementsLastRow !== 0) {
data.push({ key: `blank-${numberOfElementsLastRow}`, empty: true });
numberOfElementsLastRow++;
}
return data;
};
// const navigation = useNavigation();
const [isLoading, setLoading] = useState(true);
const [patient, setPatient] = useState([]);
const mrnum=89
useEffect(() => {
axios({
method: 'get',
url: `https://emr-system.000webhostapp.com/emrappointment/emrappointment/patient/search?mrnum=89&cnic=&qrcode=`,
}).then((response) => {
//Balance / transaction-list
setPatient(response.data.result);
console.log(response.data.result);
console.log(patient[0].patientId);
}).then(() => setLoading(false));
}, []);
return (
<View style={styles.container}>
<Header name="Select Patient" class= ""/>
<UnitClerkHeader/>
<PatientHeader/>
<View style= {{flex:1 ,width: '100%', alignSelf: 'center'}}>
<SafeAreaView style={{flex:1}} >
<FlatList
style={{flex:1, marginTop: 30, marginRight:30,marginLeft:30}}
data={ formatData(patient,numColumns)}
renderItem={({ item }) => <Item item={item}/>}
keyExtractor={item => item.patientId}
numColumns = {numColumns}
/>
</SafeAreaView>
</View>
</View>
);
}
export default SelectPatient;
You can try with
<FlatList
style={{ flex: 1, marginTop: 30, marginRight: 30, marginLeft: 30 }}
data={() => formatData(patient, numColumns)}
renderItem={({ item }) => <Item item={item} />}
keyExtractor={item => item.patientId}
numColumns={numColumns}
/>
I can help you better when you show your error too.

React native carousel item active index

I've just started learning react native and I want to make a splash screen that contains a slider with dots. But when I scroll the scrollview the active dot doesn't change properly. My slider component is like below
export default function Slider() {
const [active, setActive] = useState(0);
const onChange = ({ nativeEvent }) => {
const active = Math.floor(
nativeEvent.contentOffset.x / nativeEvent.layoutMeasurement.width
);
setActive({ active });
console.log(active);
};
return (
<View style={styles.container}>
<ScrollView
onMomentumScrollEnd={onChange}
horizontal
pagingEnabled
showsHorizontalScrollIndicator={false}
>
{dummyData.map((item, index) => {
return (
<SliderItem
key={index}
title={item.title}
image={item.url}
description={item.description}
/>
);
})}
</ScrollView>
<View style={styles.dotView}>
{dummyData.map((k, i) => (
<View
key={i}
style={{
backgroundColor: i === active ? "red" : "blue", // My problem is here
height: 10,
width: 10,
margin: 8,
borderRadius: 6,
}}
/>
))}
</View>
</View>
);
}
Change setActive({ active }); to setActive(active);

How to update a single item in FlatList - React Native

In my case I am writing a Facebook clone like application, but much simpler. I put each item in FlatList and render them.
To "like" a post, I press the "like" button on the post and the "like" button turns yellow and the likes count increase by 1 ( Also I call addLike API Function after clicking ), I press it again, it turns gray and likes count decrease by one ( Also I Call removeLike API Funtion ).
This is what I have so far: I store all the loaded posts with redux - this.props, each post has a property called "liked", which is boolean, indicating whether this user has liked this post or not, when user presses "like", Now I doing when user like the post I call the addLike() action and fetch all the posts to feed again. I want to do this without fetching data again and again
FlatList Component
<FlatList
style={styles.postList}
data={this.props.postData}
extraData={this.props}
maxToRenderPerBatch={10}
keyExtractor={item => {
return item.id;
}}
ItemSeparatorComponent={() => {
return <View style={styles.separator} />;
}}
renderItem={post => {
const item = post.item;
this.state.userisLiked = item.likedUsers.find(
user => user.id == this.state.userDetails.id,
);
// console.log('Returning Is Liked ', isLiked);
return (
<View style={styles.card}>
<View>
{item.postImage ? (
<TouchableOpacity
onPress={() =>
this.showSelectedImageFullView(item.postImage)
}>
<ImageBackground
style={styles.cardImage}
source={{
uri: Strings.AWSS3_POST_IMAGE + item.postImage,
}}>
<View style={styles.overlay} />
</ImageBackground>
</TouchableOpacity>
) : (
<View></View>
)}
</View>
<View style={{flexDirection: 'row'}}>
<Image
source={
item.user.profilePicture
? {
uri:
Strings.AWSS3_USER_PROFILE_AVATAR +
item.user.profilePicture,
}
: Images.IMAGE_PLACEHOLDER
}
style={styles.postUserImage}
/>
<View style={{flexDirection: 'column'}}>
<Text style={styles.postUserName}>
{item.user.firstName} {item.user.lastName}
</Text>
<TimeAgo
style={styles.postedTime}
time={item.createdAt}
interval={20000}
/>
</View>
<TouchableOpacity
style={styles.postMoreInfoIcon}
onPress={() => this.toggleModal(item)}>
<Image
source={Images.POST_MORE_INFO}
style={styles.postMoreInfoIcon}
/>
</TouchableOpacity>
</View>
<TouchableOpacity onPress={() => this.homeMoreInfoScreen(item)}>
<View style={{flexDirection: 'column'}}>
<Text style={styles.postTitle}>{item.title}</Text>
<Text style={styles.postBody} numberOfLines={2}>
{item.description}
</Text>
</View>
</TouchableOpacity>
<View style={styles.cardFooter}>
<View style={{flexDirection: 'row'}}>
<TouchableOpacity
onPress={() => {
this.handleUserLikes(item);
}}>
{this.state.userisLiked ? (
<Image
source={Images.POST_LIKE_CHECKED}
style={{
width: 20,
height: 20,
resizeMode: 'contain',
}}
/>
) : (
<Image
source={Images.POST_LIKE_UNCHECKED}
style={{
width: 20,
height: 20,
resizeMode: 'contain',
}}
/>
)}
</TouchableOpacity>
<Text
selectable={true}
onPress={() => this.toggleLikeModal(item)}
style={{
fontFamily: AppStyles.primaryFont,
fontSize: 15,
color: AppStyles.colorWhite,
marginLeft: 5,
}}>
{item.likesCount} Likes
</Text>
</View>
<View style={{flexDirection: 'row', marginLeft: 20}}>
<TouchableOpacity
onPress={() => this.homeMoreInfoScreen(item)}>
<Image
source={Images.POST_COMMENT}
style={{width: 20, height: 20, resizeMode: 'contain'}}
/>
</TouchableOpacity>
<Text
selectable={true}
onPress={() => this.homeMoreInfoScreen(item)}
style={{
fontFamily: AppStyles.primaryFont,
fontSize: 15,
color: AppStyles.colorWhite,
marginLeft: 5,
}}>
{item.commentsCount} Comments
</Text>
</View>
<View
style={{
flexDirection: 'row',
marginLeft: 10,
position: 'absolute',
right: 100,
top: 20,
}}>
</View>
<View
style={{
flexDirection: 'row',
marginLeft: 10,
position: 'absolute',
right: 10,
top: 20,
}}>
<TouchableOpacity
onPress={() => this.homeMoreInfoScreen(item)}>
<Text
style={{
fontFamily: AppStyles.primaryFont,
fontSize: 15,
color: AppStyles.colorWhite,
}}>
Comment
</Text>
</TouchableOpacity>
</View>
</View>
</View>
);
}}
/>
Handle User Like Action
//Add Likes to selected post click listner
handleUserLikes = item => {
//Check user already liked the post
const isLiked = item.likedUsers.find(
user => user.id == this.state.userDetails.id,
);
if (isLiked) {
this.props.removeLikeFromPost(item.id, this.state.user_token);
this.props.fetchPostData(this.state.user_token);
} else {
this.props.addLikeToPost(
item.id,
this.state.user_token,
this.state.userDetails.id,
);
this.props.fetchPostData(this.state.user_token);
}
};
Assuming that the items are available as props. You can handle the data alteration in the reducer instead of your React component. first what you have to do is
handleUserLikes = item => {
this.props.addLikeToPost({
item_id:item.id,
user_token:this.state.user_token,
user_id:this.state.userDetails.id,
});
};
inside your redux code fire a function that handles the logic.
const userLike =(state,payload)=>{
let Newitem = null;
let item= state.item.find(
user => item.id == payload.item_id
);
let itemIndex = state.item.findIndex(
user => user.id == payload.item_id
);
let isLiked = item.likedUsers.find(user=>user.id===payload.user_id);
if(isLiked){
Newitem = {...item,likedUsers:item.likedUsers.filter(user=>user.id!==payload.user_id)}
} else{
Newitem = {...item,likedUsers:[...item.likedUsers,payload.user_id]}
}
let Newitems = [
state.items.slice(0, itemIndex),
Newitem,
state.items.slice(++itemIndex)
];
return {...state,items:Newitems}
}
This method userLike should be called inside the reducer switch statement which corresponds to your particular action. like this
function appReducer(state = initialState, action) {
switch (action.type) {
.........
case 'YOU_ACTION' :
return userLike(state,action);
}
}
In this manner, you don't have to fetch the items data again and again. But make sure you send the data to the back end saying the post is liked or unliked.
const addLikeToPost = (data) => dispatch => {
// you can send a request to your back end here without Awaiting.
// data is passed from the addLikeToPost method called from you react component.
dispatch({action:'YOU_ACTION',payload:data});
}
First of all thanks to #TRomesh for his answer.
Based on that I found a solution for this issue easily
My Button Action Call
handleUserLikes = item => {
this.props.addLikeToPost({
item_id:item.id,
user_token:this.state.user_token,
user_id:this.state.userDetails.id,
});
};
Redux Reducer Function -> This is the part I edited from above answer
const userLike = (state, payload) => {
console.log('TCL: userLike -> payload', payload.payload);
console.log('TCL: userLike -> state.postData', state.postData);
let item = state.postData.find(user => user.id == payload.payload.item_id);
let local_data = state.postData;
let isLiked = item.likedUsers.find(
user => user.id === payload.payload.user_id,
);
if (isLiked) {
local_data = local_data.map(data => {
if (data.id == payload.payload.item_id) {
data.likesCount = Number(data.likesCount - 1);
}
return data;
});
} else if (isLiked == undefined || isLiked == null) {
local_data = local_data.map(data => {
if (data.id == payload.payload.item_id) {
data.likesCount = Number(data.likesCount + 1);
}
return data;
});
}
return {...state, postData: local_data};
};
Cheers !

How to reload Flatlist react native?

I have a rendered list and I would like it when a change in status is generated the entire FlatList is recharged
what I have is a search engine and I want that when it brings the new data the FlatList will refresh itself with the new information
I need that every time you enter a letter in the search engine the FlatList will recharge
async buscar(buscar) {
this.setState({refreshing: true});
const coins = await ajax.searchProd(buscar);
this.setState({coins});
console.log(coins);
this.setState({refreshing: false});
}
render() {
return (
<View style={styles.container}>
<Image style={styles.logoHeader} source={require('../../public/images/productos.png')}/>
<View style={styles.containerSearch}>
<TextInput
style={styles.inputBuscar}
placeholder={'Buscar Productos'}
underlineColorAndroid={"transparent"}
onChangeText={(buscar) => {
this.setState({buscar: buscar});
this.buscar(this.state.buscar);
console.log(this.state.buscar);
}}
/>
<Image style={styles.imageSearch} source={require('../../public/images/search.png')}/>
</View>
<View style={styles.containerPro}>
<FlatList
extraData={this.state.coins}
data={this.state.coins}
keyExtractor={(item) => item.id.toString()}
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this._onRefresh}
/>
}
renderItem={({item}) => {
let url = 'http://laeconomiadelsur.com/upload/' + item.foto;
const tableData = [
[<View>
<Image source={{uri: url}} style={{
width: 50,
height: 70,
}}/>
</View>,
],
[<View style={styles.cellIcons}>
<Text
style={styles.textCells}>{item.nombre}</Text>
<Text
style={styles.textCellsDescription}>{item.descripcion}</Text>
<Text
style={styles.textCellsPrecio}>{numeral(item.precio).format('$0,0')}</Text>
</View>,
],
[<View>
<Text style={styles.cantidad1}>seleccionar: </Text>
<View>
<NumericInput
onChange={(value) => {
let total = value * item.precio;
console.log(value);
console.log(total)
}}
totalWidth={calcSize(250)}
totalHeight={calcSize(70)}
minValue={0}
iconSize={15}
step={1}
sepratorWidth={0}
borderColor={'transparent'}
inputStyle={{
backgroundColor: 'rgb(252,226,227)',
borderTopColor: 'rgb(226,0,30)',
borderBottomColor: 'rgb(226,0,30)',
borderWidth: 1,
fontFamily: "NeutraText-BookSC",
fontSize: 17
}}
containerStyle={{
borderWidth: 1,
borderColor: 'rgb(226,0,30)',
backgroundColor: 'transparent',
}}
rounded
textColor='#000000'
rightButtonBackgroundColor={'rgb(235,209,210)'}
leftButtonBackgroundColor={'rgb(235,209,210)'}
/>
</View>
<Text style={styles.cantidad}>cantidad</Text>
</View>,]
];
return (
<View style={{flex: 1}} key={item.id}>
<Table style={styles.table} borderStyle={{borderWidth: 2, borderColor: 'white'}}
key={item.id}>
<Cols data={tableData} flexArr={[1, 2, 2]} style={styles.cells}
key={item.id}/>
</Table>
</View>
);
}}
/>
</View>
</View>
);
<TextInput
style={styles.inputBuscar}
placeholder={'Buscar Productos'}
underlineColorAndroid={"transparent"}
onChangeText={(buscar) => {
/* Updated buscar value won't be available in next line instantly
* just after the setState. So, better set buscar State in
* this.buscar() Function.
*/
/* this.setState({buscar: buscar}); Move to buscar Function*/
this.buscar(buscar).then(()=>{
console.log(this.state.buscar);
}).catch((e)=> console.log("Error: ",e))
}}
/>
An Async function always returns a promise object. So, while calling buscar function you should handle the promise.
async buscar(buscar) {
this.setState({
refreshing: true,
buscar: buscar,
});
const coins = await ajax.searchProd(buscar);
console.log(coins);
this.setState({
coins: conins,
refreshing: false
});
}
In Flatlist ExtraData property should contain the this.state
extraData={this.state}