When swiping the row in the SwipeableListView I want to delete the rowitem and re-render the list.
What is now happening is that always the last item in the list is removed, not the item that is swiped.
Any ideas what is wrong?
export default class SwipeList extends Component {
constructor(props) {
super(props);
let ds = SwipeableListView.getNewDataSource();
this.favourites = []
this.state = {
ds:[],
dataSource:ds,
isLoading:true,
closeRow:false,
};
}
componentWillMount () {
store.get('KEY_FAV').then(value => {
typeof(value) === 'object'
? this.favourites = Object.keys(mockdata.favourite)
: this.favourites = JSON.parse(value)
this.setState({
dataSource: this.state.dataSource.cloneWithRowsAndSections(this.genData(this.favourites
)),
isLoading:false
})
})
}
genData = (list) => {
let dataBlob = []
for(let i = 0; i <list.length; i++) {
dataBlob.push({id:list[i], name:list[list[i]]})
}
return [dataBlob, []]
}
Till here it is okay, the SwipeableList is loaded with all RowItems.
But in the below handleSwipeAction() while setting new state for dataSource, the list will only delete the last item, not the selected.
handleSwipeAction = (rowData, SectionID, rowID) => {
AlertIOS.alert('Remove ' + rowData.name + ' \nfrom Favourites?', null,
[
{text:'Cancel', onPress: () => {this.setState({closeRow:true})}, style:'cancel'},
{text:'OK', onPress: () => {
this.favourites.slice()
this.favourites.splice(rowID, 1)
this.setState({
closeRow:true,
})
this.setState({//I THINK HERE IS THE PROBLEM
dataSource:this.state.dataSource.cloneWithRowsAndSections(this.genData(this.favourites))
})
store.set('KEY_FAV', this.favourites)
}}
])
}
onSwipe = (rowData, SectionID, rowID) => {
return (
<View style={styles.actionsContainer}>
<TouchableHighlight
onPress={() => this.handleSwipeAction(rowData, SectionID, rowID)}>
<Text style={styles.actionsItem}>Remove</Text>
</TouchableHighlight>
</View>
);
};
and the render function
render() {
if(this.state.isLoading) return null
return (
<View style={styles.container}>
<SwipeableListView
bounceFirstRowOnMount
enableEmptySections={true}
dataSource={this.state.dataSource}
maxSwipeDistance={this.props.swipeDistance}
renderRow={(item) => this.renderItem(item)}
renderQuickActions={this.onSwipe}
renderSeparator={this.renderSeperator}
doCloseRow={this.state.closeRow}
/>
</View>
);
}
when you are done slicing, I believe if you do:
let ds = SwipeableListView.getNewDataSource(); all over again, and then
this.setState({ dataSource: ds.cloneWithRowsAndSections(this.genData(this.favourites)) })
It should work. For a reason that I still don't get. Also I don't know why you do two setState() in your function. One is enough no?
So this should work:
handleSwipeAction = (rowData, SectionID, rowID) => {
AlertIOS.alert('Remove ' + rowData.name + ' \nfrom Favourites?', null,
[
{text:'Cancel', onPress: () => {this.setState({closeRow:true})}, style:'cancel'},
{text:'OK', onPress: () => {
this.favourites.slice()
this.favourites.splice(rowID, 1)
let ds = SwipeableListView.getNewDataSource(); // add this
this.setState({ dataSource: ds.cloneWithRowsAndSections(this.genData(this.favourites)), closeRow:true })
store.set('KEY_FAV', this.favourites) }} ])
}
Related
I'm trying to display the time zone in another screen when an item is pressed in previous flatlist. My data is coming from autocomplete when I'm selecting it is displayed in flatlist.
<Autocomplete
autoCapitalize="none"
autoCorrect={false}
containerStyle={styles.autocompleteContainer}
data={autotime.length === 1 && comp(query, autotime[0].name) ? [] : autotime}
defaultValue={this.state.timeZone}
onChangeText={text => this.setState({ query: text })}
placeholder="Enter Location"
renderItem={({ name, release_date }) => (
<TouchableOpacity onPress={() => this.setState({ query: name,timezoneArray:autotime[0].timezones })}>
<Text style={styles.itemText}>
{name}
</Text>
</TouchableOpacity>
)}
/>
<View style={styles.descriptionContainer}>
{autotime.length > 0 ? (
<FlatList
style={{flex:1}}
data={this.state.timezoneArray}
renderItem={({ item }) => <TimeZoneItem text={item} />}
/>
) : (
<Text style={styles.infoText}>Enter Location</Text>
)}
I want that when I press the items of flatlist it is displayed on another page.
Picture below shows what I have made:
My Data base helper class is:
export const SaveItem = (key, value) => {
AsyncStorage.setItem(key, value);
};
export const ReadItem = async (key) => {
try {
var result = await AsyncStorage.getItem(key);
return result;
} catch (e) {
return e;
}
};
export function MultiRead(key, onResponse, onFailure) {
try {
AsyncStorage.multiGet(key).then(
(values) => {
let responseMap = new Map();
values.map((result, i, data) => {
let key = data[i][0];
let value = data[i][1];
responseMap.set(key, value);
});
onResponse(responseMap)
});
} catch (error) {
onFailure(error);
}
};
export async function DeleteItem(key) {
try {
await AsyncStorage.removeItem(key);
return true;
}
catch (exception) {
return false;
}
}
and here i have added my code to save
handleTimezone = (text) => {
this.setState({ TimeZoneItem: text })
}
newData.TimeZoneItem = this.state.TimeZoneItem
this.setState({
TimeZoneItem: '',
})
ReadItem('timeData').then((result) => {
let temp = []
if (result != null) {
temp = JSON.parse(result)
} else {
temp = []
}
temp.push(newData)
SaveItem('timeData', JSON.stringify(temp))
console.log(`New Data: ${JSON.stringify(temp)}`)
}).catch((e) => {
})
}
<FlatList
style={{flex:1}}
data={this.state.timezoneArray}
renderItem={({ item }) => (
<TouchableOpacity>
<TimeZoneItem text={item} onPress={() => this.props.onPress()}
value={this.state.TimeZoneItem}
/>
</TouchableOpacity>)}
You'll need an array to saved all the saved items.
for example
state = {
saved: []
};
On press the time zone item, get the values from state, add the new item to the array and save the array to async storage using JSON.stringify()
onSave = item => {
const { saved } = this.state;
const newItems = [...saved, item];
this.setState({
saved: newItems
});
const items = JSON.stringify(newItems);
SaveItem("saved", items)
.then(res => {
console.warn("saved", res);
})
.catch(e => console.warn(e));
};
Then in your other screen get the items in using your ReadItem function like.
state = {
saved: []
};
componentDidMount = () => {
ReadItem("saved")
.then(res => {
if (res) {
const saved = JSON.parse(res);
this.setState({
saved: saved
});
}
})
.catch(e => console.warn(e));
};
Working Demo
I want to select only one checkbox, not multiple.
If i select two checkboxes one by one the previously selected checkbox should be unselected.
In my below code i can select multiple checkboxes.
import React ,{Component} from "react";
import CircleCheckBox, {LABEL_POSITION} from "react-native-circle-checkbox";
class Select_Delivery_Option extends React.Component {
constructor(props) {
super(props);
const ds = new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2
});
this.state = {
check_data:[],
dataSource: ds.cloneWithRows([]),
checked:false,
isLoading:false,
};
}
//I had call The componentDidMount for json Data here and bind it in Data source;
render() {
return ();
}
_renderRow(rowData: string, sectionID: number, rowID: number) {
return (
<View style={{ flex:1,flexDirection:'column',backgroundColor:'#FFF'}}>
<View style={{ flex:1,flexDirection:'row',backgroundColor:'#FFF'}}>
<View style={{flexDirection:'column',margin:10}}>
{rowData.adbHomeAddress}
<CircleCheckBox
checked={rowData.checked}
onToggle={()=>this._onPressRow(rowID, rowData,rowData.checked)}
labelPosition={LABEL_POSITION.LEFT}
label={rowData.Address1 +" ,\n "+ rowData.Address2 +",\n"+rowData.ctiName+", "+rowData.staName+", "+rowData.ctrName+","+rowData.adbZip+"."}
innerColor="#C72128"
outerColor="#C72128"
styleLabel={{color:'#000',marginLeft:10}}
/>
</View>
</View>
</View>
);
}
_onPressRow = (rowID,rowData,checked) => {
const {check_data,filter} = this.state;
console.log('rowdata',rowData);
console.log('rowid',rowID);
console.log('checked',checked);
rowData.checked = !rowData.checked;
var dataClone = this.state.check_data;
dataClone[rowID] = rowData;
this.setState({check_data: dataClone });
}
}
Link to the CircleCheckBox component used: https://github.com/paramoshkinandrew/ReactNativeCircleCheckbox
I had the same requirement and wasted hours looking for solution. Eventually, I was able to resolve the problem on my own.
Posting my answer below, l have used hooks in the example, let me know if someone wants a class-based solution.
const checkboxComponent = () => {
const [checkboxValue, setCheckboxValue] = React.useState([
{ label: 'Customer', value: 'customer', checked: false },
{ label: 'Merchant', value: 'merchant', checked: false },
{ label: 'None', value: 'none', checked: false },
])
const checkboxHandler = (value, index) => {
const newValue = checkboxValue.map((checkbox, i) => {
if (i !== index)
return {
...checkbox,
checked: false,
}
if (i === index) {
const item = {
...checkbox,
checked: !checkbox.checked,
}
return item
}
return checkbox
})
setCheckboxValue(newValue)
}
return (
<View>
{checkboxValue.map((checkbox, i) => (
<View style={styles.checkboxContainer} key={i}>
<CheckBox
value={checkbox.checked}
onValueChange={(value) => checkboxHandler(value, i)}
/>
<Text style={styles.label}>{checkbox.label}</Text>
</View>
))}
</View>
)
}
export default checkboxComponent
I suggest you to use FlatList instead of ListView it's more advance and easy to use component.
For your issue please create a state checkedItem: -1 and directly assign id of your item you check last then just add a check to your CircleCheckBox item. something like below code.
<CircleCheckBox
checked={rowData.id === this.state.checkedItem}
onToggle={(rowID)=> this.setState({ checkedItem: rowID})}
labelPosition={LABEL_POSITION.LEFT}
label={rowData.Address1 +" ,\n "+ rowData.Address2 +",\n"+rowData.ctiName+", "+rowData.staName+", "+rowData.ctrName+","+rowData.adbZip+"."}
innerColor="#C72128"
outerColor="#C72128"
styleLabel={{color:'#000',marginLeft:10}}
/>
Let me know if any query.
I get id is not defined in fetchEvents, when trying to use button.value as a parameter.
I am using mapDispatchToProps and mapStateToProps in my component.
const mapDispatchToProps = (dispatch) => {
return {
resetForm: () => dispatch(resetForm()),
fetchEvents: setSubCategory => dispatch(fetchEvents(id))
};
};
const mapStateToProps = state => {
return {
setCredentials: state.setCredentials,
categories: state.fetchCategories,
isLoading: state.isLoading
};
};
I then destructure my props to get my id:
const {
fetchEvents,
resetForm,
isLoading,
setCredentials: { setStudent, setGroup, setYear }
} = this.props;
const id = setStudent || setGroup || setYear;
However, when I dispatch my action:
const buttonOptions = [
{
key: 0,
label: "refresh",
value: Id,
icon: "undo"
},
{
key: 1,
label: "back",
value: Id,
icon: "caret-left"
}
];
return (
<View style={styles.container}>
{buttonOptions.map((button, i) => {
const style =
i == 0 ? styles.divContainerLeft : styles.divContainerRight;
return (
<View style={style} key={"view" + i}>
<TouchableOpacity
disabled={isLoading ? true : false}
key={"TouchableOpacity" + i}
// dispatch action here
onPress={i == 0 ? () => fetchEvents(button.value) : resetForm}
>
<Icon
name={button.icon}
style={styles.button}
color="white"
key={"icon" + i}
size={30}
/>
</TouchableOpacity>
</View>
);
})}
</View>
);
In your mapDispatchToProps, you are re-declaring the parameter name to setSubCategory, yet, in your fetch call you pass id.
Instead, try this:
const mapDispatchToProps = (dispatch) => {
return {
resetForm: () => dispatch(resetForm()),
fetchEvents: (setSubCategory) => dispatch(fetchEvents(setSubCategory))
};
};
OR
const mapDispatchToProps = (dispatch) => {
return {
resetForm: () => dispatch(resetForm()),
fetchEvents: (id) => dispatch(fetchEvents(id))
};
};
This is because you are declaring and inlining fetchEvents as an anonymous function. The parameter names must match.
my problem is quite simple but I'm new to react native dev. I'd like to save multiple elements with AsyncStorage (I'm using react-native-simple-store
a library that works like a wrapper but it's same logic) I want display all items for a key in a list , my code look like this:
constructor(props) {
super(props)
this.state = {
UserInput: "",
}
}
SaveValue = () => {
store.push('Favorites', this.state.UserInput)
Keyboard.dismiss()
};
FetchValue = () => {
store.get('Favorites').then((value) => {
this.setState({
favs: value
});
}).done();
};
Same thing with AsynStorage, it just update the item which is not my goal, I'd like to add a new one
SaveValue = () => {
AsyncStorage.setItem("Favorites", this.state.UserInput);
Keyboard.dismiss()
};
FetchValue = () => {
AsyncStorage.getItem("Favorites").then((value) => {
this.setState({
favs: value
});
}).done();
};
This part is my view where I try to display data, you can also see that I use a text input and two buttons one to save and the other to display an array of items stored
render() {
return (
<View>
<TextInput
onChangeText={(UserInput) => this.setState({UserInput})}
placeholder= "Type something"
value={this.state.UserInput} />
<TouchableOpacity
onPress={this.SaveValue}>
<Text>Save</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.FetchValue}>
<Text>Fetch</Text>
</TouchableOpacity>
<Text>{this.state.favs}</Text>
</View>
);
}
At this point I can see only one item, I tried to figure it out and saw that I have to use another method called push but when I changed save by push it throw me an error
Unhandled Promise Rejection : Existing value for key "Favorites" must be of type null or array, revived string.
Thanks!
it will work :)
renderFavorites = () => {
AsyncStorage.getItem("Favorites").then((favs) => {
favs.map((fav) => {
return (<Text> {fav} </Text>);
});
});
}
render() {
return (
<View>
<TextInput
onChangeText={(UserInput) => this.setState({UserInput})}
placeholder= "Type something"
value={this.state.UserInput} />
<TouchableOpacity
onPress={this.SaveValue}>
<Text>Save</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.FetchValue}>
<Text>Fetch</Text>
</TouchableOpacity>
{this.renderFavorites()}
</View>
);
}
Solution using JSON:
SaveValue = () => {
const newFavs = [...this.state.favs, this.state.UserInput];
this.setState({ favs: newFavs, UserInput: '' }, () => {
AsyncStorage.setItem("Favorites", JSON.stringify(this.state.favs));
Keyboard.dismiss()
});
};
FetchValue = () => {
AsyncStorage.getItem("Favorites").then((value) => {
this.setState({
favs: JSON.parse(value)
});
}).done();
};
I am using react-native-sortable-listview in react-native for sorting same places.
constructor() {
this.state = {
makers: [
{ kolkata: 'Hawrah Birdge' },
{ Delhi: 'Lal Kila' },
{ Agra: 'Taj Mahal' },
{ Mumbai: 'India Gate' },
],
allObj: {},
order: []
};
}
componentDidMount() {
const newAllObj = this.getAllObjFromMaker(this.state.makers);
const newOrder = this.getOrderFromMaker(newAllObj);
this.setState({ allObj: newAllObj, order: newOrder });
}
getAllObjFromMaker(makers) {
const allObj = makers.reduce((result, d) => {
result[`${d.coordinate.latitude}_${d.coordinate.longitude}`] = d;
return result;
}, {});
return allObj;
}
getOrderFromMaker(allObj) {
const order = Object.keys(allObj);
return order;
}
renderOneDraggableMilestone(milestone) {
const i = this.state.makers.indexOf(milestone);
return (
<TouchableOpacity {...this.props.sortHandlers}>
<Text>{i + 1}</Text>
<Text>{milestone.address}</Text>
</TouchableOpacity>
);
}
arrangedMilestoneList(e) {
const arr = this.state.makers;
arr.splice(e.to, 0, arr.splice(e.from, 1)[0]);
const newAllObj = this.getAllObjFromMaker(arr);
const newOrder = this.getOrderFromMaker(newAllObj);
this.setState({ makers: arr, allObj: newAllObj, order: newOrder
});
}
render() {
return (
<SortableListView
data={this.state.allObj}
order={this.state.order}
activeOpacity={0.5}
onRowMoved={e => {
this.arrangedMilestoneList(e);
this.forceUpdate();
}}
renderRow={(row) => this.renderOneDraggableMilestone(row)}
/>
);
}
I want to arrange places and also their position in this.state.makers as I am doing using i in renderOneDraggableMilestone. On renderRow only draggable place are render so only their position is updated. And renderRow is last to excute so forceUpdate is also not working.
How to rerender after executing renderRow. So all position could be updated.
Ok I have find a way to re-render as follow.
<SortableListView
key={this.state.count}
data={this.state.allObj}
order={this.state.order}
activeOpacity={0.5}
onRowMoved={e => {
this.setState({ count: this.state.count + 1 });
this.props.arrangedMilestoneList(e);
console.log('onRowMoved is called');
}}
onMoveEnd={() => console.log('onMoveEnd is fired')}
renderRow={(row, s1, i) => this.renderOneDraggableMilestone(row, s1, i)}
/>
What I am doing is I added a key attribute to SortableListView and update this key on each onRowMoved action. And because of this it causes re-render.