FlatList re-render with Redux - ios

I'm using PureComponent to render items of my FlatList and when I use FlatList and pass a local state to it in data, the rendering works very well, I don't have wasteful re-rendering.
However, when I use FlatList with an array from my redux store in data, if I scroll down, each time that onReachEnd is called, re-render work well. But when I don't have anymore data to load and I scroll up, all my items re-render one per one.
Like my whole list is lost.
I'm using exactly the same list with local state in data, and it works perfectly!
The issue only appear when I try to make FlatList and Redux work together
<FlatList
ref={(view) => this.list = view}
data={this.props.requestsList}
style={{flex: 1}}
keyExtractor={(item) => String(item.emitter.id)}
renderItem={this._renderRequestsItems}
onEndReachedThreshold={0.5}
onEndReached={!this.props.lastPage ? this._endReached : null}
ListFooterComponent={reloadIndicator}
ListHeaderComponent={this._getHeaderComponent}
ListEmptyComponent={this._getEmptyComponent}
/>

Use extraData property on your FlatList component, in your case, extra data can come from props so it will look like
extraData={this.prop}

Can you try to add a new key to flat list?
<FlatList
key={Math.floor(Math.random())}
ref={(view) => this.list = view}
data={this.props.requestsList}
style={{flex: 1}}
keyExtractor={(item) => String(item.emitter.id)}
renderItem={this._renderRequestsItems}
onEndReachedThreshold={0.5}
onEndReached={!this.props.lastPage ? this._endReached : null}
ListFooterComponent={reloadIndicator}
ListHeaderComponent={this._getHeaderComponent}
ListEmptyComponent={this._getEmptyComponent}
/>
It is work for me.

Related

Why does react-native-maps MapView crash when using custom map markers, Expo, and Apple Maps after rendering four map markers?

I'm working on an Expo React Native project that is using react-native-maps to render a MapView component with a list of Markers. The Markers are custom components that render an Image. When testing using Android and Google Maps everything works perfectly. When testing using an iOS emulator the markers appear but the map runs slowly. When testing using a physical iPhone 7 (and others) the app crashes with no error message. The app always loads correctly until rendering the map, which appears for a second or two before the app crashes. Sometimes the markers will also appear for a split second before the app crashes.
If I set limits on how many items to render the markers will appear as long as the limit is less than five. Similarly I can render each marker if I specify which one to load by id, so I don't think the data is wrong or causing unhandled exceptions. I need all the items in the list to render dynamically, without a limit on how many can be rendered. If I comment out the Image component and the default red pin markers appear on the map without any problems. It seems like the issue has to do with how the markers' Images are rendered dynamically on the Apple map.
I've tried importing the image source, preloading it, requiring it, and using {{uri:url}} format for the Image source parameter. Everything results in the app crashing without an error message. Am I missing something? Is there some way for me to get any kind of error message that can help debug this? Is there a workaround if this is a known issue?
MapView:
<MapView
style={styles.map}
ref={(map) => { currentMap = map; }}
region={region}
onRegionChangeComplete={onRegionChange}
rotateEnabled={false}
loadingEnabled
>
{
eventList.map((marker, index) => {
const { location } = marker.location.geometry;
if (
location.lat <= (region.latitude + region.latitudeDelta / 2)
&& location.lat >= (region.latitude - region.latitudeDelta / 2)
&& location.lng <= (region.longitude + region.longitudeDelta / 2)
&& location.lng >= (region.longitude - region.longitudeDelta / 2)
) {
return (
<MapMarker
key={index}
mapMarker={marker}
handlePress={() => moveMapToCoordinate(marker.location)}
onSelectEvent={onSelectEvent}
/>
);
}
return null;
})
}
</MapView>
MapMarker:
<Marker
coordinate={latLng}
title={title}
onPress={() => handlePress()}
>
<CustomMapMarker
eventType={eventType}
isSanctioned={isSanctioned}
startDate={startAt}
/>
<Callout
style={styles.customCallout}
onPress={() => onSelectEvent(_id)}
>
<ViewEventScreenDetailsHeader
fullEvent={mapMarker}
containerStyle={styles.calloutDetails} />
</Callout>
</Marker>
CustomMapMarker:
const img = require('../../assets/icons/SpikeScreen/map-marker-pickup.png');
return (
<View style={[styles.pickupMarkerContainer, markerContainerStyle]}>
<Image
style={[styles.pickupMarker, markerStyle]}
source={img}
/>
<Text style={styles.dayMonthMarkerText}>{formattedStartDate}</Text>
</View>
)
Got em! Make sure your marker icon image isn't too large, Apple maps renders them differently than google maps.

setNativeProps for TextInput in React Native

How do I know what native props are available for a component to use setNativeProps. In this example, the <TextInput> component doesn't have text as a prop but apparently setNativeProps use text instead of value as a prop. Thank you!
clearText = () => {
this._textInput.setNativeProps({text: ''});
}
render() {
return (
<View style={{flex: 1}}>
<TextInput
ref={component => this._textInput = component}
/>
<TouchableOpacity onPress={this.clearText}>
<Text>Clear text</Text>
</TouchableOpacity>
</View>
);
}
}
This is pretty common in react-native, due to limited documentation. Whenever looking for any information regarding react-native components, it is a good idea to simply look at the .js file you are using. In this case, TextInput, can be found...
Project/node_modules/react-native/Libraries/Components/TextInput/TextInput.js
Hope you can find what you're looking for - with a bit of digging. If you want to look further, looking into the RCT files is a good idea also.
Project/node_modules/react-native/Libraries/Text/RCTTextField.h
Project/node_modules/react-native/Libraries/Text/RCTTextView.h

New to React: Why is one array treated differently than the other?

I'm working on a React app that is fed data from a Rails api. I'm currently working on a form that includes a nested association (i.e. in the model_a has many model_b's and you can create them in the same form).
The problem I'm having is that Rails expects nested association with a certain naming convention and the same field that controls how the parameter is named when its sent to rails also controls how React finds the right data when the Rails API responds.
This becomes problematic on the edit page because I want to show the models_a's (Retailers) already existing model_b's (SpendingThresholds in this case) and when I change the 'name' field to suit the rails side, React doesn't know where to look for that data anymore. When I try to pass the data directly it comes in as a different type of array and certain functions fail.
I think its easier to show than tell here so
initially I had this
<FieldArray
name="spending_thresholds"
component={renderSpendingThresholds}
/>
and data was coming through like
Object {_isFieldArray: true, forEach: function, get: function, getAll: function, insert: function…
to my React app from the Rails API, which worked, however that 'name' isn't to Rails liking (Rails wants it to be called 'spending_thresholds_attributes' for accepts_nested_attributes to work) so I changed it to
<FieldArray
name="spending_thresholds_attributes"
fields={this.props.retailer.spending_thresholds}
component={renderSpendingThresholds}
/>
and data start coming through to the renderSpendingThresholds component in this format
[Object]
0:Object
length:1
__proto__:Array(0)
which React doesn't like for some reason.
Anyone know how to fix this/why those two objects, which hold the same information from the Rails side anyway, are being treated differently?
EDITS
renderSpendingThresholds component
The fields attribute in the renderSpendingThresholds component is the object that's coming through differently depending on how I input it
const renderSpendingThresholds = ({ fields }) => (
<ul className="spending-thresholds">
<li>
<Button size="sm" color="secondary" onClick={(e) => {
fields.push({});
e.preventDefault();
}
}>
Add Spending Threshold
</Button>
</li>
{fields.map((spending_threshold, index) => (
<li key={index}>
<h4>Spending Threshold #{index + 1}</h4>
<Button
size="sm"
color="danger"
title="Remove Spending Threshold"
onClick={() => fields.remove(index)}
>
Remove
</Button>
<Field
name={`${spending_threshold}.spend_amount`}
type="number"
component={renderField}
label="Spend Amount"
placeholder="0"
/>
<Field
name={`${spending_threshold}.bonus_credits`}
type="number"
component={renderField}
label="Bonus Credits"
placeholder="0"
/>
</li>
))}
</ul>
);
It looks like you are passing fields through props and then destructuring the fields out of the props in the callback of the renderSpendingThresholds and discarding the rest. According to the docs, a specific redux-form object is passed through to the render callback. You're essentially overwriting this. Try changing {field} to something like member or spending_threshold. Then you can use the specific map function to iterate over the spending_threshold items. Your field prop should still be available under member.fields or something similar.
For the code that you currently show, who exactly handles the submission?
you use the original flow of form submit?
if so, so please handle that by yourself.
** this line of code, looks weird:
onClick={() => fields.remove(index)}
as you interact directly with the state values...
you need to update the state through
this.setState({fields: FIELDS_WITHOUT_ITEM})
and now when you need to handle your own submission, you don't really care of the input names. Because you are using the state as input.
ie:
class FormSpending extends Component {
handleSubmit() {
var fieldsData = this.state.fields.map(field => {
return {
whateverkey: field.dontcare,
otherKey: field.anotherDontCare
};
});
var formData = {
fields: fieldsData
};
ajaxLibrary.post(URL_HERE, formData).....
}
render() {
return (
...
<form onSubmit={()=>this.handleSubmit()}>
...
</form>
...
);
}
}

React Native ListView slow to load

I have a simple ListView which seems to take a long time to load its items. I've tested in Release mode, and its taking about 3 seconds to load the whole page. If I set initialListSize={1} I can see it building the list item by item.
The ListView JSX code:
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow}
renderSeparator={(sectionID, rowID) => <View key={`${sectionID}-${rowID}`} style={styles.separator} />}
style={styles.listView}
initialListSize={1}
/>
And the renderRow method:
renderRow(row) {
// console.log ('rendering scale row: ' + row.numerator + ':' + row.denominator );
return (
<TouchableHighlight onPress={() => this.pressRow(row)} underlayColor="grey">
<View style={styles.rowContainer}>
<Text style={styles.rowText}>{row.hasOwnProperty('label') ? row.label : row.numerator + ':' + row.denominator}</Text>
<Text style={styles.rowText}>{row.hasOwnProperty('label') ? row.numerator + ':' + row.denominator : ''}</Text>
</View>
</TouchableHighlight>
);
}
I'm not doing anything very complicated in renderRow... and the data had already been fetched in a previous view and passed in via props.
Is there something I don't know about that could be affecting load/render time?
This is iOS only and on an iPhone 4S. Only 12-21 rows in my tests.
I should also add that the core of the app is using the JUCE C++ framework and running some audio processing in one of the views. I have an audio processing thread that is only run when that view is visible - all other views (including the one with this ListView) will stop the audio processing thread. I've tried also stopping the audio thread in a similar manner but it did not make any difference.
Not sure if this is going to help you, but I can think of three things you can try...
cmd+T will toggle "slow animations" on/off. This is a long shot
If you are writing to the console.log, comment out those lines. They can surprisingly bog things down considerably sometimes.
Turn off dev mode. You shouldn't need to, but worth a shot
Better start using FlatList or SectionList since ListView is now Deprecated.
https://facebook.github.io/react-native/docs/listview.html
There are few Caveats like you must use a FLUX/ REDUX or RELAY. Please Checkout the details here (Features/Caveats)
https://facebook.github.io/react-native/blog/2017/03/13/better-list-views.html

How can I create a discrete slider in react-native (iOS)?

I have a slider component in my react-native app that is nicely restricted to discrete values from 1-5 using the minimum/maximumValue properties and some rounding in onValueChange:
<View>
<Text style={styles.text} >
{this.state.value}
</Text>
<SliderIOS
ref='slider'
style={styles.slider}
value={1}
minimumValue={1}
maximumValue={5}
onValueChange={(value) => {
this.setState({
value: Math.round(value)
});
}}
/>
</View>
What I would like is for the slider handle button to snap to the discrete positions 1,2,3,4,5 rather than move continuously along the slider. Hints appreciated!
For future reference: SliderIOS has been deprecated in the meantime and one should use the Slider component. <Slider> comes with a step attribute:
Step value of the slider. The value should be between 0 and (maximumValue - minimumValue). Default value is 0.
So the solution to the specific question would be to simply set
<Slider
step={1}
...
/>
you're almost there but in the docs of SliderIOS it says:
value number
Initial value of the slider. The value should be between minimumValue
and maximumValue, which default to 0 and 1 respectively. Default value
is 0.
This is not a controlled component, e.g. if you don't update the
value, the component won't be reset to its inital value.
Therefore, it's not possible to control the behavior of the native slider in the way you want it to work.
Solution using another component
However, there is a JavaScript version of the <Slider> component which let's you do it. Since it uses some of the props SliderIOS has, you can easily replace it. Just load the npm module, require it and change <SliderIOS> to <Slider>. Now you need to make some adjustments to your current code:
The property value of the component should reference a variable which can be changed by your code. You can use getInitialState to have a default value.
Add your callback function used for onValueChange to onSlidingComplete. Otherwise you could still have values in between when you release the slider.
I used sliderValue instead of value to make it clearer.
`
getInitialState() {
return {
sliderValue: 1
}
},
onSliderValueChange(value) {
this.setState({
sliderValue: Math.round(value)
});
},
render() {
return(
<View>
<Text style={styles.text} >
{this.state.sliderValue}
</Text>
<Slider
ref='slider'
style={styles.slider}
value={this.state.sliderValue}
minimumValue={1}
maximumValue={5}
onValueChange={this.onSliderValueChange}
onSlidingComplete={this.onSliderValueChange}
/>
</View>
)
}
`
I didn't check my last version the code but it should work like that.

Resources