useEffect enters infinite loop when using React in Hooks - reactjs

I'm paging with react. When doing this, I transfer the state in a parent component here. I transfer the information I transfer to a different state with a new array, but when I do it with useEffect, it enters an infinite loop. I couldn't set a dene state.
const Pagination = ({ posts }) => {
const gecici = posts
const sabit = 5;
const [dene, setDene] = useState([])
const [degisken, setDegisken] = useState(0)
useEffect(() => {
degistir()
console.log(dene)
}, [dene])
const degistir =() => {
var i = 1,
j;
if (sabit <= gecici.length) {
for (j = 0; j < (gecici.length / sabit); j++) {
setDene([
{
id: i, include:
gecici.slice(degisken, degisken + sabit)
}
]);
i += 1;
setDegisken(degisken+sabit)
}
}
}
Since I'm paging, I'm adding content up to a 'sabit' value on each page. The value i indicates the page number here.
The value of j is to fill the array up to the total number of pages.

The way the useEffect dependency array works is by checking for strict (===) equivalency between all of the items in the array from the previous render and the new render. Therefore, putting an array into your useEffect dependency array is extremely hairy because array comparison with === checks equivalency by reference not by contents.
const foo = [1, 2, 3];
const bar = foo;
foo === bar; // true
const foo = [1, 2, 3];
const bar = [1, 2, 3];
foo === bar; // false
So, since you have this:
useEffect(() => {
degistir()
console.log(dene)
}, [dene])
Your component enters an infinite loop. degistir() updates the value of dene to a new array. But even if the contents are the same, the reference of dene has changed, and the useEffect callback fires again, ad infinitum.
The solution depends a bit on what exact behavior you want to have happen. If you want the useEffect callback to trigger every time the size of the array changes, then simply use the length of the array in the dependency array, instead of the array itself:
useEffect(() => {
degistir()
console.log(dene)
}, [dene.length])
A less ideal solution is to convert your array into a string and compare the old stringified array to the new stringified array:
useEffect(() => {
degistir()
console.log(dene)
}, [JSON.stringify(dene)])
This is extremely inefficient but might work if the number of items in the array is small.
The best option is probably to get rid of the useEffect entirely. It is unlikely that you actually need it. Instead, you can probably accomplish your pagination as a pure result of: 1) The total number of posts, and 2) the current page. Then just render the correct posts for the current page, and have a mechanism for updating the current page value.
Perhaps something like this? The naming of your variables is confusing to me since I speak English only:
const Pagination = ({ posts }) => {
const gecici = posts
const sabit = 5;
const [degisken, setDegisken] = useState(0)
let dene = [];
var i = 1,
j;
if (sabit <= gecici.length) {
for (j = 0; j < (gecici.length / sabit); j++) {
dene = [
{
id: i, include:
gecici.slice(degisken, degisken + sabit)
}
];
i += 1;
}
}

Related

Proper on implementing incremental values

Now I know the title may be a bit vague so let me help you by explaining my current situation:
I have an array worth of 100 object, which in turn contain a number between 0 and 1. I want to loop through the array and calculate the total amount e.g (1 + 1 = 2).
Currently using .map to go through every object and calaculate the total. When I am counting up using the useState hook, it kinda works. My other approach was using a Let variabele and counting up like this. Although this is way to heavy for the browser.
I want to render the number in between the counts.
const[acousticness, setAcousticness] = useState(0);
let ids = [];
ids.length == 0 && tracks.items.map((track) => {
ids.push(track.track.id);
});
getAudioFeatures(ids).then((results) => {
results.map((item) => {
setAcousticness(acousticness + item.acousticness)
})
})
return (
<div>
Mood variabele: {acousticness}
</div>
)
What is the proper way on doing this?
I think this is roughly what you are after:
import {useMemo, useEffect, useState} from 'react';
const MiscComponent = ({ tracks }) => {
// Create state variable / setter to store acousticness
const [acousticness, setAcousticness] = useState(0);
// Get list of ids from tracks, use `useMemo` so that it does not recalculate the
// set of ids on every render and instead only updates when `tracks` reference
// changes.
const ids = useMemo(() => {
// map to list of ids or empty array if `tracks` or `tracks.items` is undefined
// or null.
return tracks?.items?.map(x => x.track.id) ?? [];
}, [tracks]);
// load audio features within a `useEffect` to ensure data is only retrieved when
// the reference of `ids` is changed (and not on every render).
useEffect(() => {
// create function to use async/await instead of promise syntax (preference)
const loadData = async () => {
// get data from async function (api call, etc).
const result = await getAudioFeatures(ids);
// calculate sum of acousticness and assign to state variable.
setAcousticness(result?.reduce((a, b) => a + (b?.acousticness ?? 0), 0) ?? 0)
};
// run async function.
loadData();
}, [ids, setAcousticness])
// render view.
return (
<div>
Mood variabele: {acousticness}
</div>
)
}

UseEffect doesn't execute itself before rest of the code. Normal or not?

currently I am working on a project and find myself in a bothersome situation. Is it normal for the components to load before useEffect?
On my page I want to implement pagination on sidebar. I have state which will determine current page and that state which is number will be an index which will take nested array and show the content.
However data is an array without nested arrays and firstly I should convert that array into array with nested ones. Because that, I want to run that inside side effect because it should only be done at initial loading.
Now I am trying with hardcoded values and later will set dynamic ones.
The problem now is that useEffect doesn't run first and the rest of code actually executes itself before useEffect and I got errors like "MenuList.js:173 Uncaught TypeError: DUMMY_FOOD[0].map is not a function" and ect. I know that my array is not in right format hence if I log without this [0] it works.
What is problem?
Code:
const MenuList = () => {
const navigate = useNavigate();
const location = useLocation();
const params = useParams();
const [page, setPate] = useState(0);
useEffect(() => {
const pages = Math.ceil(DUMMY_FOOD.length / 5);
const arr = [];
let helpArr = [];
let c = 0;
for (let i = 0; i < pages; i++) {
for (let j = c; j < c + 5; j++) {
console.log("picapicapiac");
helpArr.push(DUMMY_FOOD[j]);
}
c += 5;
arr.push(helpArr);
helpArr = [];
}
console.log(arr);
DUMMY_FOOD = arr;
}, []);
console.log(DUMMY_FOOD);
const queryPrams = new URLSearchParams(location.search);
const sort = queryPrams.get("sort");
const onNextPageHandler = () => {};
const onPreviousPageHandler = () => {};
const onSortPageHandler = () => {
navigate(`/menu/${params.foodId}/?sort=${sort === "asc" ? "desc" : "asc"}`);
sort === "asc"
? (DUMMY_FOOD = DUMMY_FOOD.sort((a, b) => a.foodPrice - b.foodPrice))
: (DUMMY_FOOD = DUMMY_FOOD.sort((a, b) => b.foodPrice - a.foodPrice));
};
return (
<Fragment>
<div className={classes["menu-list"]}>
{DUMMY_FOOD.map((foodObj) => (
<MenuItem key={foodObj.id} foodObj={foodObj} />
))}
</div>
<div className={classes["menu-list__buttons"]}>
<Button type="button" onClick={onPreviousPageHandler}>
Page 2
</Button>
<Button type="button" onClick={onSortPageHandler}>
{sort === "asc" ? `Descending &#8593` : `Ascending &#8595`}
</Button>
<Button type="button" onClick={onNextPageHandler}>
Page 3
</Button>
</div>
</Fragment>
);
};
export default MenuList;
This is how useEffect works. It is simply explained in the react docs:
What does useEffect do? By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we’ll refer to it as our “effect”), and call it later after performing the DOM updates. In this effect, we set the document title, but we could also perform data fetching or call some other imperative API.
This is expected behavior, useEffect is not a synchronous function.
What you want to do is make sure your arrays have items in them and are not null/undefined, to make sure renders don't break.
From React's docs:
Unlike componentDidMount or componentDidUpdate, effects scheduled with useEffect don’t block the browser from updating the screen. This makes your app feel more responsive. The majority of effects don’t need to happen synchronously.
Using your Dummy_food variable below.. not sure where it's coming from but I kept it in the code.
useEffect(() => {
const pages = Math.ceil(DUMMY_FOOD.length / 5);
const arr = [];
let helpArr = [];
let c = 0;
for (let i = 0; i < pages; i++) {
for (let j = c; j < c + 5; j++) {
console.log("picapicapiac");
helpArr.push(DUMMY_FOOD[j]);
}
c += 5;
arr.push(helpArr);
helpArr = [];
}
console.log(arr);
DUMMY_FOOD = arr;
}, []);
Have a portion that displays your links. This will not error out if there are no links at render.
const RenderPagination= () => {
return DUMMY_FOOD.map((f, index) => {
return (
<li>{f}</li>
);
});
};
Then where your displaying your items...
{RenderPagination()}

React state taking previous initialized values when updating

I am having an issue with react.
I have an array of states of a task whether it is pending or completed or running.
I have an used an array in use state like this
const [messageState, setMessageState] = useState<string[]>(new Array(6).fill('Pending...'));
All that has been initialized to pending first.
And then the messages come from Kafka and according to that, I am updating the state.
useEffect(() => {
if(message.info && message.info.log.indexOf('fromScript') ! == -1) {
const {info} = message
const {err} = info
for(let i=0; i < 6; i++){
const array = setTableContent(i, err)
console.log(array);
setMessageState(array)
}
}
}
)
The function for setTableContent is
const setTableContent = (row: number, err: any) => {
const value = getStatus(row, err);
const newArray = [...messageState]
newArray[row] = value;
return newArray;
}
where getStatus is a function which fetches the status for all the tasks on every kafka message.
Now the problem is that every time even if I am updating the state of messageType in useEffect snippet, I am still getting the same initialized values on the 3rd line of setTableContent function. Why is this happening I am not able to understand.
Please help. Thank you in advance.
I am still not sure why I can't update an array state in a loop but I have found a turn around for this.
Rather than putting
setMessageState() in useState, i have put the for loop in the function setTableContent() itself.
const setTableContent = (row: number, err: any) => {
const values = []
for(let i=0; i < 6; i++) {
values.push(getStatus(i, err));
}
setMessageType(values);
}
And now its working

setState not updating state after useEffect

I'm making a shopping cart app and I have a variable that loops through all prices and gets a total price of the cart. I am trying to have that total amount update the totals state through setTotals but while the total variable itself updates as items are removed from the cart, calling setTotals(total) does not update the state. It stays with the initial state on render (total price of all prices in cart).
my state declaration:
const [totals, setTotals] = useState()
getting the total from prices array:
var total = 0
for (var i = 0; i < prices.length; i++) {
total += prices[i];
}
calling setTotals in useEffect:
useEffect(() => {
setTotals(total) <-- From the loop above
setCartCopy([...deepCartCopy])
}, [totals])
Can anyone please help?
I think your approach of a card code is not the good one.
Here you always have the same information twice: you're getting the total in total and then you're setting totals with that same result. You can simplify by keeping only one of the two variables.
Also your code here is not working because the useEffect will never be executed: As you have put totals as a dependency, but it never changes. Even if you change totals somwhere else, you will have an infinite loop because in the useEffect depending on totals's changes, it change it value, which will execute the useEffect that changes totals etc etc, infinite loop.
I think your loop should also be in a useEffect with no dependencies as it will be executed only at the first render.
I would do soemthing like that, and maybe the code can be move improved:
const [prices, setPrices] = useState([1, 3, 6, 39, 5]);
const [total, setTotal] = useState(0);
useEffect(() => {
// Sets total with already present products in the card
// This useEffect is called only at the first render
prices.forEach((price) => {
setTotal((total) => total + price);
});
}, []);
useEffect(() => {
setTotal((total) => total + prices[prices.length - 1]);
// Called when prices values changes, like if you're adding a product to your card
// It keeps your total updated
}, [prices]);
const addPrice = () => {
// Simulate a new product in your card
setPrices((prices) => [...prices, Math.floor(Math.random() * 10)]);
};
return (
<div className="App">
<p>total: {total}</p>
<button onClick={() => addPrice()}>add price</button>
</div>
);
I hope I'm clear in my answer lol
const [finalTotal, setFinalTotal] = useState(0);
let total = 0;
for (let i = 0; i < prices.length; i++) {
total += prices[i];
}
useEffect(() => {
setFinalTotal(total);
}, [total, setFinalTotal]);
https://codesandbox.io/s/stackoverflowhelp-i70fd?file=/src/App.js

Rendering every iteration of an array sort in React

I'm trying to build a simple app in react that takes an unsorted array (this.state.dataset) then insertion sorts it and renders each iteration of the sorting process.
Because React is asynchronous placing a setstate after each array change does not work. Any ideas how to get around this?
I was able to do this with bubble sort by simply exiting the function after each array change then rendering it, then restarting the bubble sort with the updated array. However I can't get that approach to work here. Very inefficient I know, sorry I am new to this..
render() {
return (
<div className="buttons">
<button onClick={this.sort}>Insertion Sort</button>
</div>
);
}
sort(e) {
this.myInterval = setInterval(() => {
let arr = this.insertionSort();
this.setState({
dataset: arr,
});
}
}, 1000);
insertionSort(e) {
let inputArr = this.state.dataset
let length = inputArr.length;
for (let i = 1; i < length; i++) {
let key = inputArr[i];
let j = i - 1;
while (j >= 0 && inputArr[j] > key) {
inputArr[j + 1] = inputArr[j];
j = j - 1;
return inputArr //Added by me to stop loop and render (probably wrong solution)
}
inputArr[j + 1] = key;
return inputArr //Added by me to stop loop and render (probably wrong solution)
}
return inputArr;
};
(I have the this.state.dataset rendering in my render() method but excluded for brevity's sake
One possible solution may be to store the values required for iterating the array in the state. On each modification, you can update the state and return from the sorting function.
Because React is asynchronous placing a setState after each array change does not work. Any ideas how to get around this?
The setState function takes a second callback parameter that is called when the state updates. This means you can effectively treat state updates as synchronous.
Here is working implementation using this method:
(This is written in React Native but the logic is the same for regular React)
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
i: 1,
j: 0,
data: [...initialData],
};
}
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.data}
renderItem={({ item }) => <Text>{item}</Text>}
/>
<Button title="Sort" onPress={this.sort} />
<Button title="Reset" onPress={this.reset} />
</View>
);
}
sort = () => {
const { i, j, key, data: oldData } = this.state;
const nextSort = () => setTimeout(this.sort, 500);
if (i == oldData.length) {
return;
}
const data = [...oldData];
const value = key ? key : data[i];
if (j >= 0 && data[j] > value) {
data[j + 1] = data[j];
this.setState({ j: j - 1, data, key: value }, nextSort);
return;
}
data[j + 1] = value;
this.setState({ i: i + 1, j: i, data, key: undefined }, nextSort);
};
reset = () => this.setState({ data: [...initialData], i: 1, j: 0, key: undefined });
}
One final thing worth mentioning in your existing implementation is the mutation of state. When you have an array in your state, you cannot modify the array as this will cause issues when you call setState.
For example, your variable dataSet is stored in the state. You then set inputArr to a reference of the state value dataSet with:
let inputArr = this.state.dataset
When you now call inputArr[j + 1] = inputArr[j]; the original array (in your state) is updated. If you call setState with inputArr React will compare it against dataSet. As you have modified dataSet the values will match inputArr which means the state won't update.
You can see a workaround for this issue in my solution where I copy the dataSet into a new array with:
const data = [...oldData]
This will prevent the array in your state from updating.

Resources