Here is My Code
<FlatList
refreshControl={
<RefreshControl
enabled={true}
refreshing={loader}
onRefresh={() => getLockerHistory(1)}
tintColor={ThemeColors.primary}
/>
}
ListEmptyComponent={noDataMessage()}
onScroll={(e) => {
if (Platform.OS == 'ios') {
return;
}
let paddingToBottom = 20;
paddingToBottom += e.nativeEvent.layoutMeasurement.height;
if (e.nativeEvent.contentOffset.y >= e.nativeEvent.contentSize.height - paddingToBottom) {
getNextRecordsPage();
}
}}
ListFooterComponent={() => {
return (
<ActivityIndicator color={ThemeColors.black} animating={footerLoader} />
);
}}
ListFooterComponentStyle={footerLoader ? { marginVertical: 20 } : {}}
ListFooterComponentStyle={{ paddingVertical: 20 }}
onEndReached={() => {
if (Platform.OS == 'ios') {
getNextRecordsPage()
}
}}
onEndReachedThreshold={Platform.OS == 'ios' ? 0 : null}
keyExtractor={(item, index) => item.lockerCode + '' + index}
data={lockers}
renderItem={(itemData) => {
return renderItem(itemData.item, props.navigation);
}}
/>
When I have more then 5 records which means there is not empty space left on screen then the refresh control won't work. It only works the seperator space between cells.
And my cell is made up of plain views nothing fancy or touchable.
Note: I tried to debug it with empty view but seems like pull/drag to refresh is not being listen by Flat List.
Any help?
Below code is working for me to achieve Refresh Control
import { View, Text, RefreshControl, FlatList } from 'react-native'
<FlatList
testID="DiscussionView"
initialNumToRender={5}
maxToRenderPerBatch={5}
windowSize={11}
data={posts}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={this.onRefresh} />}
keyExtractor={this.keyExtractor}
refreshing={refreshing}
renderItem={this.renderPostItem}
onEndReached={this.onEndReached}
scrollEnabled={scrollEnabled}
onEndReachedThreshold={0.3}
contentContainerStyle
listKey="DiscussionViewList"
onMomentumScrollBegin={() => {
this.onEndReachedCalledDuringMomentum = false
}}
/>
onRefresh = () => {
this.setState(
{ refreshing: true },
// Do what you want,
)
}
keyExtractor = (item) => String(item.id)
renderPostItem = ({ item }) => {
// Render Item here
}
onEndReached = () => {
if (!this.onEndReachedCalledDuringMomentum) {
// Call API again if needed by use case
this.onEndReachedCalledDuringMomentum = true
}
}
Related
I have a flatlist in my application and I want to enable a user to mark a particular list item as selected on Long press and provide a delete button to remove multiple items in one go. These are the behaviors I expect.
If no items in the flatlist are selected, pressing an item opens a new screen with item details and back button takes you back to the flatlist.
If no items are selected, long pressing an item marks it as selected. Every item pressed after any particular item is selected is marked as selected instead of opening the details screen.
Items which are already selected and then pressed become unselected.
If any number of items are selected, a delete button is rendered and pressing the back button unselects all items.
I have been able to achieve most of the first three behaviors but I am completly lost with the Back Handler. Here is my component with only the relevant code for brevity. Only the state array which contains the items selected for deletion and the listItem which is used as the RenderItem prop for the flatlist is shown.
const Home = (props) => {
const [deleteItems, setDeleteItems] = useState([]);
const renderItem = ({ item }) => {
let bb_OR_ub = item.useBy ? 'Use By ' : 'Best Before '
let exp_check = CheckExp(item, 1);
let listStyle = {};
if (exp_check === -1)
listStyle = { backgroundColor: '#ff9ea5' }
else if (exp_check === 0)
listStyle = { backgroundColor: '#fff185' }
if (deleteItems.indexOf(item.name) != -1) {
listStyle = { opacity: 0.3 }
}
return (
<ListItem
containerStyle={listStyle}
badge={
exp_check !== 1 ?
exp_check === -1 ? { status: 'error', value: `!` } : {
status: 'warning'
} : null
}
title={item.name}
subtitle={bb_OR_ub + item.date}
bottomDivider
leftAvatar={{ source: require('../shared/cexp.png'), imageProps: { resizeMode: 'contain' } }}
onPress={() => {
if (deleteItems.length == 0)
navigate('ExpiryDetails', { item })
else {
setDeleteItems([...deleteItems, item.name])
}
}}
onLongPress={() => {
if (deleteItems.indexOf(item.name) == -1 || deleteItems.length == 0) {
setDeleteItems([...deleteItems, item.name])
}
else {
setDeleteItems(deleteItems.filter(el => el != item.name))
}
}} />
);
}
The BackHandler provided by react-native allows you to subscribe to back button being pressed. The callback provided to backhandler can provide either true (when the default behavior should not trigger) or false (when the default behavior is allowed to continue).
For your case, we want to have custom behavior on back when there are items selected, at that moment we want to unselect all items.
I adjusted your code to introdcue the BackHandler and unselect any items on back being pressed
const Home = (props) => {
const [deleteItems, setDeleteItems] = useState([]);
// Subscribe to BackHandler once the component is mounted
// or when deletedItems changes
useEffect(() => {
const handler = BackHandler.addEventListener('hardwareBackPress', () => {
// If no deleted items: we return false
if (!deletedItems.length) {
return false;
}
// clear the selected items, and indicate that the back if handled
setDeletedItems([]);
return true;
});
// unsubscribe when component unmounts
return () => {
handler.remove();
};
}, [deletedItems]);
const renderItem = ({ item }) => {
let bb_OR_ub = item.useBy ? 'Use By ' : 'Best Before '
let exp_check = CheckExp(item, 1);
let listStyle = {};
if (exp_check === -1)
listStyle = { backgroundColor: '#ff9ea5' }
else if (exp_check === 0)
listStyle = { backgroundColor: '#fff185' }
if (deleteItems.indexOf(item.name) != -1) {
listStyle = { opacity: 0.3 }
}
return (
<ListItem
containerStyle={listStyle}
badge={
exp_check !== 1 ?
exp_check === -1 ? { status: 'error', value: `!` } : {
status: 'warning'
} : null
}
title={item.name}
subtitle={bb_OR_ub + item.date}
bottomDivider
leftAvatar={{ source: require('../shared/cexp.png'), imageProps: { resizeMode: 'contain' } }}
onPress={() => {
if (deleteItems.length == 0)
navigate('ExpiryDetails', { item })
else {
setDeleteItems([...deleteItems, item.name])
}
}}
onLongPress={() => {
if (deleteItems.indexOf(item.name) == -1 || deleteItems.length == 0) {
setDeleteItems([...deleteItems, item.name])
}
else {
setDeleteItems(deleteItems.filter(el => el != item.name))
}
}} />
);
}
I know there are 1000 of similar questions like this but even when trying out different examples I can't get this to work, apologies in advance.
When I pull to refresh my data the request is sent and the new response is received (As verified per my logs). However, this data is not reflected in the list itself. I understand the listview is clever enough to refresh the data when there is a change to the datasource but it doesn't seem to be happening here.
constructor(props) {
super(props);
const dataSource = new ListView.DataSource({rowHasChanged: (row1, row2) => row1 !== row2});
this.state = {
dataSource: dataSource.cloneWithRows([]),
refreshing: false
};
this._renderRow = this._renderRow.bind(this);
this._getCoinData = this._getCoinData.bind(this);
}
componentWillMount() {
this._getCoinData();
}
_getCoinData() {
return new Promise((resolve) => {
getCryptocurrencyData()
.then(function (result) {
const ds = new ListView.DataSource({rowHasChanged: (row1, row2) => row1 !== row2});
this.setState({
dataSource: ds.cloneWithRows(result),
jsonData: result
});
resolve();
}.bind(this))
});
}
_renderRow(data) {
return (
<CoinCell
coinName={data.name}
coinPrice={data.price_gbp}
coinPercentageChange={data.percent_change_24h}>
</CoinCell>)
}
_renderHeader() {
return (
<Header />
)
}
_onRefresh() {
this.setState({refreshing: true});
this._getCoinData()
.then(() => {
this.setState({refreshing: false});
});
}
render() {
return (
<View>
<ListView
enableEmptySections
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this._onRefresh.bind(this)}
/>
}
ref={'resultListView'}
dataSource={this.state.dataSource}
renderRow={this._renderRow}
renderSeparator={(sectionId, rowId) => <View key={rowId} style={styles.separator}/>}
renderHeader={() => this._renderHeader()}
/>
</View>
);
}
}
used custom ScroolView is the best solution to rerender list data ti view.
dataSource = [ ['SectionAScene',strings.SectionA,false],
['SectionBScene',strings.SectionB,false],
// ['SectionC1Scene',strings.SectionC1,false],
['SectionD1Scene',strings.SectionD1,false],
// ['SectionEScene',strings.SectionE,false],
['SectionFScene',strings.SectionF,false],
// ['SectionGScene',strings.SectionG,false],
['SectionH1Scene',strings.SectionH,false],
['SectionIScene',strings.SectionI,false],
['SectionJScene',strings.SectionJ,false],
['SectionKScene',strings.SectionK,false],
['SectionLScene',strings.SectionL,false],
['SectionMScene',strings.SectionM,false],
['SectionNScene',strings.SectionN,false],
['SectionOScene',strings.SectionO,false],
['SectionPScene',strings.SectionP,false],
['SectionQScene',strings.SectionQ,false],
['SectionQ1Scene',strings.SectionQ1,false],
['SectionRScene',strings.SectionR,false],
['SectionSScene',strings.SectionS,false],
];
render() {
let data = this.state.dataSource;
return (
{data != [] ?
{this._renderHeader()}
<ScrollView>
{ data.map( (item,index) =>
<TouchableNativeFeedback onPress={this._onPressButton.bind(this,item)}>
<View style={item[2]? styles.itemSelected : styles.item}>
<Text>{item[1]}</Text>
<View style={{flex:1}} />
<Icon name={item[2]?"check":"chevron-right"} size={16} color="#444"/>
</View>
</TouchableNativeFeedback>
)}
</ScrollView>
:<View />}
</View>
);
}
I'm new in react-native and I'm writing my first big app for android. I need to render buttons dynamically on screen and close each 3 of them in their own View tag to make them stay in one row. This is structure what I'm trying to achieve
<View>
<Button />
<Button />
<Button />
</View>
<View>
<Button />
<Button />
<Button />
</View>
<View>
<Button />
<Button />
<Button />
</View>
etc...
Before I realized that I need to group them by 3 inside the view tag I made code like this:
render() {
const mappedButtons = reversed_buttons.map((button, i) => {
if (reversed_types[i] == 1) {return (<Floor key={i} content={button}/>)}
else {return (<Step key={i} title={button}/>)}
})
return (
<View>
{mappedButtons}
</View>)
}
It's working fine but I don't know how to close each 3 of Step-tag inside their own view tag. I was trying to return opening tag of view at the begining and closing and opening tag every third button but I'm getting error 500.
I was trying to do it this way:
render() {
var reversed_buttons = [ ];
var reversed_types = [ ];
for (var i = buttons.length-1; i >= 0; i--) {
reversed_buttons.push(buttons[i]);
reversed_types.push(types[i]);
}
const mappedButtons = reversed_buttons.map((button, i) => {
var y = i%3;
if (i == 0){return(<View>)}
if (i == 2){return(</View><View>)}
if (reversed_types[i] == 1) {return (<Floor key={i} content={button}/>)}
else {return (<Step key={i} title={button}/>)}
})
return (
<View>
{mappedButtons}
</View>)
}
and also in this way:
render() {
const mappedButtons = reversed_buttons.map((button, i) => {
var y = i%3;
if (reversed_types[i] == 1) {return (<Floor key={i} content={button}/>)}
else {
if (i == 0){
return (<View><Step key={i} title={button}/>)
}
if (y == 2){
return (</View><View><Step key={i} title={button}/>)
}
}
})
return (
<View>
{mappedButtons}
</View>)
}
but this thing still don't want to work fine giving me error 500 in console.
This will do the trick. I'm first breaking down the larger array into individual arrays of not more than 3. Then, I map those arrays to create each group.
const buttons = ['b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'b10', 'b11', 'b12', 'b13', 'b14', 'b15', 'b16', 'b17'];
const button_groups = [];
while (buttons.length) {
button_groups.push(buttons.splice(0, 3));
}
const renderButtons = button_groups.map(group => {
const btns = group.map(title => <Button>{title}</Button>);
return <View>{btns}</View>;
})
I'm using Beefe's react-native-picker and I want to add custom styles.
I have an method that renders the picker as follows:
renderModal() {
if (!this.state.itemPickerVisible) return;
return (
<View>
{
Picker.init({
pickerData: map(categories, cat => cat.code + "." + cat.name),
pickerTitleText: gettext("Please select"),
pickerConfirmBtnText: gettext("Done"),
pickerCancelBtnText: gettext("Cancel"),
selectedValue: [this.state.selectedValue.code],
pickerBg: [255, 255, 255, 1],
onPickerConfirm: data => {
let code = data[0].substring(0, data[0].indexOf('.'));
this.setState({selectedValue: get_category(code), itemPickerVisible: false}, () => this.selectedItem());
},
onPickerCancel: data => {
Picker.hide();
},
onPickerSelect: data => {
console.log(data);
}
})
}
{
Picker.show()
}
</View>
)
}
And the render method is as follows:
render() {
let value = this.props.value ? this.props.value.name : "";
return (
<View style={{
borderLeftWidth: 4,
borderLeftColor: this.props.mandatory ? this.props.value == null ? s.paletteMandatory : s.success : '#fff'
}}>
{this.renderModal()}
<ItemDetail locked={this.props.locked} selectItem={this.selectItem.bind(this)}
resetItem={this.resetItem.bind(this)} title={this.props.title} value={value}
icon={this.props.icon}/>
</View>
)
}
Is there any way to add custom style (fontFamily, color) to the picker items?
If you check the component docs there are several parameters you can pass to init function.
Picker.init({
pickerTitleColor: [90, 90, 90, 1], // RGBA values
pickerData: data,
selectedValue: [59],
onPickerConfirm: data => {
console.log(data);
},
onPickerCancel: data => {
console.log(data);
},
onPickerSelect: data => {
console.log(data);
}
});
Picker.show();
I'm having serious issues with the RN Picker Item, whenever I try to load the picker Items I get the following error.
undefined is not an object (evaluating 'this.inputProps.value')
Here us the screenshot.
This is my code - Component - Basic
import React, { Component } from 'react';
import { Picker } from 'react-native';
export default class Basic extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
var options = this.props.list.map((item, key) => {
return <Picker.Item label={item} value={item} key={key} /> ;
});
return (
<Picker mode="dropdown" selectedValue={this.props.selected} supportedOrientations={['portrait','landscape']} {...this.props}>
{ this.props.default && <Picker label={this.props.default} value=""/> }
{ options }
</Picker>
);
}
}
File - Dynamic OptionSet
This will use the Basic component to display the Picker.
class DynamicOptionSets extends Component {
constructor(props) {
super(props);
this.state = {};
this.ucfirst = this.ucfirst.bind(this);
this._renderMain = this._renderMain.bind(this);
this._renderSpinner = this._renderSpinner.bind(this);
}
componentWillMount() {
InteractionManager.runAfterInteractions(() => {
this.props["get"+this.ucfirst(this.props.option)]();
});
}
ucfirst(string)
{
return string.charAt(0).toUpperCase() + string.slice(1);
}
render() {
return (
<View>
{this._renderSpinner()}
{this._renderMain()}
</View>
);
}
_renderMain(){
if(!this.props[this.props.option]['data']){
return null;
}
return (
<Basic list={this.props[this.props.option]['data']} { ...this.props }/>
)
}
_renderSpinner(){...}
}
const mapDispatchToProps = (dispatch, ownProps) => {
var {getCountries, getStates,
getDepartments, getBranches,
getBusinessSectors, getGenPostingGroup,
getCustPostingGroup, getVatPostingGroup,
getPricelist, getSalesPersons
} = ActionCreators;
return bindActionCreators({
getCountries, getStates,
getDepartments, getBranches,
getBusinessSectors, getGenPostingGroup,
getCustPostingGroup, getVatPostingGroup,
getPricelist, getSalesPersons
}, dispatch);
}
const mapStateToProps = (state) => {
var {
countries, countriesUpdate,
states, statesUpdate,
departments, departmentsUpdate,
branches, branchesUpdate,
businessSectors, businessSectorsUpdate,
genPostingGroup, genPostingGroupUpdate,
ccustPostingGroup, ccustPostingGroupUpdate,
vatPostingGroup, vatPostingGroupUpdate,
pricelist, pricelistUpdate,
salesPersons, salesPersonsUpdate,
} = state;
return {
countries, countriesUpdate,
states, statesUpdate,
departments, departmentsUpdate,
branches, branchesUpdate,
businessSectors, businessSectorsUpdate,
genPostingGroup, genPostingGroupUpdate,
ccustPostingGroup, ccustPostingGroupUpdate,
vatPostingGroup, vatPostingGroupUpdate,
pricelist, pricelistUpdate,
salesPersons, salesPersonsUpdate,
}
}
export default connect(mapStateToProps, mapDispatchToProps)(DynamicOptionSets);
So now I can call the dynamic option set like a regular picker component only and specify the data group (option)
<DynamicOptionSets option="salesPersons" mode="dropdown" onValueChange={this._updateValue.bind(this, 'salesperson')} selectedValue={this.state.form_data.salesperson} />
I don't understand why this is happening as this is the exact way I render Pickers dynamically in RN. I have gone through the doc and followed the instructions as specified.
NB: I'm dynamically loading the picker so it's inside a component I'm calling whenever I need to, display a picker that should explain the {... this.props} on the picker component.
You have a basic mistake in your code.
render() {
var options = this.props.list.map((item, key) => {
return <Picker.Item label={item} value={item} key={key} /> ;
});
return (
<Picker mode="dropdown" selected={this.props.selected} supportedOrientations={['portrait','landscape']}>
{/*_________________^^^^^^^^____ You should place `selectedValue` here instead */}
{ this.props.default && <Picker.Item label={this.props.default} value=""/> }
{ options }
</Picker>
);
}