Struggling with useEffect and flatlist - react-native

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]);

Related

Why is data from useQuery undefined when I start screen?

When I start screen, I get data from useQuery
And by using useEffect, I get data and immediately sort this data.
After sorting data, I put this data to useState named flatlistdata.
And I draw screen by using Flatlist with this data.
Flatlist data should be sorted.
So I need to refined data before.
const { data: allFeedData, refetch: allFeedRefetch } = useQuery(SEE_ALL_FEED_ORDER);
const [flatlistdata, setFlatlistdata] = useState([]);
useEffect(() => {
allFeedRefetch();
setFlatlistdata(
[...allFeedData.seeAllFeedOrder].sort(function (a, b) {
return b.directFeedNumber - a.directFeedNumber;
})
);
}, []);
<FlatList
data={flatlistdata}
keyExtractor={(item) => item.id}
renderItem={RankRow}
refreshing={refreshing}
onRefresh={refresh}
/>
However when I click screen, it says undefined is not an object which means data is empty.
I think the problem here is that screen is drawn, before sorting data and putting it to useState...?
I need a little hint 😭
Please help me.
The query is an asynchronous operation, so, the data variable will always start out as undefined while the network request completes. There are two possible solutions.
In your useEffect, only set your state if data is defined. Wrap your setFlatlistData with if (allFeedData?.length) { ... }
Use the React Query selection prop to have Query do the same operation before allFeedData gets populated. Check the select prop in the docs for useQuery: https://react-query-v2.tanstack.com/reference/useQuery

How can I create a React Native Button that saves an array value and the Button state to the device using AsyncStorage?

I am trying to create a button that changes the text when clicked and it saves a value to an array. I then need to save the state of the button and the array to the device using AsyncStorage.
I have the first part done (please see Snack link here) but I am struggling to get the AsyncStorage to work properly. If the button had been clicked once, I would expect that the array has the "item 1" value in it and the button would say clicked. Even if the app is closed and reopened, those values should still remain until the button is clicked again.
I have not found any solutions so far. Is there anyone that has some ideas?
This is the workflow that you should follow:
create an object in state (in this.state = {} for classes, or setState() with hooks) for your button's text value
initialize the above value with the value from AsyncStorage (make sure to add some conditional that if it's empty it returns [])
also take note of how Asyncstorage is async, meaning you'll have to add 'await' when you're assigning the value
add some conditional for the text value of your button, whereas it will show a loading icon, (or nothing, doesn't matter) while AsyncStorage is retrieving the initial data
onPress on your button will change the state value AND the AsyncStorage value (or you can only update the AsyncStorage value when you're closing the page with componentWillUnMount, or useEffect(() => return(--do it here--))
If you're using functional components, it would look something like this:
const [textValues, setTextValues] = useState([])
const setInitialValues = async () => {
const info = await AsyncStorage.getItem('myValues')
setTextValues(info)
}
useEffect(() => {
setInitialValues()
return(
AsyncStorage.setItem('myValues', textValues)
)
}, [])
return(
<View>
<Button onPress={() => setTextValues(textValues + 1) title={textValues.length === 0 ? '' : textValues[textValues.length - 1]}}
</View>
)

What is the best way to initialize a state in ReactNative with custom values on a Modal?

So, I have a specific problem where i need to initialize a state with custom values that comes from the parent component props.theValue. But the problem is that the props.theValue isn't ready when we set the state. So this line should never work:
const [grams, setGrams] = useState(props.theValue)
The scenario:
We have a list of food on the Parent Screen, when the users Clicks on it, it opens a modal (Like a PopUp) with some options to choose before the selected item gets manipulated
What I tried?
Set initial flag useState(-1) to indicate it should set the value from props but again, the props isn't ready in this state
Save the state on the parent and pass the state with the set Function to the modal. But still it doesn't seems to be right
useEffect but whenever i call setGrams it get a Loop Rendering error, also the props.theValue is still undefined at this point
Example:
In this case, the both TextInput must have a initial value that comes
from the Parent Component and must be manipulated.
You could use Redux for manipulating your data between Components or you can try something like:
set a Global variable:
let previousObj = null
and then set a timeout to update your data only once, even if you set Modal invisible and Visible again
setTimeout(() => {
if(food.grams != undefined && previousObj != food){
previousObj = food
setGrams(""+food.grams)
}
}, 100)
You can either handle it in the child with default value or don't render the component in parent until the value is available:
{theValue && <Component theValue={theValue} />}
You can use useEffect to update the default value as well:
const [grams, setGrams] = useState(0)
useEffect(() => {
if (theValue) {
setGrams(theValue);
}
}, [theValue])

React Native FlatList Not Re-Rendering after Asyncronous operation

I have an async function like this:
getDeals() {
if(this.props.user) {
this.setState({loading: true});
this.setState({deals: []});
var parameters = {
zip: this.props.user.zip,
sort: 'All',
category: this.props.selectedCategory,
company: this.props.user.company,
page: null,
user: this.props.user,
search: null
}
axios.post(`${constants.api}/grab-deals/`, parameters)
.then((response) => {
this.setState({totalDeals: response.data.length});
this.setState({deals: response.data, loading: false, refreshing: false});
this.forceUpdate();
})
}
}
And a FlatList component Like this:
<FlatList data={this.state.deals} style={{flex: 1, padding: 10}} extraData={this.state} keyExtractor={this.keyExtractor} renderItem={this.renderDeal.bind(this)} />
Here is the keyextractor:
keyExtractor = (item, index) => item.id;
When I call this.getDeals() the first time it works great. However when I call it a second time the axios call get's all of the correct data, but the flat list still keeps old data (it doesn't remove items that aren't in the new call).
How do I get the FlatList to always reflect the returned data?
Call this.getDeals() in componentWillUpdate() and update props?
I believe you confussing what props and state is for. Basically state is used for things that could change during the lifecycle of the component and props are kept immutable. I use them for behavior.
Unless you are changing the parameters for the getDeals function on the second call, see that all of the properties are based on the props, which are not always updated.
RN has a method called componentWillUpdate that is triggered with the new props which you can then be used to update the component itself. If you want to keep using props in your getDeals method, you will need to check if the props have changed (this happens when the parent updates the child with new props) and then trigger again the data fetch.
If this does not help, please post more code.
According to the docs you need to set the state.selected value
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.

Using FlatList#onViewableItemsChanged to call a Component function

I'm currently attempting to implement a form of LazyLoading using the FlatList component, which introduces a neat little feature called onViewableItemsChanged which gives you a list of all of the components that are no longer on the screen as well as items that are now on the screen.
This is a custom LazyLoad implementation and as such is more complicated than most LazyLoad open-sourced libraries that are available, which is why I'm working on my own implementation. I'm already looked into react-native-lazy-load and others.
Basically, I need to be able to call a function that's part of the component being rendered in the FlatList, I've tried creating a reference to the item rendered in the FlatList and calling it as such, but it doesn't seem to work.
For example:
<FlatList data={...}
renderItem={(item) => <Example ref={(ref) => this[`swiperRef_${item.key}`] = ref}}
onViewableItemsChanged={this.onViewableItemsChanged}
/>
onViewableItemsChanged = ({viewableItems}) => {
viewableItems.forEach((item) => {
const { isViewable, key } = item;
if(isViewable && !this.cachedKeys.includes(key)) {
const ref = this[`swiperRef_${key}`];
if(!ref) return console.error('Ref not found');
ref.startLoading();
this.cachedKeys.push(key);
}
});
}
Now in the <Example /> component I would have a function called startLoading which should be called when a new visible item is brought onto the screen, however the ref never exists.
I was actually doing everything correctly, but I accidently forgot to deconstruct the parameter returned from the renderItem function, so (item) should have been ({ item })
That's all there was to it.