I am having trouble visualizing sorting algorithms - reactjs

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.

Related

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 Hooks - Infinite loops when setting a state within a map

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.

React assign values to an array

I'm trying to handle some star rating according to some data from an api. The problem is that I have created an array for the stars and nothing is rendered. If I console .log inside the fetchMovieDetails I can see there is data. What am I missing? I even tried var stars = useState([]) but still same result
export default function MovieDetails({ match }) {
const [movie, setMovie] = useState({});
var stars = [];
useEffect(() => {
fetchMovieDetails();
}, []);
async function fetchMovieDetails() {
// get data from api
// Handle star rating
var rating = (response.rating/ 2).toFixed(1);
for (var i = 0; i < 5; i++) {
var star = {};
if (i < ~~rating) star = 'filled';
else if (rating % 1 === 0) star = 'half-filled';
stars.push(star);
}
setMovie(response.data);
}
return (
<div className="movie-container">
<div className="movie-grid">
<div className="movie-rating">
<div className="star-rating">
// nothing is rendered here
{stars.map((star) => (
<span className="{`icon-star ${star}`}">
<span className="path1"></span>
<span className="path2"></span>
</span>
))}
{(movie.rating/ 2).toFixed(1)} / 5
</div>
</div>
</div>
</div>
);
}
I guest var stars is always re-declare. Why don't you using the useState like:
const [stars, setStarts] = useState([]);
useEffect(() => {
fetchMovieDetails();
}, []);
async function fetchMovieDetails() {
// get data from api
// Handle star rating
var rating = (response.rating/ 2).toFixed(1);
var stars = [];
for (var i = 0; i < 5; i++) {
var star = {};
if (i < ~~rating) star = 'filled';
else if (rating % 1 === 0) star = 'half-filled';
stars.push(star);
}
setStarts(stars);
setMovie(response.data);
}
If you prefer to not to keep stars in the state, and if you need to move the star logic in to a separate section then useMemo will help with it.
async function fetchMovieDetails() {
// get data from api
.....
setMovie(response.data);
}
const stars = useMemo(() => {
if (!movie.rating) {
//if movie is not fetched then return an empty array
return [];
}
const rating = (movie.rating/ 2).toFixed(1);
const starsTemp = [];
for (let i = 0; i < 5; i++) {
var star = {};
if (i < ~~rating) star = 'filled';
else if (rating % 1 === 0) star = 'half-filled';
starsTemp.push(star);
}
return starsTemp;
}, [movie]); //when movie gets changed call this function
Why i prefer this is, lets say you can add a rating by clicking on a star, and then lets say you need to show the updated rating, with your approach you have to change two states, rating property of movie and the stars, so basically if a one state is depending on a another state, its better to do like this.

Iterating through HTMLcollection in React,js

I am having trouble iterating through and HTMLCollection in a React.js component. I have the following function:
shuffleLists = () => {
var elems = document.getElementsByTagName("ul");
console.log(elems)
for (let item of elems) {
console.log(item);
}
}
console.log(elems) prints out an HTMLCollection of ul elements as expected. But the for loop after it doesn't print anything in the console, when I would expect to see each ul element printed in the console. What am I doing wrong?
Edit for clarity:
The key issue here is that the line console.log(item) inside the loop does not output anything into the console in chrome dev tools, and the same applies to other various loop syntaxes as discussed in the answers and comments below.
I also have noticed that there is different lengths being logged from console.log(elems) between different browsers. In chrome I see HTMLCollection[] length: 10 ...
But in Firefox I see
HTMLCollection {length 0} ...
The item in your for loop is actually the key (array index). You need to get the particular element from the elems array by using the syntax elems[item].
Refactoring your code (and changing item to key, just for clarity):
shuffleLists = () => {
var elems = document.getElementsByTagName("ul");
console.log(elems);
for (let key of elems) {
console.log(elems[key]);
}
}
UPDATE: because result of getElementsByTagName() is a NodeList
shuffleLists = () => {
var elems = document.getElementsByTagName("ul");
console.log(elems);
elems.forEach(function(val) {
console.log(val);
})
}
From the MDN Web Docs
Although NodeList is not an Array, it is possible to iterate over it
with forEach(). It can also be converted to a real Array using
Array.from().
However, some older browsers have not implemented NodeList.forEach()
nor Array.from(). This can be circumvented by using
Array.prototype.forEach().
As per the updated requirement of the OP
shuffleLists = () => {
var ulElems = document.getElementsByTagName("ul");
console.log(ulElems);
for(i = 0; i < ulElems.length; i++) {
var liElems = ulElems[i].getElementsByTagName("li");
for(j = 0; j < liElems.length; j++) {
console.log(liElems[j].innerHTML);
}
}
}
document.getElementsByTagName will return an array of elements, in your case array of elements in the page, so you need to have something like the following if you want to have the first ul
shuffleLists = () => {
var elems = document.getElementsByTagName("ul");
console.log(elems)
for (let idx of elems) {
console.log(elems[idx]);
}
}
I would suggest to use document.getElementById() to be more precise about your selected elements
I figured out my problem was that I was calling the shuffleLists function from the parent component in React, prior to the ul and li elements I wanted to iterate through being rendered in the sub-component. The console output for the elems var was confusing as it showed all the lists and list items inside the HTMLCollection.
When I moved the shuffleLists function to the sub-component and called it inside componentDidMount I was able to loop through and console out all the list items as desired.

strange behavior in shuffle React

in my React app, I call shuffle 2 times for 2 different card sets but shuffle always gives 2 card sets the exact same result, can someone help me fix it?
class PairThemUp extends React.Component{
constructor(props){
super(props);
this.state={
cards1:[],
cards2:[],
}
}
shuffleCards=()=>{
const cards=this.props.selectedCards
const cards1=shuffle(cards)
const cards2=shuffle(cards)
this.setState({cards1, cards2})
const id1=cards1.map(c=>c.id)
const id2=cards2.map(c=>c.id)
console.log(id1, id2)
}
shuffle gives 2 card sets the same result until I run shuffleCards function again. this is my shuffle function
export const shuffle=(a)=> {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
cards, cards1, and cards2 are all pointing to the same array in your example as JavaScript passes array by reference.
The result is that every time you call shuffle, you are modifying and returning the underlying array that was passed to the function, and therefore any variable that was pointing to a result from previously calling shuffle will reflect the most recently shuffled array.
The fix is to create a copy of your array in shuffle so that cards, cards1, and cards2 all point to different arrays:
let shuffle = (a) => {
let newArr = [].concat(a); // create new array
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[newArr[i], newArr[j]] = [newArr[j], newArr[i]];
}
return newArr;
};

Resources