I'm making a react app where i have to update a list twice (at least or more). Obviously i have to use useState hook for being able to edit that list whenever i want. That's pretty easy, but the problem is that i don't really know but in my app, the two lists fusion themselves. Here's an example: i have a fruit list ["apples", "bananas", "avocados"] and a social network one ["instagram", "snapchat", "facebook", "whatsapp", "telegram"]. Now, when i change the state to the first list it becomes exactly: ["apples", "bananas", "avocados", "whatsapp", "telegram"]. And that's really weird. Instead of a set it makes a somelike difference(?). By the way, here's some simplified code of what i wrote (the essential):
// Initialize in the App function
const [colors, setColors] = useState([])
const [colorpicker,setColorPicker] = useState(null)
const [inputskin, setInputSkin] = useState(localStorage.getItem('skin') !== null ? localStorage.getItem('skin') : testskin)
const inputFile = useRef(null) // rename as inputSkin
useEffect(() => {
const allcolors = []
async function getPixels(){
console.log('changed input skin')
const image = await pixels(inputskin)
for(let i = 0; i < image.height; i++){
pixelLoop:
for(let j = 0; j < image.width; j++){
const color = []
for(let k = 0; k < 4; k++) {
color.push(image.data[(i * image.width + j) * 4 + k])
}
const hex = rgbToHex(color)
for (let clr of allcolors) if (clr[0] === hex) { clr[1]++; continue pixelLoop }
if (!(allcolors.includes(hex)) && color[3] != 0) allcolors.push([hex,0])
}
}
allcolors.sort((a,b) => b[1] - a[1])
setColors(allcolors)
console.log(allcolors)
}
getPixels()
changePalette = setColorPicker
}, [inputskin])
I really can't figure out what's the problem because when i console log the colors array, that's okay, it shows me the array that i want to have in the render, but in the render it basically does not refresh itself. I tried removing by DOM the children of the components nodes, but this does not really help. Here's how i render the components:
// render
return(
...
{ colors.map(([color], i) => <Color colorstart = { color } id = { i } key = { i } colorChange = { colorChange } />) }
...
)
Here's when i load the first color array:
And here's when i load the second color array:
Here's what i would expect to have when i load the second array (I obtain this only if i reload the page):
As you can see it eats the first two color rows :(
You are not setting key property in the map function which probably causes the problem. Set unique key property in
<Color colorstart = { color } id = { i } colorChange = { colorChange } />
for example
<Color key={color[0]} colorstart = { color } id = { i } colorChange = { colorChange } />
and check if it helps.
Read more here: https://reactjs.org/docs/lists-and-keys.html
Related
I am working on a project that visualizes various path algorithms.
The method I chose to update the visual effects were to use document.querySelector(node).classList and update the class to match CSS.
This is the start of the logic, which is triggered by click event.
const visualizeDijkstra = () => {
isVisualizing = true;
const startNode = grid[startNodePos.row][startNodePos.col];
const finishNode = grid[finNodePos.row][finNodePos.col];
const visited = initiateDijkstra(grid, startNode, finishNode);
const path = getPath(finishNode);
animateVisitedAndPath(visited, path);
};
The variables visited and path are arrays containing the result of the algorithm. Each item inside the array is an object containing the coordinates for the grid.
const animateVisitedAndPath = (visited: INode[], path: INode[]) => {
let totalTime = 0;
for (let i = 0; i < visited.length; i++) {
const { row, col } = visited[i];
animateAsVisited(row, col, i, speed, startNodePos, finNodePos);
if (i === visited.length - 1) {
animatePath(path, i, speed, startNodePos, finNodePos);
}
if (i === visited.length - 1) {
totalTime = (i + path.length) * speed;
setTimeout(() => {
isVisualizing = false;
}, totalTime);
}
}
};
Inside animatedVisitedAndPath function, I use a for loop and setTimeOut function to visualize the coordinates which are inside visited and path array.
export function animateAsVisited(
row: number,
col: number,
turn: number,
speed: number,
startNodePos: NodePos,
finishNodePos: NodePos
) {
setTimeout(() => {
const { row: sRow, col: sCol } = startNodePos;
const { row: fRow, col: fCol } = finishNodePos;
if (row === sRow && col === sCol) return;
if (row === fRow && col === fCol) return;
document.getElementById(`node-${row}-${col}`)?.classList.add('visited');
}, speed * turn);
}
And here, I use the document object to access each node, and update the classList.
Everything works fine, but when I want to stop the user to interact with the program using a state variable such as isVisualizing, the moment I update the state before, or after visualizeDijkstra, all of my setTimeOut call back functions invokes immediately, updating all visualizations at the same time when visualizeDijkstra gets invoked.
I apolgize if this is a duplicated question, or the way I ask the question is inappropriate.
I want to render some stars based on a specific value, and this is what I have done so far
const Rating = ({ value }) => {
const renderStars = () => {
let stars = []; // This array should contain all stars either full, half or empty star
for (let i = 0; i < value; i++) {
if (value % 1 !== 0) {
// If the value has decimal number
} else {
// If the value has NO decimal number
}
}
return stars?.map((star) => star); // Mapping the stars array
};
return <div className="rating">{renderStars()}</div>;
};
export default Rating;
Now I have 3 icons: a full star, a half star, and an empty star. Let's say the rating value is 3.5, so what I want is to push to the stars array 3 full stars 1 half star and 1 empty star so that will be 5 stars in total. And then I can map through the array and render all the stars.
You can loop through up until your value as you're currently doing, where for each iteration you push a full star, and then after the loop is complete, check if value is a decimal to determine if you should push an additional half star:
const STAR_COUNT = 5;
const Rating = ({ value }) => {
const stars = Array.from({length: STAR_COUNT}, () => <EmptyStar />);
let i;
for (i = 0; i < value; i++) { // this will loop Math.floor(value) times
stars[i] = <FullStar />;
}
if (value % 1 != 0) // if value is a decimal, add a half star
stars[i-1] = <HalfStar />;
return <div className="rating">{stars}</div>;
};
I would also suggest wrapping this component in a call to React.memo() so that the for loop logic only runs when your value prop changes, and not what the parent rerenders.
Another, perhaps more concise way, is to use some array methods to help, such as .fill() to populate an array firstly with empty stars, then replace those empty stars up to a given index based on your value, and finally add a half star if required:
const STAR_COUNT = 5;
const Rating = ({ value }) => {
const stars = Array(STAR_COUNT).fill(<EmptyStar />).fill(<FullStar />, 0, Math.floor(value));
if (value % 1 != 0) // if value is a decimal, add a half star
stars[Math.floor(value)] = <HalfStar />;
return <div className="rating">{stars}</div>;
};
Try this in your code
let rating = 3.5;
let ratingCount = 0
for(let i=1; i<=5; i++){
if(i<=rating){
console.log("full")
ratingCount++
}
}
if(rating-Math.floor(rating) !== 0){
console.log("Half")
ratingCount++
}
for(let i=ratingCount ; i<5; i++){
console.log("empty")
}
I have following code where I get 4 different attributes, I need to check each if its true and increment the count
var count = 0;
if (props.isApple) {
count += 1;
}
if (props.isOranges) {
count += 1;
}
if (props.isBanana) {
count += 1;
}
if (props.isJackfruit) {
count += 1;
}
console.log(count);
Is there any simple or nicer way of doing this? Any suggestions??
Looks like a job for reduce
const props = {
isApple: true,
isOranges: false,
isBanana: true,
isJackfruit: true
} // 3 true values
const checks = ["isApple", "isOranges", "isBanana", "isJackfruit"];
const count = checks.reduce((sum, check) => sum + (props[check] ? 1 : 0), 0);
console.log("count", count)
This iterates the list of checks and adds one to the sum if that property is truthy in props.
If you wanted it to be more dynamic and inclusive of all prop properties, you could define checks as...
const checks = Object.keys(props)
Since you've tagged this with reactjs, I'd use the memo hook to avoid unnecessary recalculation
import { useMemo } from "react";
const checks = ["isApple", "isOranges", "isBanana", "isJackfruit"];
const YourComponent = (props) => {
const count = useMemo(() =>
checks.reduce((sum, check) => sum + (props[check] ? 1 : 0), 0),
[ props ]
);
// etc
}
I am trying to create a merge sort visualizer.
The merge sort works in the sense that if I give it an array, it'll spit back a correctly sorted array. The problem is that the "visualization" aspect of my program isn't quite working as I thought it would.
The way I attempted the implementation was that mergeSort() would take the two elements being compared, highlight them in red (no longer focusing on this for now to simplify problem), then swap the height if necessary, then turn the two elements back to the default colour:
const {
useState,
useEffect
} = React;
const numberOfBars = 8; //Number of bars shown on screen
const animationSpeed = 100; //How fast the bars animate
function SortingVisualizer() {
const [arr, setArr] = useState([]);
useEffect(() => {
newArray();
}, [])
function newArray() {
const tempArr = [];
for (let i = 0; i < numberOfBars; i++) {
tempArr.push(Math.floor(Math.random() * 100) + 5)
}
setArr(tempArr);
}
return (
<div id = "main-container" >
<button id = "new-array-button" onClick = {() => newArray()}> New Array </button>
<button id = "merge-sort-button" onClick = {() => mergeSort(arr)}> Merge Sort < /button>
<div id = "bar-container" >
{
arr.map((value, index) => (
<div className = 'bar'
key = {
index
}
style = {
{
backgroundColor: "aquamarine",
height: value + "px"
}
}
/>
))
}
</div>
</div >
)
}
// Recursively breaks down unsortedArray into lowIndex, midIndex, highIndex and pass to mergeSortHelper()
// Initialized aux, lowIndex and highIndex in argument.
function mergeSort(unsortedArray, aux = [...unsortedArray], lowIndex = 0, highIndex = unsortedArray.length - 1) {
// Base case
if (highIndex === lowIndex) return;
// Get midIndex
const midIndex = Math.floor((highIndex + lowIndex) / 2);
// Recursively run left side and right side until base case reached.
mergeSort(unsortedArray, aux, lowIndex, midIndex);
mergeSort(unsortedArray, aux, midIndex + 1, highIndex);
// Merge the left sides and right sides
mergeSortHelper(unsortedArray, aux, lowIndex, midIndex, highIndex);
}
// Does the work of sorting list and animating the height swap
function mergeSortHelper(unsortedArray, aux, lowIndex, midIndex, highIndex) {
let auxkey = lowIndex;
let i = lowIndex;
let j = midIndex + 1;
let barI = undefined;
let barJ = undefined;
// While there are elements in left/right sides, put element in auxillary array
// then increment the indexes by 1.
while (i <= midIndex && j <= highIndex) {
let arrayBars = document.getElementsByClassName('bar');
barI = arrayBars[i].style;
barJ = arrayBars[j].style;
if (unsortedArray[i] <= unsortedArray[j]) {
aux[auxkey] = unsortedArray[i];
auxkey++;
i++;
} else {
let tmpHeight = barJ.height; //swaps value at index i and j
barJ.height = barI.height; //swaps value at index i and j
barI.height = tmpHeight; //swaps value at index i and j
aux[auxkey] = unsortedArray[j];
auxkey++;
j++;
}
}
// Move any remaining elements from unsortedArray into auxillary array.
while (i <= midIndex) {
aux[auxkey] = unsortedArray[i];
auxkey++;
i++;
}
// Copy the auxillary array to the unsortedArray so that in the next iteration, the unsortedArray will reflect the new array
for (let k = 0; k <= highIndex; k++) {
unsortedArray[k] = aux[k];
}
}
ReactDOM.render( < SortingVisualizer / > , document.querySelector("#app"))
#new-array-button {
position: absolute;
}
#merge-sort-button {
position: absolute;
left: 100px;
}
#bar-container {
align-items: flex-end;
background-color: lightgray;
display: flex;
height: 200px;
justify-content: center;
}
.bar {
margin: 0 2px;
width: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
I've been trying to implement the color change and height swap in different parts of my program but it never seems to work correctly. Where am I going wrong with the visualization?
Removed my jsfiddle link from the question as I am using the on-site code snippet now. But in case anyone still wants it, it's here: https://jsfiddle.net/SushiCode/k0954yep/17/
I am having an issue with my merge sort visualizer.
My program has no issues visualizing bubble sort or quick sort, as I can do the swapping operation of css property values in-place, but I am having major issues trying to get merge sort to work properly. The issue arises when I try to update a css property on the dom, it causes the sort to not function.
I have tried passing in copies of the data I wish to sort, and all sorts of weird things I could think of to make it work. I am currently trying to sort by the css property 'maxWidth'. I use that to display how large a div element is in the html file and then visualize the sort from there.
My latest thought has been to set all the div elements to have another css property equal to the maxWidth (I am using fontSize as it does not affect my program) and then sorting based on fontSize, allowing me in theory to change the maxWidth properties of the divs without affecting merge sorts algorithm.
I am including my entire js file as I hope reading my correctly working bubble sort or quick sort functions can help you see what I am trying to achieve. Thank you so much for taking the time to read this and offer any help!
Important Note: I am not trying to visualize the individual steps of merge sort yet because I am unable to update the final result to the html page without affecting the merge sort algorithm. According to console logs, my merge sort algorithm does indeed work, I just can't update the DOM without messing it up. Once I can do that, I will turn it into an asynchronous function using async and await like I previously did with bubble and quick sort.
/********* Generate and Store Divs to be Sorted *************/
const generateSortingDivs = (numOfDivs) => {
const divContainer = document.querySelector('.div-container');
let html = '';
for (let i = 0; i < numOfDivs; i++) {
let r = Math.floor(Math.random() * 100);
html += `<div class='sorting-div' id='id-${i}' style='max-width: ${r}%'> </div>`;
}
divContainer.innerHTML = html;
for(let i = 0; i < numOfDivs; i++) {
let x = document.getElementById('id-' + i);
x.style.fontSize = x.style.maxWidth;
}
}
const storeSortingDivs = () => {
const divContainer = document.querySelector('.div-container');
let divCollection = [];
const numOfDivs = divContainer.childElementCount;
for(let i=0; i<numOfDivs; i++) {
let div = document.getElementById('id-' + i);
divCollection.push(div);
}
return divCollection;
}
/********** SLEEP FUNCTION ************/
//Used to allow asynchronous visualizations of synchronous tasks
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/******* SWAP FUNCTIONS *********/
//Used for Testing Algorithm before Animating Visualization
const syncSwap = (div1, div2) => {
let tmp = div1.style.maxWidth;
div1.style.maxWidth = div2.style.maxWidth;
div2.style.maxWidth = tmp;
}
async function asyncSwap(div1, div2) {
await sleep(50);
let tmp = div1.style.maxWidth;
div1.style.maxWidth = div2.style.maxWidth;
div2.style.maxWidth = tmp;
}
const swapDivs = (smallerDiv, biggerDiv) => {
return new Promise(resolve => {
setTimeout(() => {
let tmp = smallerDiv.style.maxWidth;
smallerDiv.style.maxWidth = biggerDiv.style.maxWidth;
biggerDiv.style.maxWidth = tmp;
resolve();
}, 50);
});
}
/****************************************/
/*********** SORTING ALGO'S *************/
/****************************************/
/******* BUBBLE SORT ***********/
async function bubbleSort(divCollection) {
displayBubbleSortInfo();
const len = divCollection.length;
for(let i=0; i<len; i++) {
for(let j=0; j<len-i-1; j++) {
divCollection[j].style.backgroundColor = "#FF4949";
divCollection[j+1].style.backgroundColor = "#FF4949";
let numDiv1 = parseInt(divCollection[j].style.maxWidth);
let numDiv2 = parseInt(divCollection[j+1].style.maxWidth);
let div1 = divCollection[j];
let div2 = divCollection[j+1];
if(numDiv1 > numDiv2) {
await swapDivs(div2, div1);
}
divCollection[j].style.backgroundColor = "darkcyan";
divCollection[j+1].style.backgroundColor = "darkcyan";
}
divCollection[len - i - 1].style.backgroundColor = 'black';
}
}
function displayBubbleSortInfo(){
const infoDiv = document.querySelector('.algo-info');
let html = `<h1>Bubble Sort Visualizer</h1>`;
html += `<h2>Time Complexity: O(n^2)</h2>`;
html += `<h3>Space Complexity: O(1)</h3>`;
html += `<p>This sorting algorithm loops through the array and continues to push the
largest found element into the last position, also pushing the last available
position down by one on each iteration. It is guaranteed to run in exactly
O(n^2) time because it is a nested loop that runs completely through.</p>`;
infoDiv.innerHTML = html;
}
/****** QUICK SORT ********/
async function quickSort(divCollection, start, end) {
if(start >= end) return;
let partitionIndex = await partition(divCollection, start, end);
await Promise.all([quickSort(divCollection, start, partitionIndex - 1), quickSort(divCollection, partitionIndex + 1, end)]);
}
/* This function takes last element as pivot, places
the pivot element at its correct position in sorted
array, and places all smaller (smaller than pivot)
to left of pivot and all greater elements to right
of pivot */
async function partition(divCollection, start, end) {
let pivotIndex = start;
let pivotValue = parseInt(divCollection[end].style.maxWidth);
for(let i = start; i < end; i++) {
if(parseInt(divCollection[i].style.maxWidth) < pivotValue) {
await asyncSwap(divCollection[i], divCollection[pivotIndex]);
pivotIndex++;
}
}
await asyncSwap(divCollection[pivotIndex], divCollection[end]);
return pivotIndex;
}
function displayQuickSortInfo(){
const infoDiv = document.querySelector('.algo-info');
let html = `<h1>Quick Sort Visualizer</h1>`;
html += `<h2>Time Complexity: O(n log n)</h2>`;
html += `<h3>Space Complexity: O(log n)</h3>`;
html += `<p>This sorting algorithm uses the idea of a partition to sort
each iteration recursively. You can implement quick sort
in a variety of manners based on the method in which you
pick your "pivot" value to partition the array. In this
visualization, I implemented the method that chooses the
last element of the array as the pivot value. You could
also choose the first value, the middle value, or the median
value based on the first, middle, and last values.</p>`;
infoDiv.innerHTML = html;
}
/* Merge Sort does not sort in place, and thus we have to be
* clever when implementing it and also editing the css style
* of our divs to show the visualization of how the algorithm
* works. My method is to store a copy of the divs, that way
* I can use one to be sorted by merge sort, and the other to
* change the css style property to show the visualization.
* Unlike Quick Sort and Bubble Sort, we are not swapping
* elements when sorting, instead we are merging entire
* arrays together as the name implies. */
function mergeSort(divCollection) {
if(divCollection.length < 2) return divCollection;
let middleIndex = Math.floor(divCollection.length / 2);
let left = divCollection.slice(0, middleIndex);
let right = divCollection.slice(middleIndex);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
let mergedCollection = [];
while(left.length && right.length) {
if(parseInt(left[0].style.fontSize) < parseInt(right[0].style.fontSize || right.length === 0)) {
let el = left.shift();
mergedCollection.push(el);
} else {
let el = right.shift();
mergedCollection.push(el);
}
}
let res = mergedCollection.concat(left.slice().concat(right.slice()));
return res;
}
/***** INITIALIZATION FUNCTION *******/
generateSortingDivs(10);
let divs = storeSortingDivs();
let copyDivs = [...divs];
console.log('Original State: ')
console.log(divs);
//bubbleSort(divs);
//displayQuickSortInfo();
//quickSort(divs, 0, divs.length-1);
let x = mergeSort(copyDivs);
console.log('Sorted: ');
console.log(x);