Switch conditional statement inside map function - javascript

I am mapping over array of objects in react-native:
const status_image = [
{
image: require("../../assets/claimsImages/Question.png")
},
{
image: require("../../assets/claimsImages/Approved.png")
},
{
image: require("../../assets/claimsImages/In Process.png")
},
{
image: require("../../assets/claimsImages/Cancelled.png")
}
]
this.state = {
newdata: [],
images: status_image,
}
render(){
var img = this.state.images.map( val => val.image);
const claimData = this.state.newdata.map((val, index) => {
return (
<View key={index} style={{flexDirection: 'row', justifyContent: 'space-between', margin: 15,}}>
<View style={{ borderBottomWidth: 1,borderBottomColor: '#A6ACAF' }}>
<Text>EW</Text>
{val.claim_approval_status.map((status, i) => (
<View style={{marginVertical: 12}}>
<Image
style={styles.stretch}
source={img[i]}
/>
</View>
))}
</View>
<View>
{val.claim_approval_status.map((status, i) => (
<View style={{marginTop: 36}}>
<Text>{status}</Text>
</View>
))}
</View>
</View>
)
});
return (
<View>
{claimData}
</View>
)
}
What I want to achieve is the images should be mapped on the basis of status value i.e. if my val.claim_approval_status equals 'question', then the image 'Question.png' should be rendered in front of that status. If the val.claim_approval_status equals 'approved', then image be 'Approved.png'.
All these status values are mapped all together in a table along with the images, just the images need to appear as per their status values.
I tried following switch case approach likewise, but got confused implementing.
componentDidMount(){
this.state.newdata.map((x, i) => {
x.claim_approval_status.map((y, i) => {
let status = y;
console.log(status) //this gives four status values
'question, approved, inprocess,
cancelled'
});
});
var status_values = this.state.images.map((val, i) =>
{
switch (i) {
case 1:
// something to append that image in status data mapping
break;
case 2:
// something to append that image in status data mapping
break;
case 3:
// something to append that image in status data mapping
break;
}
}
// ...
);
}
How can I achieve the result? Any help appreciated.

Instead of switch case, you can apply simplest approach, by assigning object key as the status code, something like below:
const status_image =
{
question: require("../../assets/claimsImages/question.png"),
approved: require("../../assets/claimsImages/approved.png"),
inprocess: require("../../assets/claimsImages/inprocess.png"),
cancelled: require("../../assets/claimsImages/cancelled.png")
};
and once you have the response you can just use
<Image
style={styles.stretch}
source={status_image[status]}
/>

Related

Items in grid are not updating after I update the useState in react native applcation

I have a react native project. Part of my react native project is a grid that I created. When a user clicks on an item in the grid, I want to update an array that I have set in state const [selectedHomeTypes, setSelectedHomeTypes] = useState(['Houses']).
When I add an item to the array, I want it to udpate the grid so that all the items in the array have a blue background. If there was 1 item in selectedHomeTypes then 1 item in the grid has a blue background. If I add a second item to selectedHomeTypes, I want two items in the grid to have a blue background and sale with when I unselect an item that is deleted from the array.
What is hapening now is that selectedHomeTypes is being updated like normal so when an item that is not in the state is clicked on it is added to the array and same with unslelect. The issue is that the background colors of the items are not updating at all and no matter what happens, only the default item has blue background. Nothing is visually changing....
Main Screen:
const [selectedHomeTypes, setSelectedHomeTypes] = useState(['Houses'])
const updateSelectedHomeTypes = (selected) => {
let selectedHome = selectedHomeTypes
if(selectedHome.length == 0){
setSelectedHomeTypes(['Houses'])
}
if(selectedHome.includes(selected)){
let target = selectedHome.indexOf(selected)
selectedHome.splice(target, 1)
if(selectedHome.length == 0){
setSelectedHomeTypes(['Houses'])
} else {
setSelectedHomeTypes(selectedHome)
}
} else {
selectedHome.push(selected)
setSelectedHomeTypes(selectedHome)
}
}
return (
<View style={styles.filterContainer}>
<ScrollView style={styles.scrollContainer}>
<View style={styles.header}>
<Text style={styles.label}>
Filter
</Text>
<TouchableOpacity onPress={() => {resetFilter()}}>
<Text style={[styles.label, styles.blueText]}>
Reset
</Text>
</TouchableOpacity>
</View>
<View style={styles.sectionContainer}>
<FlatList
style={styles.homeTypeContainer}
data={homeTypeOptions1}
keyExtractor={(item) => item.key}
numColumns={numberOfColumns}
renderItem={(item) => {
return(
<GridItemComponent
item={item}
updateSelectedHomeTypes={updateSelectedHomeTypes}
selectedHomeTypes={selectedHomeTypes}
/>
)}}
/>
</View>
</ScrollView>
<TouchableOpacity onPress={() => {applyFilters()}}>
<View style={styles.buttonContainer}>
<Text style={styles.buttonText}>Apply Filters</Text>
</View>
</TouchableOpacity>
</View>
)
gridItemComponenet:
const GridItemComponent = (props) => {
const {
item,
updateSelectedHomeTypes,
selectedHomeTypes
} = props
return(
<>
{
selectedHomeTypes.includes(item.item.value) ? <TouchableOpacity style={styles.itemContainerBlue} onPress={() => {updateSelectedHomeTypes(item.item.value)}}>
<View style={styles.item}>
<Image style={styles.icon} source={item.item.image}/>
<Text style={styles.label}>{item.item.value}</Text>
</View>
</TouchableOpacity>
: <TouchableOpacity style={styles.itemContainer} onPress={() => {updateSelectedHomeTypes(item.item.value)}}>
<View style={styles.item}>
<Image style={styles.icon} source={item.item.image}/>
<Text style={styles.label}>{item.item.value}</Text>
</View>
</TouchableOpacity>
}
</>
)
}
list of property type options:
const numberOfColumns = 3
const homeTypeOptions1 = [
{
key: 1,
value: 'Houses',
image: require('./src/assets/home.png')
},
{
key: 2,
value: 'Condos',
image: require('./src/assets/building.png')
},
{
key: 3,
value: 'Lot/Land',
image: require('./src/assets/management.png')
},
{
key: 4,
value: 'Multi-family',
image: require('./src/assets/multi-family.png')
},
{
key: 5,
value: 'Manufactured',
image: require('.//src/assets/tiny-house.png')
},
{
key: 6,
value: 'Townhomes',
image: require('.//src/assets/townhouse.png')
}
]
as you can see in the image below, the selectedHomeTypes has 3 items in the array but only 1 item is highlighted. I am having trouble trying to update the background color of selected items dynamically
-----------UPDATE---------------------
how would I updated an array in useState if I want to eliminate an item from the array and have it rerender once the item was eliminated from the array. Keep in mind I have the index of the iteam I want to eliminate.
const updateSelectedHomeTypes = (selected) => {
let selectedHome = selectedHomeTypes
if(selectedHome.length == 0){
setSelectedHomeTypes(['Houses'])
}
if(selectedHome.includes(selected)){
let target = selectedHome.indexOf(selected)
selectedHome.splice(target, 1)
setSelectedHomeTypes(selectedHome)
} else {
setSelectedHomeTypes([...selectedHome, selected])
}
}
At a glance I think this is the problem:
When you do this:
selectedHome.push(selected)
setSelectedHomeTypes(selectedHome)
React doesn't re-render because you're setting selectedHomeTypes to the same array that's already in state.
You've pushed a new entry into it but React is doing an identity comparison between the old state and the new state to decide whether a rerender is needed. In this case newArray === oldArray so it doesn't trigger an update.
You could dodge this by spreading into a new array (and appending the new item instead of calling push):
setSelectedHomeTypes([...selectedHome, selected])
When updating state using its previous value, use the callback argument (to avoid stale state values).
https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
It should be
this.setState(state=>
({selectedHomeTypes: state.selectedHomeTypes.concat(selected)})
);
If you are updating a state property, using another state property, it's best to use class components.
State updates to arrays should be done using methods that return a new array (push does not while concat does).
See this comprehensive article to learn how to update state arrays in React.

I can't remove items from FlatList one by one by clicking via redux in react native

I've created a simple shopping app which has three screens including: Home, Book Store and Cart screens
I am using redux for updating all states, Everything works smoothly except one thing. I want the users when click on items in Cart they get removed one by one but the problem is that when i click on one item all of them get removed simultaneously
How can i fix this?
Reducer code:
import {
ADD,
REMOVE
} from '../actions/types';
const initialState = {
items: [],
counter: 0
}
export const cardReducer = (state = initialState, action) => {
switch (action.type) {
case ADD:
return {
...state, items: state.items.concat({
name: action.payload.name,
index: Math.random(),
price: action.payload.price,
id: action.payload.id
}), counter: state.counter + 1 }
break;
case REMOVE:
return {
...state,
items: state.items.filter((item) => {
item.index !== action.payload
}), counter: state.counter - 1 }
break;
default:
return state;
}}
Action code:
import {
ADD,
REMOVE
} from './types';
export const addSomething = (item) => {
return {
type: ADD,
payload: item
}}
export const removeSomething = (index) => {
return {
type: REMOVE,
payload: index
} }
And this is the Cart screen codes:
import { useDispatch, useSelector } from 'react-redux';
import { addSomething, removeSomething } from '../actions';
const Cards = (props) => {
const { navigation } = props;
const dispatch = useDispatch();
const items = useSelector(state => state.cardR.items)
return (
<View style={{ alignItems: 'center', flex: 1 }}>
<View style={{ width: '80%' }}>
<FlatList
data={items}
keyExtractor={(item, index) => index}
renderItem={({ item, index }) => (
<TouchableOpacity
style={styles.button}
activeOpacity={0.7}
onPress={(index) => dispatch(removeSomething(item.index))}
>
<Text style={{ color: 'white' }}>{item.name}</Text>
<Text style={{ color: 'white' }}>{item.price}</Text>
</TouchableOpacity>
)} />
</View>
</View>
)}
The problem seems in the index property, likewise #giotskhada has mentioned in his response. The possible solution could be to check the removable item with its id instead of the index, which is already there to uniquely identify the each item.
Try this instead -
Reducer Code:
case REMOVE:
return {
...state,
items: state.items.filter((item) => {
return item.id !== action.payload // Id instead of index and return statement
}),
counter: state.counter - 1
}
break;
Cart Screen Code:
<TouchableOpacity
style={styles.button}
activeOpacity={0.7}
onPress={(index) => dispatch(removeSomething(item.id))}> // id instead of index
<Text style={{ color: 'white' }}>{item.name}</Text>
<Text style={{ color: 'white' }}>{item.price}</Text>
</TouchableOpacity>
I believe the problem here is that index property is the same for all items.
Having randomly generated indices is a bad idea on its own, because you might end up with duplicate indices. Moreover, Math.random() is a pseudo-random floating-point number generator and shouldn't be trusted.
If you want to have a different index for each item, you have to do that another way. For example, you can have a idCounter state, which only increases by one when a new item is added. Then you can set new item's index to state.idCounter and be sure that it will be different from all the others.

How to update rendered flat/section list items immediately React Native

I am creating a ContactListScreen. The immediate child of ContactListScreen is ContactItems and ContactItems is a sectionList which renders each ContactItem.
But the problem arises, as my ContactItems should be multi-selectable.
I passed the array of selectedContacts from my state to every ContactItem. The logic here I used is ContactItem checks if the length of selectedContacts is 0 or not. If the length is zero it should not render any selectItemView, if I select an item, it should push itself to the selectedContacts using a callback. But the problem is the children components (ContactItem)s doesn't get updated until I selected deselect an item twice or thrice. How can I make it work?
Part of ContactList.tsx
class ContactList extends Component {
this.state = {
loading: false,
error: null,
data: [],
selectedItems: []
};
handleSelectionPress = (item) => {
this.setState(prevState => {
const { selectedItems } = prevState;
const isSelected = selectedItems.includes(item);
return {
selectedItems: isSelected
? selectedItems.filter(title => title !== item)
: [item, ...selectedItems],
};
});
};
renderItem(item: any) {
return <ContactItem item={item.item}
isSelected={this.state.selectedItems.includes(item.item)}
onPress={this.handleSelectionPress}
selectedItems={this.state.selectedItems}
/>;
}
render() {
return (
<View style={styles.container}>
<SectionList
sections={this.state.data}
keyExtractor={(item, index) => item.id}
renderItem={this.renderItem.bind(this)}
renderSectionHeader={({section}) => (
section.data.length > 0 ?
<Text>
{section.title}
</Text> : (null)
)}
/>
</View>
);
}
}
Part of ContactItem.tsx
class ContactItem extend Component {
render() {
const checkBox = <TouchableOpacity onPress={() => {
this.props.onPress(this.props.item)
}
} style={this.props.selectedItems.length > 0 && {display: 'none'}}>
{!this.props.isSelected ?
<View style={{borderRadius: 10, height: 20, width: 20, borderColor: "#f0f", borderWidth: 1}}>
</View> : <View style={{
borderRadius: 10,
height: 20,
width: 20,
borderColor: "#f0f",
borderWidth: 1,
backgroundColor: "#f0f"
}}>
</View>}
</TouchableOpacity>
return (
<View style={this.styles.contactsContainer}>
<TouchableOpacity
onLongPress={() => this.props.onPress(this.props.item)}>
<View style={this.styles.contactInfo}>
{checkBox}
</View>
</TouchableOpacity>
</View>
);
}
Note: Functional Components are not used where I work.
I'm not 100% certain about this, but I have a feeling that the problem is that the SectionList component isn't triggering its update because the supplied sections={this.state.data} property never changes.
The easiest way to handle this is to add the selectedItems as an extraData property to section list:
<SectionList
sections={this.state.data}
extraData={this.state.selectedItems}
//...

Is there a faster way to update a json array than by looping through it?

Until expo lets me use Realm databases ive decided to go with Json and asyncstorage. The source data originates from Json anyway so for this app it makes sense to leave it as json.
I have a flatlist with the json displayed. To the left of each item in the list is a star icon.
When I touch each item in the list the star will go solid to indicate it has been pressed. Press it again, the icon will be an outline of a star to indicate it has been de-selected.
The onpress function looks like this symbols is the name of the JSON data the JSON data has 3 keys... symbol, name and iconName. Symbol is the item in the flatlist that is touched.
onPressListItem = ( symbol ) => {
for (var i = 0; i < this.state.symbols.length; i++){
if (this.state.symbols[i].symbol === symbol){
const copyOfSymbolsList = [...this.state.symbols];
if (copyOfSymbolsList[i].iconName === 'md-star') {
copyOfSymbolsList[i].iconName = 'md-star-outline';
} else {
copyOfSymbolsList[i].iconName = 'md-star';
}
this.setState({ symbols:copyOfSymbolsList });
}
}
}
So as you can see above it basically just scrolls through the entire json array to find the appropriate row and then makes a copy of the list, changes the data and then sets state again.
The app isn't super fast maybe half a second delay before the icon changes on my pixel 2 and there is only 100 records in the list. Im a little worried if the list gets into the thousands of rows itll be really bad.
Is there a better/faster/simpler/more react-y way to solve this problem?
EDIT
This is the renderListItem function which calls the onPress function
renderListItem = ({ item }) => {
return (
<TouchableOpacity
onPress={() => this.onPressListItem(item.symbol)}
>
<View style={{flex: 1, flexDirection: 'row'}}>
<View style={{backgroundColor: 'powderblue'}}>
<Ionicons style={styles.listItemIcon} name={item.iconName} />
</View>
<View style={{backgroundColor: 'skyblue'}}>
<Text style={styles.listItem}>
{item.name.toUpperCase()} {item.symbol}
</Text>
</View>
</View>
</TouchableOpacity>
);
};
EDIT #2
This is the FlatList code.
<View style={styles.mainContainer}>
<FlatList
data={this.state.symbols}
keyExtractor= {(item, index) => item.symbol}
ItemSeparatorComponent={this.renderListSeparator}
renderItem={this.renderListItem}
/>
</View>
i guest your json structure is like this.
the_data = [
{'iconName': 'xxx', 'symbol': '<i class="fa fa-items"></i>'}
]
You can access this structure by index, passing the index to the onPressListItem function.
renderListItem = ({ item }, index) => {
return (
<TouchableOpacity
onPress={() => this.onPressListItem(index)}
>
<View style={{flex: 1, flexDirection: 'row'}}>
<View style={{backgroundColor: 'powderblue'}}>
<Ionicons style={styles.listItemIcon} name={item.iconName} />
</View>
<View style={{backgroundColor: 'skyblue'}}>
<Text style={styles.listItem}>
{item.name.toUpperCase()} {item.symbol}
</Text>
</View>
</View>
</TouchableOpacity>
);
};
With this design you don't have to iterate your json.
onPressListItem = ( index ) => {
the_dataobj = the_data[index];
the_dataobj.iconName = 'md-start';
if (the_dataobj.iconName === 'md-start'){
the_dataobj.iconName = 'md-start_outline';
}
//you don't need a extra else here, is in-necesarry
the_data[index] = the_dataobj;
//set state ... do more
}
BTW: This is have nothing to do with react, the correct design of your workflow should be independent of the framework library.
Happy Codding!!!
For those of you reading, the accepted answer did not update the rendered list properly on screen. So the actual working code for me is below. Im pretty sure its because the accepted answer did not use the this.setState function to write the values back to the array.
Also the if then else needs to have an else in it because when the user taps the same row twice i want it to reverse its changes.
All that being said, the updates are still very slow. About the same speed as it was when using the for loop in the question. Not sure why.
onPressListItem = ( index ) => {
const copyOfSymbolsList = [...this.state.symbols];
thePressedSymbol = copyOfSymbolsList[index];
if (thePressedSymbol.iconName === 'md-star') {
thePressedSymbol.iconName = 'md-star-outline';
}else{
thePressedSymbol.iconName = 'md-star';
}
copyOfSymbolsList[index] = thePressedSymbol;
this.setState({ symbols:copyOfSymbolsList });
}

How to do conditional rendering in react native

How to do conditional rendering in react native with more than 1 condition?
Following is some portion of my code
Index
.then(response => response.json())
.then((responseData) => {
this.setState({
progressData:responseData,
});
.....
......
render() {
const { progressData }= this.state;
return(
<View style={{flex:1}}>
<HeaderExample />
<View>
{progressData == "1"}
(<View>
<Text style={{fontSize:28,color:"#8470ff",fontWeight: 'bold',paddingTop:20,alignSelf:'center'}}>Pending</Text>
</View>)}
{ progressData == "2" &&
(<View>
<CardSection>
<Text style={{fontSize:28,color:"#8470ff",fontWeight: 'bold',paddingTop:20,alignSelf:'center'}}>InProgress </Text>
<View style={styles.buttonContainer}>
<Button
title="Report"
color="#8470ff"
onPress={() =>onPressReport()}
/>
</View>)}
But here it is for a single case means if responseData contains only one field. But now the reponseData contains 2 arrays. Each with 3 objects. So how do I check conditional rendering here?My responseData looks like this. I want to populate some UI on each condition. That means if status = 1 && work_type ="plumber" then render some UI.
Also if status = 2 && work_type="electrical" && assigend_to="worker_45" then render some ui. So how do I do this?
Please help
You can move your render in a new variable, or function. to keep clear the render function
render() {
const { progressData }= this.state;
return(
<View style={{flex:1}}>
<HeaderExample />
<View>
{renderProgressData(progressData)}
... //rest of your code
)
}
and in your renderProgressData function you can create a switch
renderProgressData = (progress) => {
switch(progress) {
case 1:
return (<View>1</View>)
case 2:
return (<View>1</View>)
// ... and so on
default:
return (<View>Default View</View>)
}
}
It is a little cleaner in this way for me.

Categories

Resources