UI not rendering while useEffect fires - react-native

I'm trying to get dynamic next page with Object.entries[currentTaskIndex], everything works great and i'm getting the task data based on their indexes , but useEffects not rendering UI when dependency changes, actually it's rendering same task as before ,so this mean when useState fire, currentTaskIndex state changes to 2 but it's stillshowing index 1 items, but when i refresh the page it's showing index 2 items,so the question is that, how can i show index 2 items while useEffects fire?
This is what i have:
const [isCorrect, setIsCorrect] = useState(false);
const [currentTaskIndex, setCurrentTaskIndex] = useState(0);
useEffect(() => {
const checkIfCorrect = newData.map((data) => {
....
});
}, [!isCorrect ? newItems : !newItems]);
// Filtered task
useEffect(() => {
const filterObject = newData.filter((data) => {
return data.map((dataItems) => {
return dataItems.map((tasks, i) => {
return Object.entries(tasks)[currentTaskIndex].map((task, i) => {
setFilteredTasks(task);
});
});
});
});
}, [newData]);
useEffect(() => {
isCorrect && setCurrentTaskIndex((prev) => prev + 1);
setNewItems([]);
setIsCorrect(false);
const filterObject = newData.filter((data) => {
return data.map((dataItems) => {
return dataItems.map((tasks, i) => {
return Object.entries(tasks)[currentTaskIndex].map((task, i) => {
setFilteredTasks((currTask) => [...currTask, task]);
});
});
});
});
}, [isCorrect]);
{filteredTasks.map((taskItems, i) => (
<View key={i}>
{taskItems.en?.map((enItems, i) => (
.....
))}
</View>

React components automatically re-render whenever there is a change in their state or props. A simple update of the state, from anywhere in the code, causes all the User Interface (UI) elements to be re-rendered automatically.
So, by adding just currentTaskIndex on dependency array your useEffect also re-render.
when you just put newData on dependency array its could not update UI because newData is not a state or Props.
Yes this is your solution:
// Filtered task
useEffect(() => {
const filterObject = newData.filter((data) => {
return data.map((dataItems) => {
return dataItems.map((tasks, i) => {
return Object.entries(tasks)[currentTaskIndex].map((task, i) => {
setFilteredTasks(task);
});
});
});
});
}, [newData, currentTaskIndex]); <------

An finally fixed!
by just adding CurrentTaskIndex as dependency to Filtered task useEffects
// Filtered task
useEffect(() => {
const filterObject = newData.filter((data) => {
return data.map((dataItems) => {
return dataItems.map((tasks, i) => {
return Object.entries(tasks)[currentTaskIndex].map((task, i) => {
setFilteredTasks(task);
});
});
});
});
}, [newData, currentTaskIndex]); <------

Related

React Native useState Render Error : [Too many re-renders.]

This is my code:
export default function App() {
const [onProcess, setOnProcess] = useState("normal")
var myid = "123"
const [data, setData] = useState([]);
useEffect(() => {
fetch('https://api.npoint.io/0294bea2185268c9ac70')
.then((response) => response.json())
.then((json) => setData(json))
.catch((error) => console.log('ERR :', error))
},[]);
for (let x in data) {
if (data[x].client_id == myid) {
var set = data[x].situation
setOnProcess(set)
console.log(data[x].situation)
break
}
}
const rt_normal = (
<View style={styles.container}>
<Text> This is normal view </Text>
</View>
)
const rt_process = (
<View style={styles.container}>
<Text> This is process view </Text>
</View>
)
if (onProcess == "normal") {
return rt_normal
}
else if (onProcess == "_on_process") {
return rt_process
}
}
The error I got is:
:[Render Error. Too many re-renders. React limits the number of renders to prevent an infinite loop.]
This happens because of setOnProcess(set) code. How can I solve this?
You should remove your for...in loop and refactor to utilise useEffect.
useEffect(() => {
// Get a specific entry where client_id matches myId.
const filteredItem = data.find(item => item.client_id === myId);
// Perform a check as .find() can return undefined.
if(filteredItem.situation) {
setSituation(filteredItem.situation);
}
}, [data]);
Put the for loop inside a useEffect
(Untested) example:
useEffect(() => {
for (let x in data) {
if (data[x].client_id == myid) {
var set = data[x].situation;
setOnProcess(set);
console.log(data[x].situation);
break;
}
}
}, [data]);

remove duplicate error from JSON value React Native

I am trying to pass filtered value from JSON to the parent component, however I've tried using Set but seems the output is still the same. The component that I'm using to render the JSON is picker from native-base. I want to filter out the repeated value in my picker. Greatly appreciated if anyone can help me.
enter image description here
Here's my code.
Picker.js
const DefaultPicker = ({labelItem, pickerWidth, onHandleValue, ...rest}) => {
const context = useContext(WindowContext);
const [selectedValue, setSelectedValue] = useState('-Select-');
const [data, setData] = useState([]);
const {user, setUser} = useContext(AuthContext);
function onNewData() {
if (user) {
user.getIdToken().then((idToken) => {
Axios.get('URL_ENDPOINT', {
headers: {
Authorization: 'Bearer' + idToken,
},
})
.then(({data}) => {
setData(data.features);
// console.log(data.features);
})
.catch((error) => {
console.error(error);
});
});
}
}
useEffect(() => {
const form = onNewData(onNewData);
return form;
}, []);
return (
<PickerWrapper>
<PickerItem
width={pickerWidth}
height="60"
mode="dropdown"
selectedValue={selectedValue}
onValueChange={(itemValue, itemIndex) => {
setSelectedValue({itemValue});
}}>
{Array.from(
new Set(
data.map((value, index) => (
<PickerItem.Item
key={index}
label={value.properties[labelItem]}
value={value.properties[labelItem]}
{...rest}
/>
)),
),
)}
</PickerItem>
</PickerWrapper>
);
};
And here is my parent component
SiteData.js
const SiteData = () => {
const [values, setValues] = useState([]);
const onHandleValue = (params) => {
setValues(params);
console.log(params);
};
return (
<ScrollableView>
<DetailContainer>
<DetailWrapper>
<DetailTitle>Site Data</DetailTitle>
<DetailSubtitle marginTop="10">
Insert new data found during your audit or observation session
</DetailSubtitle>
<DetailSubcontainer>
<DefaultPicker
labelItem={'category'} <-- receive value from child
pickerWidth="100%"
onHandleValue={onHandleValue}
/>
</DetailSubcontainer>
</DetailWrapper>
</DetailContainer>
</ScrollableView>
);
};
UPDATE 1:
I'm using the filter() method so i can create a new array but it returns only one value in the picker list.
const indexData = data.filter(
({category}, index) => {
return (
data.findIndex(
(item) =>
item.category === category,
) === index
);
},
);
The output
enter image description here
I fixed my code by adding this on child component
var setObj = new Set();
var result = data.reduce((acc,item)=>{
if(!setObj.has(item.category)){
setObj.add(item.category,item)
acc.push(item)
}
return acc;
},[]);

FlatList items re-rendering even with React.memo

I am trying to render a list of items in React Native with the FlatList component but every time I fetch new data it re-renders the who list of items even with React.memo.
Here is what my code looks like:
const data = [
{ _id: 1, text: 'Hello World' },
{ _id: 2, text: 'Hello' },
{ ... }
]
const renderItem = ({ item }) => (<Component item={item} />)
const loadMore = () => {
//Fetching data from db and adding to data array
}
<FlatList
data={data}
keyExtractor={item => item._id}
renderItem={renderItem}
onEndReached={loadMore}
removeClippedSubviews={true}
/>
Component.js
const Component = ({ item }) => {
console.log('I am rendering')
return (
<Text>{item.text}</Text>
)
}
const equal = (prev, next) => {
return prev.item.text === next.item.text
}
export default React.memo(Component, equal)
Every time the onEndReached function gets triggered and calls the loadMore function, all FlatList items get re-rendered, it console.log 'I am rendering' every single time and causes the error virtualizedlist you have a large list that is slow to update
Thanks to anyone who can help me!
I don't know why but I fixed it with an if statement in the equal function
//Changing this
const equal = (prev, next) => {
return prev.item.text === next.item.text
}
//To this
const equal = (prev, next) => {
if(prev.item.text !== next.item.text) {
return false;
}
return true
}
Hope this could help someone else.

React Admin Change field based on related record field

Let's say I have a record called 'assets' which has a column called deducitble. An asset can have one Insurer. The insurer has a boolean field 'allowOtherDeductible'.
When editing the asset, I want the ability to first check if the associated insurer has allowOtherDeductible set to true. If so I'll allow a TextInput for deductible, if false, a SelectInput.
How can I achieve this? I cannot see a way to fetch related record's fields when conditionally rendering fields.
I ended up pulling in all the insurers and loading the asset when the component loaded. Seems a bit inefficient, but I couldn't come up with a better way:
export const AssetEdit = props => {
const dataProvider = useDataProvider();
const [insurers, setInsurers] = useState([]);
const [asset, setAsset] = useState(null);
const [otherDeductible, setOtherDeductible] = useState(false);
useEffect(() => {
dataProvider.getOne('assets', { id: props.id })
.then(({ data }) => {
setAsset(data);
return dataProvider.getList('insurers', { pagination: { page: 1, perPage: 100 } });
})
.then(({ data }) => setInsurers(data))
.catch(error => {
console.log(error)
})
}, [])
useEffect(() => {
if (asset && insurers) {
const selectedInsurer = insurers.find(insurer => insurer.id === asset?.insurer_id);
setOtherDeductible(selectedInsurer?.other_deductible);
}
}, [insurers])
return (
<Edit {...props}>
<SimpleForm>
{otherDeductible &&
<NumberInput
source={'deductible'}
parse={val => dollarsToCents(val)}
format={val => centsToDollars(val)}
/>
}
<FormDataConsumer>
{({ formData, ...rest }) => {
if(!otherDeductible){
return <SelectInput
source="deductible"
parse={val => dollarsToCents(val)}
format={val => centsToDollars(val)}
choices={getDeductibleChoices(formData.insured_value)}
/>
}
}}
</FormDataConsumer>
</SimpleForm>
</Edit>
)
}
I'd write a custom Input taking advantage of the fact that SimpleForm injects the record to all its children:
const DeductibleInput = ({ record }) => {
if (!record) return null;
const { data, loaded } = useGetOne('insurers', record.insured_id);
if (!loaded) return null; // or a loader component
return data.otherDeductible
? <NumberInput
source="deductible"
parse={val => dollarsToCents(val)}
format={val => centsToDollars(val)}
/>
: <SelectInput
source="deductible"
parse={val => dollarsToCents(val)}
format={val => centsToDollars(val)}
choices={getDeductibleChoices(record.insured_value)}
/>
}

connect() does not re-render component

a component dispatches an action which modifies the Redux store and the other component should get the changed state to props and rerender.
The thing is, the component gets the props, and they are correct and modified, but the component is never rerendered.
Could someone help, been stuck too much..
Component who uses store:
on mount it does a http request,
and should rerender when the state is changed.
class CalendarView extends Component {
componentDidMount() {
axios.get('http://localhost:3000/api/bookings/get')
.then(foundBookings => {
this.props.getBookings(foundBookings);
})
.catch(e => console.log(e))
}
render() {
return (
<Agenda
items={this.props.items}
selected={this.props.today}
maxDate={this.props.lastDay}
onDayPress={this.props.setDay}
renderItem={this.renderItem}
renderEmptyDate={this.renderEmptyDate}
rowHasChanged={this.rowHasChanged}
/>
);
}
renderItem = (item) => {
return (
<View style={[styles.item, { height: item.height }]}>
<Text>Name: {item.name} {item.surname}</Text>
<Text>Time: {item.time}</Text>
</View>
);
}
renderEmptyDate = () => {
return (
<View style={styles.emptyDate}><Text>This is empty date!</Text></View>
);
}
rowHasChanged = (r1, r2) => {
console.log('hit')
return true;
}
}
const mapStateToProps = (state, ownProps) => {
return {
today: state.app.today,
lastDay: state.app.lastDay,
items: state.app.items
}
}
const mapDispatchToProps = (dispatch) => {
return {
setDay: date => dispatch(appActions.setSelectionDate(date.dateString)),
getBookings: data => dispatch(appActions.getBookings(data)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CalendarView);
Action Dispatching:
dispatches an action which modifies the state
onSubmit = (name, surname, selectionDate, selectionTime) => {
axios.post('http://localhost:3000/api/bookings/create', {
bookerName: name,
bookerSurname: surname,
bookerTime: selectionTime,
date: selectionDate
}).then(savedBookings => {
this.props.createBooking(savedBookings);
this.props.navigator.pop({
animationType: 'slide-down',
});
}).catch(e => console.log(e))
}
const mapStateToProps = state => {
//...
}
const mapDispatchToProps = (dispatch) => {
return {
createBooking: data => dispatch(appActions.createBooking(data))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(NewBookingScreen);
Reducer:
case types.CREATE_BOOKING: {
const { date , bookings } = action.savedBookings.data;
let dateArr = state.items;
// formatting a booking how needed
Object.keys(dateArr).forEach(key => {
if (key == date) {
dateArr[key] = [];
bookings.map(oneBooking => {
dateArr[key].push({
name: oneBooking.bookerName,
surname: oneBooking.bookerSurname,
time: oneBooking.bookerTime,
height: Math.max(50, Math.floor(Math.random() * 150))
});
})
}
});
return {
...state,
items: dateArr
};
}
full repo if needed: https://github.com/adtm/tom-airbnb/tree/feature/redux
Thank You in advance!
Your reducer is mutating the state, so connect thinks nothing has changed. In addition, your call to map() is wrong, because you're not using the result value.
Don't call push() on an array unless it's a copy. Also, please don't use any randomness in a reducer.
For more info, see Redux FAQ: React Redux ,Immutable Update Patterns, and Roll the Dice: Random Numbers in Redux .