React Hooks - Infinite loops when setting a state within a map - reactjs

For reference: I am working on a sliding puzzle game.
So I have a const Board function and I have defined 2 states named:
puzzlePieces
hiddenIndexNumber
The point of 'hiddenIndexNumber' is to keep track of the hidden block index within the game. So before the game starts, I loop through a new array that I create for puzzlePieces and use map to return HTML elements. When looping, I want to make sure that I get the hidden block index for my hiddenIndexNumber to keep track of the hidden block.
This is how my code (partially) looks:
const Board = () => {
const totalPieces = 9
const hiddenNumber = totalPieces
const[hiddenIndexNumber, setHiddenIndex] = useState(-1)
// here I create an array of 9 elements and shuffle them with underline
const [puzzlePieces, changePuzzlePieceContent] = useState(
_.shuffle( [ ...Array( totalPieces ).keys() ].map( num => num + 1 ) )
)
let puzzleElements = [ ...Array( totalPieces ).keys() ].map( index => {
// the problem here is that setHiddenIndex makes the framework rerender
// the DOM after setting the index number and I don't know how to solve the issue here
if( puzzlePieces[index] === hiddenNumber ) {
setHiddenIndex(index)
}
return <Puzzle
key = { index }
index = { index }
number = { puzzlePieces[index] }
hidden = { puzzlePieces[index] === hiddenNumber && true }
onChange = { handleChange }
/>
} )
}
The problem is with this code:
if( puzzlePieces[index] === hiddenNumber ) {
setHiddenIndex(index)
}
How do I make sure that I set hiddenIndexNumber without requesting for rerendering the DOM?

I would suggest you to look into shouldComponentUpdate() and useEffect() Hooks.
It's well described here: shouldComponentUpdate()? with an example.

Related

I am having trouble visualizing sorting algorithms

I am trying to visualize a sorting algorithm with react, my algorithm works fine but i want to link it to divs on the DOM that change their height as the sorting algorithms sorts a given array, i have only made it work at the last stage, for example divs on the DOM take their height from an unsorted array and when i click on the sort button, instead of dynamicly changing their height as the algorithm sorts the unsorted array, they just wait untill the algorithm finishes sorting and then in one instance the div go from unsorted height to sorted height (i have tried to do setTimeout but it never worked for me as it intended, it just does one timeout instead of doing it in every iteration).
This is my code:
function App() {
const [array, setArray] = useState();
const elements = useRef();
const test = (Array) => {
if(Array){
for(let i=0; i < Array.length; i++){
elements.current.children[i].style.height = `${Array[i]*2}vh`;
}
}
};
useEffect(()=> {
const startArray = [];
for(let i=0; i < 51; i++){
let randomNumber = Math.floor(Math.random() * 27);
if(randomNumber !== 0){
startArray.push(randomNumber)
}
}
setArray(startArray)
}, [])
const bubbleSort = arraY => {
let iteration = true;
let shouldContinue = false;
let count = arraY.length-1;
while(iteration){
shouldContinue = false
if(count > 0){
for(let i=0; i < count; i++){
shouldContinue = true;
if(arraY[i] > arraY[i+1]){
let smaller = arraY[i+1];
arraY[i+1] = arraY[i];
arraY[i] = smaller;
test(arraY);
}
}
}
count = count -1
if(!shouldContinue){
iteration = false
}
}
return array
}
const sort = ()=> {
bubbleSort(array);
}
return (
<div className="App">
<nav>
<h1 onClick={sort}>BUBBLE SORT</h1>
<h1 onClick={sort}>--- SORT</h1>
<h1 onClick={sort}>--- SORT</h1>
<h1 onClick={sort}>--- SORT</h1>
</nav>
<div className='container' ref={elements}>
{array && array.map(item=>{
return <div style={{height: `${item*2}vh`}}></div>
})
}
</div>
</div>
);
}
Any help is appreciated
You need to do one sort operation, and then set the state, so that react re-renders. You also will need to set some timeout, to avoid it all happening so quickly you can't see anything. Something like this:
const [currentlyAt, setCurrentlyAt] = useState(0);
const bubbleSort () => {
const newArray = [...]
// perform one step of bubble sort, storing my position in the sort
// with setCurrentlyAt, so I can resume it next time I'm called
setArray(newArray);
setTimeout(() => bubbleSort(), 500);
}
This allows one step (a single item swap in bubble array) to be performed, state updates so it'll rerender, and timeout gives time for the rerender to be seen by the user before resuming the bubble sort and doing the next swap.
Also, please, don't use arraY and Array as variable names to avoid shadowing array, use something like partiallySorted or at least arr. And you don't need to useRef at all, your logic where you map over array and have div heights based on array value works fine, you don't want to manually edit the style using a ref.

split a number by total number onChange input

My requirement is to split a to num in input so that it should not exceed total sum and for eg : if I enter 50 in any field other fields should have 25 each for 2 input values if there are 3 fields.
here is code sandbox for work on codesand box
Remove useEffect hook, the dependency for the hook is num and inside the callback of useEffect num is once again updated with setNum. This is a big no-no.
I suggest following changes to the handleChange callback
function handleChange(evt) {
const value = evt.target.value;
const inputElements = document
.getElementById("App")
.getElementsByTagName("input");
let numberOfInputElements = 1;
if (numberOfInputElements !== null) {
numberOfInputElements = inputElements.length - 1;
}
setNum((inputNum) => {
for (const key in inputNum) {
if (inputNum.hasOwnProperty(key)) {
inputNum[key] = value / numberOfInputElements;
}
}
return { ...inputNum, [evt.target.name]: value };
});
}
Link to codesandbox for complete code.
But I understand your requirement is slightly different from the problem statement.

How is useState() updating my data here? STRANGE

I have data from an movie-api I want to sort based on a select menu, either by year in descending order or title in alphabetical order.
Although Im only updating state in the sort function, not using the variable any where, the data thats already mapped out, and in a different array, updates accordingly. I guess its somehow related to the first change in state for each of the two differen variables?
Any idea how I should solve this correctly and why this is happening?
const sortData = (e) => {
if (e === "year"){
const yearData = data.sort(function(a, b) {
const yearA = a.Year;
const yearB = b.Year;
if (yearA < yearB) {
return -1;
}
if (yearA > yearB) {
return 1;
}
return 0;
});
setYearData(yearData);
}
if (e === "title") {
const titleData = data.sort(function(a, b) {
const titleA = a.Title.toUpperCase();
const titleB = b.Title.toUpperCase();
if (titleA < titleB) {
return -1;
}
if (titleA > titleB) {
return 1;
}
return 0;
});
setTitleData(titleData);
}
}
The sort() method sorts the elements of an array in place, so the data(state) changed without using setState (It may cause some unpredictability happened in the execution)
You can use the sort() method on a copy of the array, so it doesn't affect your state array, I guess this change may work:
use [...data].sort(...) instead of data.sort(...)
Array.sort(), in your case data.sort() updates the original array in addition to returning it. Seems like your data variable is some sort of global that gets changed during sort().

React not re-rendering when state changed from hook

Edit: Thanks for the help everyone. I needed to change the reference of the array and fixed it by doing:
setData([...sorted])
I am currently rendering out a list of tasks. This is a snippet of my return function within a functional component:
const [ data, setData ] = useState( mockData )
<tbody>
{ data.map(d => <TaskItem key={d.claimable} task={d}/>) }
</tbody>
When I click on a certain button on the page, the dataset gets sorted and I call setData(sortedData)
For some reason, the table isnt being re-rendered with the sorted data. Is there something I did wrong here?
This is the sort function:
function filterByContactAmount():void {
let sorted = data.sort((a:any, b:any) => {
let aTimesContacted:number = a.data.person.contact.list.reduce((acc:number, val:any):number => acc + val.history.length, 0)
let bTimesContacted:number = b.data.person.contact.list.reduce((acc:number, val:any):number => acc + val.history.length, 0)
if ( aTimesContacted > bTimesContacted ) {
return 1
}
if ( bTimesContacted > aTimesContacted ) {
return -1
}
return 0;
})
console.log(sorted)
setData(sorted)
}
Its because you are using the same ref of the array, you need set the new data with
setData(old => "sorted data");
to change the reference of the state and it updates
function filterByContactAmount():void {
let sorted = data.sort((a:any, b:any) => {
let aTimesContacted:number = a.data.person.contact.list.reduce((acc:number, val:any):number => acc + val.history.length, 0)
let bTimesContacted:number = b.data.person.contact.list.reduce((acc:number, val:any):number => acc + val.history.length, 0)
if ( aTimesContacted > bTimesContacted ) {
return 1
}
if ( bTimesContacted > aTimesContacted ) {
return -1
}
return 0;
})
console.log(sorted)
setData(old => [...sorted]) // Sorted is the new state sorted
}
You are mutating sate, the other answer is probably not the best because you are still mutating state and then setting state with a copy of the already mutated value.
The sort function can also be optimized. Maybe try the following:
function filterByContactAmount() {
let sorted = data
.map(d => ({//map shallow copies the array
...d,//shallow copies the item
sortedNum: d.data.person.contact.list.reduce(//do this once for every item, not for every time sort callback is called
(acc, val) => acc + val.history.length,
0
),
}))
.sort((a, b) => a.sortedNum - b.sortedNum);
console.log(sorted);
setData(sorted);
}
I think issue is located under d.claimable, I suppose it is boolean variable type. You must know that every key prop must be unique. Check if you have for example.id property, if not add it.
Uniqueness of key prop is very important during reconciliation process.
Unique identifier with a group of children to help React figure out which items have changed, have been added or removed from the list. It’s related to the “lists and keys” functionality of React described here.
Very nice article about reconciliation.
<tbody>
{ data.map(d => <TaskItem key={d.claimable} task={d}/>) }
</tbody>

convert a Array.filter callback to a recursive function

First of all sorry the messy code and this is the first time I'm posting a question!
So I'm using react-ui-tree. I need to implement a recursive function which goes deep into the tree and changes certain boolean values so that only one node(which is toggled) remains open/close while toggled the collapse button.
At the moment the callback function I'm passing to Array.filter is not checking recursively deep into the tree.(I just manually made it work)
If anyone could give me a solution on how to turn this handleNesting function to a recursive function.
toggleCollapse = nodeId => {
const tree = this.state.tree;
const index = tree.getIndex(nodeId);
const node = index.node;
node.collapsed = !node.collapsed;
const handleNesting = (parent) => {
if (parent.children && parent.children.find(inner => inner.ID ===
node.ID)) {
parent.collapsed = false
parent.children.filter(inner => {
if (inner.ID !== node.ID) inner.collapsed = true;
return inner;
});
}
if (parent.ID !== node.ID && parent.children && !parent.children.find(inner => inner.ID === node.ID)){
parent.collapsed = true
return parent;
}
return parent
}
tree.obj.children.filter(handleNesting) // collapses all the
//opened folder except the one which has been toggled(including its //parent folder)

Resources