React-native: how to change row's content or style while swiping? - react-native

I want to have a functionality similar to Google Inbox (demo), when swiping an item causes a change of the background color and size of the icon.
My implementation has a ListView of Swipeable components as rows and I want to change the content or style of a row (in the right pane for example) based on the swipe position.
In order to have the ListView to re-render while swiping I added a pos state which I set using the onPanAnimatedValueRef prop of the Swipeable.
The problem is that the ListView doesn't re-render and nothing changes.
<ListView
...
renderRow={(data) => (
<Swipeable rightContent={
<View><Text>{this.state.pos}</Text></View>
}
...
onPanAnimatedValueRef={(pan) => {
pan.addListener(val => {
this.setState({
pos: val.x
});
}}>
{data}
</Swipeable>
)}
/>
Do you see any problem with this?

I would wrap the Swipeable in your own component, hold state there and your animation logic. It is not the ListView's responsibility to rerender the whole thing since the data hasn't changed, just your row itself.
Alternatively, you could change the data via setState to the ListView's data source (which presumably was based in state and hence force a rerender but this is a less efficient path)

Related

Struggling with useEffect and flatlist

I am rendering a component for every item in a flatList. Each component has a label, and when the component is rendered, I have a useEffect that fetches the updated label name for that specific label.
For some reason, it seems to only be running for the last item in the flatList. The last item is the only item with the updated name, while all other still contain the outdated information.
Assuming there is an updated name for each label, why could my useEffect only be running on the last item?
<FlatList
data={labels}
keyExtractor={keyExtractor}
renderItem={renderItem}
/>
Label.js - I would think this would run for every label component rendered. Could there be a possible issue with what I have here? Or must it be somewhere else in my code?
let name = label.name;
useEffect(() => {
updateLabel()
name = label.name
}, [label]);
return (
<>
{name}
</>
)
I see several possible issues. Some important code is missing, so I'll answer what I can.
You're not using state to hold your label name in the Label component (name = label.name), so React will never know to re-render the component when it changes. It's rare to need to use a let variable in React. To hold properties that the component needs to change, use the useState hook.
However, you shouldn't do that here, because of the next point.
It looks like you are updating the label somewhere else, and also locally (name = label.name). Don't do this, it's too easy for the two to get out of sync and cause bugs. If the name is coming from somewhere else, show it and set it from props.
I'm not sure what updateLabel() does or where it comes from (how does the function know what to update the label to?), but if you need it, it should come from props.
If label.name is a string, you can't render it in a fragment. You must render it in a Text component. <Text>{label.name}</Text>
The object that FlatList passes in to the renderItem callback does not have a property called label, you are looking for item - this is the object from the data prop.
function renderLabel({ item }) { // item, not label
return <Label label={item} onPress={() => onPressLead(item)}/>;
}
const Label = ({ label, updateLabel }) => {
// no local label variable
useEffect(() => {
updateLabel(); // what is this supposed to do?
}, []); // no dependencies, if you only want to update the label once on mount
return <Text>{label.name}</Text>; // if label.name is a string
};
// your FlatList is fine as written
Your use effect probably needs the label as a dependency.
useEffect(() => {
updateLabelName()
}, [label]);

Flatlist of buttons does not render on first navigation

I have a Stack.Navigator, when I navigate to a page that contains my FlatList which renders 4x6 items at once, all of the other components will render and the flatlist will be invisible/not rendered. The app then locks up, and then after that the flatlist renders all the items and the layout is bumped around which is not desirable. Is this expected behaviour and what can I do about it? I'd prefer the whole app freeze/stutter until all of the first page is ready to show at once, rather than rendering everything but the Flatlist and then bumping the UI around.
The FlatList component is very uncontroversial as far as I can see.
<View>
<FlatList
numColumns={4}
columnWrapperStyle={styles.colWrap}
keyExtractor={(theitem) => theitem.id}
renderItem={renderIndividualSound(onSoundButtonPress)}
data={soundPage}
key="soundspage"
/>
</View>
Upon further inspection, I was using a useState() hook that init'd with a blank array, and then using a useEffect(()=>{}, []) hook to update the state once at initialisation, meaning a blank list was passed through for the very first render.

How to wait for a function to execute by all components of flatlist?

I want to solve this in a much elegant way but I feel the code currently is pretty messed up. So I have a parent component that contains a Flatlist and a button. The Flatlist contains child components that have checkboxes, on press of the button, I want to check which checkboxes in my Flatlist are ticked. The way I tackled this problem was by passing down a prop into the child component. This is how my parent FlatList and state looks -
constructor(props) {
super(props);
this.state = {
getAllVendorDetails: 0, //This state is to trigger an event(by
incrementing) in the child checkboxes to send the ticked vendors to
this parent component.
};
}
<FlatList
data={this.state.vendorsToPick}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => <SelectVendors getAllVendorDetails=
{this.state.getAllVendorDetails} pushData={(item) =>
this.addVendor(item)} vendorData={item}></SelectVendors>}
horizontal={true}
extraData={this.state.getAllVendorDetails}
showsHorizontalScrollIndicator={false}
/>
On press of the button, I increment the state getAllVendorDetails, which triggers a rerender in my child component and in componentDidUpdate I push the component that has its checkbox ticked, to the parent, as a parameter through a prop - Which inturn calls a function in the parent component that adds these items to an array. All this works perfectly fine, I get all the ticked checkboxes on press of the button. (Maybe there's a better way to do this, idk).
I want to perform an operation ONLY after i've received all the items that have a ticked checkbox in my array, I dont know how I could achieve that, currently I have a delay of 500ms after pressing my button, and then if I check my array, I have all the ticked checkbox items. But for sure there has to be a better way to do this.

ReactNative [Flatlist] scrollToOffset, how do I figure out offset position of clicked row?

I want a row in a FlatList to scroll to the top of the screen whenever a user clicks on it. I am currently doing this by using the scrollToIndex method but the problem is that I have to animate the top margin (from 10 to 0) to get it flushed to the top of the screen. I also have an issue where if a user clicks on a row to expand the row, and a previous row was already expanded, the previous row will collapse causing the original scrollToIndex to animate to some position off the top of the screen.
My current two alternatives are to trigger an additional scrollToIndex once the previous row finishes contracting or use scrollToOffset to figure out where to scroll to based on good old math values. Is there a way to figure out row offset positions by index or some other value whenever it is clicked?
Thanks for any help anybody might be able to provide!
You can use scrollToItem to achieve what you are trying to achieve
So suppose you have this FlatList component
<FlatList
ref={(ref) => { this._flatList = ref; }}
data = {dataSourche}
renderItem = {( info: {item:item, index:number}) => this._renderFlatListItem(info.item, info.index)}
keyExtractor = {(item) => item.id}
decelerationRate={0}
snapToInterval={cardHeight}
snapToAlignment={"start"} //Can also be 'center' or 'end'
onMomentumScrollEnd = {(event) => this._onMomentumScrollEnd(event)}
getItemLayout = {this.getItemLayout}/>
You will need to define a method getItemLayout that will tell the flatList 3 things
length: In your case height of the card
offset: this is distance from top of the list to current card usually we can set it as length * index of the card
index of the card
so this is a sample getItemLayout which you can refer to
getItemLayout = (data, index) => (
{ length: 170, offset: 170 * index, index }
);
Next will be your FlatList scrollToItem call
this._flatList.scrollToItem({
animated:true, //can also be false
item:item,
viewPosition:0 //this is the first position that is currently attached to the window
})
Instead of trying to figure out where to scroll to using the scrollToOffset method, I'm still using the scrollToIndex method and use the viewOffset parameter in order to calculate how much offset to add to the method: the difference between the expanded and collapsed heights of any other currently expanded item (if any) and subtracting the top margin of the clicked item.

TouchableHighlight and TouchableOpacity get highlighted on render()

I experience a behaviour where TouchableHighlight and TouchableOpacity reacts visually upon render (onPress is not being called).
One thing is that it looks just a little strange, when I enter the page and my button make a small "blink". This is strange but tolerable. The more frustrating part is that if I alter state for the parent component and thus invoke a re-render(), the button will "blink" again, making all buttons blink whenever I alter state.
Pushing the buttons alters page state, and thus pushing a button makes both buttons "blink".
I use react-redux, but this should not affect this behaviour.
The code below is just for illustration.
render()
{
return(
<View>
<ToucableHightlight> //Click here changes state
<Content/>
</ToucableHightlight>
<ToucableHightlight> //Click here changes state
<Content/>
</ToucableHightlight>
<View>
);
}
Add activeOpacity in TouchableOpacity and it will force to not blink.
<TouchableOpacity style={styles.opecity} activeOpacity={1}>
I solved the problem. Earlier during my render function i defined the "Content"-components, resulting in new (but alike) components being defined during each update. Placing the definitions of "Content" outside of the render function fixed it, so that the components no longer flashes when the page is re-rendered.
This explains why my component was rendered as a new component upon each render in the parent component, but it does not explain why a TouchableHighlight blinks during its initial render.
Buttons blinking during initial render is acceptable to me - buttons blinking upon any state-change is not.
So I am sufficiently happy now.
Not sure if it's because I'm running a later version, but I found this blinking behavior happens only on the first click.
My solution was putting the code that triggers rerendering in a setTimeout
<TouchableOpacity
onPress={function() {
setTimeout(function() {
_this.setState({myState: 'someValue'})
});
}}
>