React Native Flatlist dynamic style - react-native

I'm trying to make buttons out of react native FlatList items, that means when I click on them they change color.
This is the function that gets rendered by the renderItem prop:
renderRows(item, index) {
return (
<TouchableOpacity
key={index}
style={[styles.item, this.calculatedSize()]}
onPress={() => this.onPressImage(index)}
>
<Image
style={[styles.picture]}
source={{ uri: item.uri }}
/>
<View
style={[
styles.imageTextContainer,
{
backgroundColor:
_.indexOf(this.active, index) === -1
? 'rgba(0,0,0,0.2)'
: 'rgba(26, 211, 132, 0.7)',
},
]}
>
<Text style={styles.imageText}>{item.title}</Text>
</View>
</TouchableOpacity>
)
}
oh yeah and im using loadash to get the index.
The function "onPressImage(index)" works fine, in "this.active" (array) I always have the positions(integer) of the elements I would like to change color,
however nothing happens, the only thing you can see is the response by the touchableOpacity. What am I doing wrong ?

Like Andrew said, you need to trigger a re-render, usually by updating state.
However, if the items don't depend on anything outside the FlatList, I would recommend creating a component (preferably a PureComponent if possible) for the items themselves, and updating their state upon press. This way it will only re-render each list item individually if there is a change instead of the parent component.

Thanks, updating the state was a little bit difficult, the items array I've used has been declared outside the class component, thus only using an "active" array with indices that should change color in the state wasn't enough, I had to map the items array in the state like so:
state = {
items: items.map(e => ({
...e,
active: false,
}))
}
then I could manipulate the active state of the element I wanted to change color like this:
onPressItem(index) {
const { items } = this.state
const newItems = [...this.state.items]
if (items[index].active) {
newItems[index].active = false
} else {
newItems[index].active = true
}
this.setState({ items: newItems })
}
and change the color like so:
style={{
backgroundColor: item.active
? 'rgba(26, 211, 132, 0.7)'
: 'rgba(0,0,0,0.2)',
}}

Related

How can I get SetState to stop overwriting my component tags

I am using these tags in my react native app: https://github.com/peterp/react-native-tags
Being a react rookie I have a problem with SetState. I have set-up the tags like this:
<Tags
initialText=""
textInputProps={{
placeholder: "Servicable Postcodes"
}}
initialTags={["3121"]}
onChangeTags={(tags) => this.changeTags(tags)}
inputStyle={{ backgroundColor: "white", borderBottomWidth: 1 }}
renderTag={({ tag, index, onPress, deleteTagOnPress, readonly }) => (
<TouchableOpacity key={${tag}-${index}} onPress={onPress}>
<Text style={styles.tagStyle}>{tag}</Text>
</TouchableOpacity>
)}
/>
changeTags(newtags) {
//this.setState({ myTags: newtags.toString()});
var tagList = newtags.toString();
/*this.setState({myTags: tagList}, function () {
console.log(this.state.myTags);
});*/
}
Adding tags works like this [3121] [1111] [2222]
So that code works well. As soon as I uncomment the setState it will run everything like normal but the tag will not get added to the list. So if I add 1111 my console log will be 3121, 1111 (But 1111 will not show as as a tag) then if i try to add another it will be 3121, 2222 etc.
I think because it re-renders with setState the tag is never added/overwritten and I just keep getting left with the single 3121 tag. Or maybe i'm a react noob and have no idea.
Any help is appreciated.
Thanks
You are right in saying that each render will cause the Tag component to reset back to 3121.
To avoid this you need to store your current tags in your state like this:
export default class App extends React.Component {
state = { tags: ['3121'], myTags: "3121" }; // This line
render() {
return (
<View style={styles.container}>
<Tags
initialText=""
textInputProps={{
placeholder: 'Servicable Postcodes',
}}
initialTags={this.state.tags} // This line
onChangeTags={tags => this.changeTags(tags)}
inputStyle={{ backgroundColor: 'white', borderBottomWidth: 1 }}
renderTag={({ tag, index, onPress, deleteTagOnPress, readonly }) => (
<TouchableOpacity key={`${tag}-${index}`} onPress={onPress}>
<Text style={styles.tagStyle}>{tag}</Text>
</TouchableOpacity>
)}
/>
</View>
);
}
changeTags(newtags) { // This function
this.setState({ tags: newtags, myTags: newtags.toString() }, () =>
console.log(this.state)
);
}
}
This means for each render (which will happen every time a tag is added as you are calling setState), the Tag component will render the tags you have stored in the state.
I have also included another member in your state called myTags, which maps to the string value of your tags as it seems like this was necessary from your previous code.
Here is a working snack

FlatList ref scrollToIndex is not a function

I am facing what seems to be a long-lasting issue in react native.
I am using Expo SDK35 with RN version 0.59. I have not updated to Expo SDK36 / RN 0.60 yet, due to large code base, but I could update if that makes up for a solution to my issue.
I have an Animated.View component that has a FlatList child, and I am unable to use the static methods (scrollToIndex() in particular) that should be available on the FlatList reference. See the next example code:
class Example extends React.Component{
constructor(props){
super(props);
this.myRef = null;
}
componentDidUpdate = () => {
/*
somewhere in code outside this class, a re-render triggers
and passes new props to this class.
I do have props change detection, and some more other code,
but I have removed it in order to minimize the code example here
*/
// This call throws:
// TypeError: undefined is not a function (near '...this._scrollRef.scrollTo...')
this.myRef.scrollToIndex({
animated: true,
index: 1,
viewOffset: 0,
viewPosition: 0.5
});
// Other suggested solution from SO
// This also throws:
// TypeError: _this.myRef.getNode is not a function. (In '_this.myRef.getNode()', '_this.myRef.getNode' is undefined)
this.myRef.getNode().scrollToIndex({
animated: true,
index: 1,
viewOffset: 0,
viewPosition: 0.5
});
}
render = () => <Animated.View style={{ /* ... some animated props */ }}>
<FlatList ref={(flatListRef) => { this.myRef = flatListRef; }}
// more FlatList related props
/>
</Animated.View>
}
I have tried to use Animated.FlatList instead, still throws the same errors as in the code example above.
I have also tried to use react native's findNodeHandle() utility function on the received flatListRef parameter, but it returns null.
I have found the same issue posted multiple times in the past here on Stack Overflow, most with no answer, or which do not work for me. These posts are also a bit old (a year or so), which is why I am posting again for the same issue.
Did anyone manage to find a solution/workaround for this issue?
EDIT: Possible workaround
As I was playing with code, I tried to use a ScrollView component instead of FlatList - and the scrollTo method works!
The changes were only on the FlatList - ScrollView specific props (so, for a ScrolLView it would be childs instead of data={[...]} and renderItem={()=>{ ... }}, ect.), and the scrollToIndex method in componentDidMount which was replaced by scrollTo.
The render method of the class, with a ScrollView, now looks like this:
render = () => <Animated.View style={{ /* ... some animated props */ }}>
<ScrollView ref={(flatListRef) => { this.myRef = flatListRef; }}>
{/*
this.renderItem is almost the same as the
renderItem method used on the FlatList
*/}
{ this.state.dataArray.map(this.renderItem) }
</ScrollView>
</Animated.View>
Please note that ScrollView does not have a scrollToIndex() method, so you'll have to cope with manually keeping track of child positions, and maybe, implement a scrollToIndex method of your own.
I am not making this the answer to my question, because the underlying issue remains. But as a workaround, maybe you can go with it and call it a day...
TL;DR;
this.myRef = React.createRef();
this.myRef.current.doSomething(); // note the use of 'current'
Long version:
While the idea behind what I was trying was correct, the error in my original post seems to be quite stupid. In my defense, the docs were not clear (probably...). Anyway...
React.createRef returns an object with a few fields on it, all of them useless for the developer (used by React in the back) - except one: current.
This prop holds the current reference to the underlying component that the ref is attached to. The main ref object is not usable for the purpose I meant to in my original question above.
Instead, this is how I should've used the ref correctly:
this.myRef.current.scrollToIndex(...)
Hold up, don't crash
Both the main myRef object, and the current field will be null if the component has not yet mounted, has unmounted at any point later, or if the ref cannot be attached to it for some reason. As you may know (or found out later), null.something will throw an error. So, to avoid it:
if ((this.myRef !== null) && (this.myRef.current !== null)){
this.myRef.current.scrollToIndex(...);
}
Extra insurance
If you try to call an undefined value as a function on a field on the ref, your code will crash. This can happend if you mistakenly reuse the same ref on multiple components, or if the component you attached it to does not have that method (i.e. View does not have a scrollTo method). To fix this you have two solutions:
// I find this to be the most elegant solution
if ((this.myRef !== null) && (this.myRef.current !== null)) {
if (typeof this.myRef.current.scrollToIndex === "function") {
this.myRef.current.scrollToIndex(...);
}
}
or
if ((this.myRef !== null) && (this.myRef.current !== null)) {
if (typeof this.myRef.current.scrollToIndex === "function") {
try {
this.myRef.current.scrollToIndex(...);
} catch (error) {
console.warn("Something went wrong", error);
}
}
}
I hope this to be useful for anyone else learning to use refs in React. Cheers :)
With Animated.ScrollView:
Create a ref to your FlatList (the old way only works):
<ScrollView ref={ (ref) => (this.MyRef=ref) } />
Access scrollToIndex using this.myRef.getNode().scrollToIndex
Animated.FlatList is currently not working unfortunately...
With FlatList:
Create a ref to your FlatList by:
<FlatList ref={ this.flatListRef } />
constructor(props) {
super(props);
this.flatListRef = React.createRef();
}
Access scrollToIndex using this.flatListRef.current.scrollToIndex
Also make sure to wrap your code inside an if statement like:
if (this.myRef.getNode()) { this.flatListRef.getNode().scrollToIndex(); }
o do not know if this will help you... it scroll to a especific item in the list:
/*Example to Scroll to a specific position in scrollview*/
import React, { Component } from 'react';
//import react in our project
import {
View,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
Image,
TextInput,
} from 'react-native';
//import all the components we needed
export default class App extends Component {
constructor() {
super();
//Array of Item to add in Scrollview
this.items = [
'zero',
'one',
'two',
'three',
'four',
'five',
'six',
'seven',
'eight',
'nine',
'ten ',
'eleven',
'twelve',
'thirteen',
'fourteen',
'fifteen',
'sixteen',
'seventeen',
'eighteen',
'nineteen',
'twenty ',
'twenty-one',
'twenty-two',
'twenty-three',
'twenty-four',
'twenty-five',
'twenty-six',
'twenty-seven',
'twenty-eight',
'twenty-nine',
'thirty',
'thirty-one',
'thirty-two',
'thirty-three',
'thirty-four',
'thirty-five',
'thirty-six',
'thirty-seven',
'thirty-eight',
'thirty-nine',
'forty',
];
//Blank array to store the location of each item
this.arr = [];
this.state = { dynamicIndex: 0 };
}
downButtonHandler = () => {
if (this.arr.length >= this.state.dynamicIndex) {
// To Scroll to the index 5 element
this.scrollview_ref.scrollTo({
x: 0,
y: this.arr[this.state.dynamicIndex],
animated: true,
});
} else {
alert('Out of Max Index');
}
};
render() {
return (
<View style={styles.container}>
<View
style={{
flexDirection: 'row',
backgroundColor: '#1e73be',
padding: 5,
}}>
<TextInput
value={String(this.state.dynamicIndex)}
numericvalue
keyboardType={'numeric'}
onChangeText={dynamicIndex => this.setState({ dynamicIndex })}
placeholder={'Enter the index to scroll'}
style={{ flex: 1, backgroundColor: 'white', padding: 10 }}
/>
<TouchableOpacity
activeOpacity={0.5}
onPress={this.downButtonHandler}
style={{ padding: 15, backgroundColor: '#f4801e' }}>
<Text style={{ color: '#fff' }}>Go to Index</Text>
</TouchableOpacity>
</View>
<ScrollView
ref={ref => {
this.scrollview_ref = ref;
}}>
{/*Loop of JS which is like foreach loop*/}
{this.items.map((item, key) => (
//key is the index of the array
//item is the single item of the array
<View
key={key}
style={styles.item}
onLayout={event => {
const layout = event.nativeEvent.layout;
this.arr[key] = layout.y;
console.log('height:', layout.height);
console.log('width:', layout.width);
console.log('x:', layout.x);
console.log('y:', layout.y);
}}>
<Text style={styles.text}>
{key}. {item}
</Text>
<View style={styles.separator} />
</View>
))}
</ScrollView>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 30,
},
separator: {
height: 1,
backgroundColor: '#707080',
width: '100%',
},
text: {
fontSize: 16,
color: '#606070',
padding: 10,
},
});
if i completly wrong, tell me...
Because ScrollView has no scrollToOffset function and It has only scrollTo function.
So let use function scrollTo with ScrollView or scrollToOffset with FlatList and it works normal.
If you are working with 'KeyboardAwareFlatList' this worked nicely:
https://github.com/APSL/react-native-keyboard-aware-scroll-view/issues/372
In short, use useRef and use the innerRef property of the KeyboardAwareFlatList rather than the ref property.

Why React Native FlatList style not change with state

I have some tabs. I want to change the color of the selected tab. I created a state for selected tab index which will hold the tab ID. When Tab is pressed the selected state change to the pressed tab ID. I am comparing the selected state to tab ID. If both are equal then the selected tab will have some different style.
But when state changes and condition is true, the selected tab is not changing its state. Why change in state do not trigger the comparison in the style to update the style?
<FlatList
horizontal
data={this.state.drinkgroup}
showsHorizontalScrollIndicator={false}
renderItem={({item, index}) =>
{
return <Tab
item={item}
selected={this.state.selected}
changeSelected={
() => {
this.setState({selected: item.id}, function(){ console.log(this.state.selected, item.id)
console.log(this.state.selected==item.id)
})
}}
}
}
/>
export const Tab = ({item, selected, changeSelected}) => {
return (
<TouchableOpacity
style={[styles.tabStyle, (selected==item.id)? styles.tabSelectedStyle: null]}
onPress={changeSelected}
underlayColor='#fff'
>
<Text style={{color: '#f2f2f2', textAlign: 'center', fontSize: 15}}>{item.name}</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
tabStyle: {
backgroundColor: '#800080',
height: 32,
paddingRight: 15,
paddingLeft: 15
},
tabSelectedStyle: {
borderBottomColor: 'white',
borderBottomWidth: 3
}
})
By passing extraData={this.state} to FlatList we make sure FlatList itself will re-render when the state.selected changes. Without setting this prop, FlatList would not know it needs to re-render any items because it is also a PureComponent and the prop comparison will not show any changes.
<FlatList
data={this.props.data}
extraData={this.state}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
/>
This is a PureComponent which means that it will not re-render if props remain shallow- equal.
Make sure that everything your renderItem function depends on is passed as a prop (e.g. extraData) that is not === after updates, otherwise your UI may not update on changes.
This includes the data prop and parent component state.
More Details :- FlatList
You need to provide extraData prop to the Flatlist, if you want it to re render its items. You can do it like extraData={this.state}.
By passing extraData={this.state} to FlatList we make sure FlatList itself will re-render when the state.selected changes. Without setting this extraData prop, FlatList would not know it needs to re-render any items because it is also a PureComponent and the prop comparison will not show any changes.
For further details you can visit official documentation here

React Native FlatList onViewableItemsChanged Returning Incorrect set of Items

I am trying to use onViewableItemsChanged event to detect the items of my FlatList that are currently displayed on screen.
In my ViewabilityConfig (Code is provided below), I set the itemVisiblePercentThreshold parameter to 100 which I assumed will require my item to be fully displayed to be considered viewable. However that is not the case for me.
As you can see in the following screenshot:
Screenshot of my App
It is obvious that the top most item is not completely on screen (Which should make the visible items consist only of 3 items). But when I print the length of the array in my onViewableItemsChanged event handler, it returns 4 (and when I inspect the values, including the top most item).
Log Result of Viewable Items Array Length
Is this the problem of FlatList onViewableItemsChanged event? Or did I implemented it incorrectly?
I tried to find solution from the documentation and React-native github but there is no further explanation about how this event works.
Some related snippets of my code are as follow:
FlatList Definition
<FlatList
viewabilityConfig={this.clippingListViewabilityConfig}
inverted={true}
horizontal={false}
data = {this.props.clippingResultArray}
ref={(ref) => this.clippingResultFlatList = ref}
style={{
// flexGrow:0,
// backgroundColor: 'green',
// width:'100%',
// width: Dimensions.get('window').width,
}}
contentContainerStyle={{
// justifyContent:'flex-end',
// flexGrow:0,
// flexDirection:'row',
// alignItems:'flex-end',
}}
renderItem={this.renderClippingListItemRight}
keyExtractor={(item, index) => index.toString()}
onViewableItemsChanged={this.onClippingListViewableChanged}
// removeClippedSubviews={true}
{...this._clippingListItemPanResponder.panHandlers}
/>
onViewableItemsChanged Listener
onClippingListViewableChanged = (info) => {
console.log("***************************NUMBER OF CURRENT VIEWABLE ITEMS:",info.viewableItems.length);
console.log("Item list:",info.viewableItems);
this.setState({
...this.state,
viewableItems: info.viewableItems,
});
};
Viewable Configuration
this.clippingListViewabilityConfig = {
waitForInteraction: false,
itemVisiblePercentThreshold: 100,
minimumViewTime: 500, //In milliseconds
};

Get TextInput value from different component

I have 2 react native components in my app, one is a toolbar that has a "Done" button, pressed when a user is done filling a form.
The other is the form itself from where I need to get the data.
When the user clicks "Done" I send a post request with the parameters, but I can't find a neat way to get the data.
What is best practice for this?
My toolbar:
<TopToolbar text='Upload new item'
navigator={this.props.navigator} state={this.state} donePage={'true'}/>
In the toolbar component I have the done button:
<TouchableHighlight style={styles.done} onPress={() =>{this.state.text=this.props.state.data.price}} underlayColor='#4b50f8'>
<Image source={require('./Images/Path 264.png')}/>
</TouchableHighlight>
and one of the text inputs is:
<TextInput placeholder='Price*' style={styles.text} onChangeText={(text) => { this.state.data.price = text }}></TextInput>
Use state. You need to bind the view to the model => (state). Please add your code for a better guide.
Each time that you press and new character need be saved in your state using onChangeText
Example:
class UselessTextInput extends Component {
constructor(props) {
super(props);
this.state = { text: 'Useless Placeholder' };
}
render() {
return (
<TextInput
style={{height: 40, borderColor: 'gray', borderWidth: 1}}
onChangeText={(text) => this.setState({text})}
value={this.state.text}
/>
);
}
}
When you press in Done button. The TextInput value will be stored in this.state.text
For more info: https://facebook.github.io/react-native/docs/textinput.html
I think your main problem is that you need to read more about the states in the react documentation
https://facebook.github.io/react-native/docs/state.html
When you needs set the state. You should use setState not this.state.data.price = text. The state is a object with multiple keys y your needs modify one internal key inside data you need modify all data key and replace it.
Example:
In your constructor declare
this.state = { data: {price: 10, name: xxxx} };
if need modify data you should do something like.
var dataM = this.state.data;
dataM.price = 200
this.setState({ data: dataM});