Issue with react-native FlatList rendering data when updating array of objects maintained in redux - react-native

I'm loading a dynamic form in a FlatList. I'm supposed to add few rows with UI elements such as TextInput, checkbox, etc. based on a condition. For example, I have a dropdown, when I select a value, I have to add a row with a button and remove that row when I select another value.
My issue is that whenever I select a value in the dropdown to add a row with button, FlatList is adding duplicate rows and they are not visible as objects in my array when I debug them. I'm using Redux to manage my application state and updating my array in reducers.
I have to display objects in my array in FlatList without any duplicates using Redux for state management.
Code:
<FlatList
key={Math.floor(Math.random())}
ref={(ref) => { this.flatListRef = ref; }}
data={this.state.mainArray}
renderItem={this._renderItem}
keyExtractor={({ item, id }) => String(id)}
extraData={this.prop}
keyboardDismissMode="on-drag"
showsVerticalScrollIndicator={false}
scrollEnabled={true}
removeClippedSubviews={false}
scrollsToTop={false}
initialNumToRender={100}
/>
componentWillReceiveProps(nextProps) {
this.setState({mainArray: nextProps.mainArray});
}
Updating array in Reducer:
case VALIDATE_DEPENDENCY: {
// adding or removing dependency questions
let dependingQues = [];
let formArrayTemp = [...state.mainArray];
let exists;
// console.log("formArrayTemp123", formArrayTemp);
if (action.item.isDependentQuestion) {
if (action.item.dependentQuestionsArray.length > 0) {
let dependent_ques = action.item.dependentQuestionsArray[0];
state.depending_questions_array.filter((obj, id) => {
if (JSON.stringify(obj.key_array) === JSON.stringify(dependent_ques.key)) {
dependingQues.push(obj);
}
});
// console.log("dependingQues123", dependingQues);
if (dependent_ques.answer === action.item.answer) {
dependingQues.map((obj, id) => {
exists = formArrayTemp.filter((res, idx) => {
if (JSON.stringify(res.key_array) === JSON.stringify(obj.key_array)) {
return res;
}
});
// console.log("exists123", exists);
if (exists.length === 0) {
formArrayTemp.splice(action.index + id + 1, 0, obj);
// console.log("mjkgthbythyhy", action.index + id + 1);
}
});
}
else {
let objIndex = formArrayTemp.findIndex(obj => JSON.stringify(obj.key_array) === JSON.stringify(dependent_ques.key));
if (objIndex !== -1) {
formArrayTemp[objIndex].answer = "";
formArrayTemp[objIndex].borderColor = "black";
formArrayTemp.splice(objIndex, 1);
// console.log("frtg6hyjukiu", formArrayTemp);
}
}
}
}
// adding or removing dependent sections
let dependentSections = [];
let tempArr = [];
let count = 0;
if (action.item.isDependent) {
if (action.item.dependentArray.length > 0) {
let dependent_section = action.item.dependentArray[0];
state.dependent_sections_array.filter((obj, id) => {
if (obj.section_id === dependent_section.section_id) {
dependentSections.push(obj);
}
});
// console.log("dependentSections123", dependentSections);
let lastIndex;
formArrayTemp.filter((res, id) => {
if (action.item.section_id === res.section_id && res.dependentArray &&
JSON.stringify(action.item.dependentArray) === JSON.stringify(res.dependentArray)) {
tempArr.push(res);
lastIndex = formArrayTemp.FindLastIndex(a => a.section_id === res.section_id);
}
});
// console.log("lastIndex123", lastIndex);
tempArr.map((obj, id) => {
if (obj.answer === obj.dependentArray[0].answer) {
count++;
}
});
if (dependent_section.answer === action.item.answer) {
dependentSections.map((obj, id) => {
if (formArrayTemp.contains(obj) === false) {
formArrayTemp.splice(lastIndex + id + 1, 0, obj);
// console.log("cfrererfre", formArrayTemp);
}
});
}
else {
let countTemp = [];
if (count === 0) {
let countTemp = [];
dependentSections.filter((res, id) => {
if (formArrayTemp.contains(res) === true) {
formArrayTemp.filter((obj, idx) => {
if (obj.section_id === res.section_id) {
obj.answer = "";
obj.borderColor = "black";
countTemp.push(obj);
}
});
}
});
let jobsUnique = Array.from(new Set(countTemp));
formArrayTemp = formArrayTemp.filter(item => !jobsUnique.includes(item));
// console.log("frefrefre123", formArrayTemp);
// return { ...state, mainArray: [...formArrayTemp] }
}
}
}
}
console.log("formArray123", formArrayTemp);
formArrayTemp.sort((a, b) => {
return a.posiiton - b.position;
});
return { ...state, mainArray: formArrayTemp }
};
Update
After researching this issue I came across this issue #24206 in GitHub. There it states:
There's a few things you aren't doing quite correctly here. Each time the render function runs you are creating a new array object for your data, so React has to rerender the entire list each time, you're renderItem function and keyExtractor function are also remade each rerender, and so the FlatList has to be rerun. You're also using the index for your key which means as soon as anything moves it will rerender everything after that point. I would recommend looking at what does/doesn't change when you rerun things (arrays, functions, object), and why the keyExtractor is important for reducing re-renders.
So I tried commenting KeyExtractor prop of FlatList and the issue got resolved. But this is causing issue in another form. How can I resolve that?

Related

In React-native i'm using 'sharingan-rn-modal-dropdown'; Dropdown in form for add and edit data but selected values not getting reflected in dropdown

what exactly is happening is when i select the delivery service onChangeService i'm setting the next dropdown i.e. carrier's list.Also i'm setting the selected carrier as per the service.But all the time value is getting set correctly in {this.state.selectedCarrierName} but still i can show its label only.lists contain label an value pair
async onChangeService(value) {
//{ ...this.props.deliveryOptionsValue, selected: value.value === value }
this.setState({ newCarrierList: [], selectedCarrierName: '', selectedCararierName: '' });
const priceDeliveryService = this.props.orderDetailsDTO.priceDeliveryServiceDTOs;
console.log("carriers", JSON.stringify(this.props.carriers));
var newList = [];
var carrier = '';
await priceDeliveryService.forEach(m => {
console.log("compare", m.serviceId === value.id);
if (m.serviceId === value.id) {
console.log("carrier", JSON.stringify(this.props.carriers));
let l = this.props.orderDetailsDTO.carriers.filter(n => n.id === m.carrierId);
console.log("l", l);
if (l !== undefined) {
// / orderList: [...this.state.orderList, ...content]
// this.setState({ newCarrierList: [...this.state.newCarrierList, ...l[0]] });
newList.push(l[0]);
console.log("defa", newList);
if (m.defaultCarrierService) {
this.setState({ selectedCarrier: l[0], selectedCarrierName: l[0].name });
carrier = l[0].name;
} else {
this.setState({ selectedCarrier: newList[0], selectedCarrierName: newList[0].name });
carrier = newList[0].name;
}
}
}
});
const list = newList.map(item => ({
label: item.name,
value: item
}));
this.setState({ newCarrierListValue: list, newCarrierList: newList })
console.log("newCarrierListValue", list);
console.log("newCarrierList", newList);
// console.log("carrier service", p.carrierName, value.id);
const carrierServices = this.props.orderDetailsDTO.carrierServiceDTO;
console.log(carrierServices.length);
const pi = await this.setCarrierService(carrierServices, value, carrier);
// let pi;
// for (let i = 0; i < carrierServices.length; i++) {
// console.log("if", carrierServices[i].cdlServiceId == value.id, carrierServices[i].carrierName === carrier)
// if (carrierServices[i].cdlServiceId == value.id && carrierServices[i].carrierName === carrier) {
// console.log("mathced data", carrierServices[i]);
// pi = carrierServices[i];
// console.log(pi);
// break;
// }
// }
this.setState({ carrierService: pi.serviceName, carrierServices: carrierServices });
console.log(pi.serviceName);
}
<Dropdown required
label="Select delivery option"
data={this.props.deliveryOptionsValue}
// enableSearch
defaultValue={1}
value={this.state.selectedDeliveryOptionName}
onChange={(value) => {
console.log(" change value", value);
this.setState({ selectedDeliveryOptionName: value.name, selectedDeliveryOption: value, deliveryOptionError: false, });
this.onChangeService(value);
}} />
<Dropdown
required
label="Select a carrier"
data={this.state.newCarrierListValue}
// selectedValue={this.state.selectedCarrierName}
value={this.state.selectedCarrierName}
onChange={value => {
// console.log("value", value);
this.setState({ selectedCarrierName: value.name, selectedCarrier: value });
this.onChangeCarrier(value, this.state.selectedDeliveryOption);
}} />

React Native updating only after I save file

I have an application that queries the stock exchange in real time, but when I open the application the first time it does not update any displayed value, as if it were hardcoded, but when I save the file (ctrl + S) it starts to update normally , Can anyone help me with this?
description code below:
this function is listening the data on firebase;
ticketRT is my state that is not updating at first time
Object.keys(subscriptionStocks).forEach((stock) => {
if(!subscriptionStocks[stock].listener) {
firebase.database().ref(`${endPointStocks}/${stock}`).on('value', async data => {
let stock = data.val();
if (stock) {
stockArray[stock.ticket] = stock;
setTicketRT(stockArray);
await updateListStocks(stock);
} else {
console.log(`[listenerStock]: Não foi possível cadastrar o ativo "${stock}"`);
}
});
subscriptionStocks[stock].listener = true;
this function updates the stock list
let tmpStock = {};
console.log('LENGTH 11111', stockArray.length);
if (stockArray.length === 0) {
console.log('LENGTH 22222 ≥≥≥ 2', stockArray.length);
tmpStock[stock.ticket] = stock;
stockArray.push(tmpStock);
setTicketRT(stockArray);
updateWalletBalance();
} else {
const foundStockInArray = stockArray.some(
el => {
let inStock = el[Object.keys(el)[0]];
return inStock.ticket == stock.ticket;
}
);
if (foundStockInArray) {
updateStock(stock);
} else {
tmpStock[stock.ticket] = stock;
stockArray.push(tmpStock);
setTicketRT(stockArray);
updateWalletBalance();
}
}
this is my FlatList that is not updating
<FlatList
style={styles.actionList}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
data={ticketRT}
extraData={(item, index) => {
if(ticketRT !== undefined) {
let stocks = ticketRT[index];
let stock = stocks[Object.keys(stocks)[0]];
return stock.ticket;
}
}}
keyExtractor={(item, index ) => {
if(ticketRT !== undefined) {
let stocks = ticketRT[index];
let stock = stocks[Object.keys(stocks)[0]];
return stock.ticket;
}
}}
renderItem={({ index }) => {
let stocks = ticketRT[index];
let stock = stocks[Object.keys(stocks)[0]];
return <Ativos data={stock} />
}}
contentInset={{ bottom: 10, top: -5 }}
/>
ALL CODE IS FOUNDED HERE: https://gist.github.com/paulorod07/4e74976345a6a68b0e32755cf5ae1c46

setinterval function is running infinitely

const interval = setInterval(() => {
fire.database().ref().child(getpath())
.once("value",
(snapshot) => {
let item = snapshot.val()
console.log(item)
if (item !== null) {
let array = [];
Object.
keys(item)
.forEach(i => array.push(item[i]));
setCard1(array);
}
console.log(item, "item")
if (item !== null) {
itemlen = 7 //length of object I get from valid result
//stop polling for results
console.log(itemlen, "should clear")
}
else {
console.log("polling")
}
})
}, 1000)
console.log("comingout")
if (itemlen !== 0) {
console.log("goingIn")
clearInterval(interval);
}
}, [prefVar]);
expected clearinterval to stop the setinterval function but it is running continuosly and not stopping
itemlen is getting non zero values.Is there a better way of implementing this ?
I want stop useEffect once I get valid value from db.My code inside the for setinterval selects a random path and retrieve that path only problem is that sometimes the path is empty,thus using setInterval
I would create two state items, one which keeps the interval and the other which stores itemlen, and would use another useEffect to listen on changes to itemlen, and when it is not 0, the interval should clear. Also, I would check if there is another interval running before you start another one.
const [itemlen, setItemlen] = useState(0);
const [pollingInterval, setPollingInterval] = useState(null);
useEffect(() => {
if (pollingInterval === null) {
setPollingInterval(setInterval(() => {
fire.database().ref().child(getpath())
.once("value",
(snapshot) => {
let item = snapshot.val()
console.log(item)
if (item !== null) {
let array = [];
Object.
keys(item)
.forEach(i => array.push(item[i]));
setCard1(array);
}
console.log(item, "item")
if (item !== null) {
setItemlen(7);
console.log("should clear")
} else {
console.log("polling")
}
})
}, 1000));
}
}, [prefVar]);
useEffect(() => {
if (itemlen !== 0 && pollingInterval !== null) {
clearInterval(pollingInterval);
setPollingInterval(null);
}
}, [itemlen])

Hide/Show the corresponding data from the chart on legend click ngx-charts

I am working with angular 6 and ngx-chart and I need on clicking the legend item, the corresponding data from the chart should show/hide
The ng-chart library does have this functionality and my client requests it.
Edit01:
I have almost everything working but I have a problem when applying axisFormat. once I remove an item from the legend it reformats the x-axis and doesn't literally put how the data comes without applying the AxisFormat. Any solution?
onSelect (event) {
let temp = JSON.parse(JSON.stringify(this.multi));
this.sourceData = JSON.parse(JSON.stringify(this.multi2));
if (this.isDataShown(event)) {
//Hide it
temp.some(pie => {
const pie2 = pie.name[0] + ',' + pie.name[1];
// console.log('pie', pie[0], event, pie2);
if (pie2 === event) {
pie.series = [];
return true;
}
});
} else {
//Show it back
console.log('Entramos en el ELSE');
const pieToAdd = this.sourceData.filter(pie => {
const pie2 = pie.name[0] + ',' + pie.name[1];
return pie2 === event;
});
temp.some(pie => {
const pie2 = pie.name[0] + ',' + pie.name[1];
if (pie2 === event) {
pie.series = pieToAdd[0].series;
return true;
}
});
}
console.log('log temp: ' + JSON.stringify(temp));
this.multi = temp;
// this.axisFormat(this.multi);
}
isDataShown = (name) => {
const selectedPie = this.multi.filter(pie => {
const pie2 = pie.name[0] + ',' + pie.name[1];
return pie2 === name && pie.series[0] !== undefined;
});
return selectedPie && selectedPie.length > 0;
}
axisFormat(val) {
const options = { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' };
// Esto funciona pero al hacer timeline no pone horas val.toLocaleDateString("es-ES", options);
console.log('val:', val.toLocaleDateString('es-ES', options));
return val.toLocaleDateString('es-ES', options);
}
HTML
<ngx-charts-line-chart [view]="" [scheme]="colorScheme" [results]="multi" [gradient]="gradient" [xAxis]="showXAxis" [yAxis]="showYAxis" [legend]="showLegend" legendPosition="'below'" [showXAxisLabel]="showXAxisLabel" [showYAxisLabel]="showYAxisLabel"
[xAxisLabel]="xAxisLabel" [yAxisLabel]="yAxisLabel" [autoScale]="autoScale" [timeline]="timeline" [roundDomains]="true" [animations]="animations" (select)="onSelect($event)" [xAxisTickFormatting]="axisFormat">
<ng-template #seriesTooltipTemplate let-items="model">
<p>{{items[0].name | date:'medium'}}</p>
<ul>
<li *ngFor="let item of items">
{{item.series}}: {{item.value | number}}
</li>
</ul>
</ng-template>
</ngx-charts-line-chart>
EDIT
Hello,
I have already managed to solve the problem adding an example in case it can help other people.
https://stackblitz.com/edit/click-lengd-ngx-charts
I am kinda new to Stack Overflow, but i think you should specify your answer more and show us what you already tried. Nevertheless I will try to help you.
You should give your chart a (select)="onClickFunction ($event)" in HTML. In your TS you then call the onClickFunction(event). I always start with giving it a console.log(event) to see what i get from clicking on the legend.
After clicking on the legend, you get the label of the element you clicked on. You can then search for this label in your data and remove this data out of the array you use for filling the chart.
If you provide a stackblitz or plunker wtih your code, I will gladly show you how to do it.
This is how we can achieve it for ngx-pie-chart. With the help of select event, capture it, identify the item from the data and make it zero. Next, on the next click, add the value back from the source copy. See it working here ngx-pie-chart label-filter
onSelect (event) {
let temp = JSON.parse(JSON.stringify(this.single));
if (this.isDataShown(event)) {
//Hide it
temp.some(pie => {
if (pie.name === event) {
pie.value = 0;
return true;
}
});
} else {
//Show it back
const pieToAdd = this.sourceData.filter(pie => {
return pie.name === event;
});
temp.some(pie => {
if (pie.name === event) {
pie.value = pieToAdd[0].value;
return true;
}
});
}
this.single = temp;
}
isDataShown = (name) => {
const selectedPie = this.single.filter(pie => {
return pie.name === name && pie.value !== 0;
});
return selectedPie && selectedPie.length > 0;
}

Cannot modify managed objects outside of a write transaction - React Native, Realm

onPressFavourites(item) {
let realm = Realm.open({
path: RNFS.DocumentDirectoryPath +'/trees.realm',
schema: [sightingSchema, treeSchema],
schemaVersion: 7,
}).then(realm => {
if(item.favourites === 0) {
realm.write(() => {
item.favourites = 1;
item.favouritesColour = '#91b54d';
});
} else {
realm.write(() => {
item.favourites = 0;
item.favouritesColour = 'transparent';
});
}
});
alert(item.favourites)// Update a property value
}
I am trying to update an object in the Realm when a button is clicked however I get the error
"Possible Unhandled Promise Rejection (id: 0):
Error: Cannot modify managed objects outside of a write transaction."
This code was working a couple days ago but is now throwing the above error.
I am still learning React Native and Realm but from my understanding and following examples and the Realm docs, I am using the correct code so it should work.
EDIT
Seems the realm and write transactions were fine.
We were able to find a roundabout way to fix it however now the updates don't display until the app is refreshed.
It seems the error Possible Unhandled Promise Rejection (id: 0):
Error: Cannot modify managed objects outside of a write transaction. was produced when using the argument item to define the chosen Realm Object. However, if we create a variable related to the item, it doesn't produce that error. Any ideas why this would happen?
onPressFavourites(item) {
//console.log(realm);
let realm = Realm.open({
path: RNFS.DocumentDirectoryPath +'/trees.realm',
schema: [sightingSchema, treeSchema],
schemaVersion: 7,
}).then(realm => {
let trees = realm.objects('TreeInfo');
for(var i=0; i<trees.length;i++){
if(trees[i].commonName == item.commonName){
var chosen = trees[i];
break;
}
}
console.log(chosen);
if(item.favourites === 0) {
realm.write(() => {
//item.favourites = 1;
//item.favouritesColour = '#91b54d';
chosen.favourites = 1;
chosen.favouritesColour = '#91b54d';
});
}
else {
realm.write(() => {
chosen.favourites = 0;
chosen.favouritesColour = 'transparent';
});
}
});
alert(item.favourites)// Update a property value
}
EDIT
render() {
var treeList = this.state.trees;
console.log(treeList);
const {navigate} = this.props.navigation;
//var tree = treeList[0];
return(
<Container>
<Header searchBar style={styles.searchBar}>
<Item style={styles.searchBarInner}>
<Icon name="ios-search" />
<Input placeholder="Search" />
</Item>
</Header>
<List dataArray={treeList}
renderRow={(item) =>
<ListItem style={styles.ListItem} button={true}>
<ListButton item={item}
onSelect={(item) => this.saveTreeInfo(item)}
onSelectFavourites={(item) => this.onPressFavourites(item)
}
/>
</ListItem>
}
>
</List>
</Container>
);
}
Above is where item is being passed to the onPressFavourites function. item is being generated from an array of Realm Objects treelist and displayed in a list.
The treelist array comes from the variable this.state.trees which is displayed below.
filterContent(){
Realm.open({
path: RNFS.DocumentDirectoryPath +'/trees.realm',
schema: [sightingSchema, treeSchema],
schemaVersion: 7,
}).then(realm => {
let trees = realm.objects('TreeInfo');
let length = trees.length;
let treeRealm = trees.sorted('commonName');
console.log(this.state.leafEdges);
if (this.state.leafEdges === 'smooth') {
var smoothLeaf = treeRealm.filtered('leafEdges CONTAINS "Smooth"');
this.setState({trees:smoothLeaf});
}
if (this.state.leafEdges === 'toothed') {
var toothedLeaf = treeRealm.filtered('leafEdges CONTAINS "Toothed"');
this.setState({trees:toothedLeaf});
}
if (this.state.leafEdges === 'notsure') {
this.setState({trees:treeRealm});
}
else if (this.state.leafEdges === 'null') {
this.setState({trees:treeRealm});
}
});
}
Answer in English:
The trees has something about realm, so you cannot change it. Just copy a new Array like this:
let trees = realm.objects('TreeInfo');
var arr=[];
for (let i = 0;trees && i < trees .length; i++) {
var item=trees[i];
var item2={
id: item.id,
....
}
arr.push(item2);
}
You should improve the syntax
let order = {...bla, bla,bla}
realm.write(() => {
realm.create("order", order);
});
这个trees 里面关联了realm里的一些东西,所以不能改变,要重新复制一个数组,像这样
let trees = realm.objects('TreeInfo');
var arr=[];
for (let i = 0;trees && i < trees .length; i++) {
var item=trees[i];
var item2={
id: item.id,
....
}
arr.push(item2);
}