i'm trying to understand better react and algorithms so i decided to implement a visual bubbleSort.
Problem
I'm able to generate a random array of numbers + sort the values and display them...but
I cannot figure how to make the swap animation between two elements.
Code
import React, {Component} from 'react';
import './Chart.css';
class Chart extends Component {
state = {
columns: [],
buttonPressed: false,
}
bubbleSort = (inputArr) => {
let len = inputArr.length;
for (let i = 0; i < len; i++) {
for (let j = 0; j < len; j++) {
if (inputArr[j] > inputArr[j + 1]) {
let tmp = inputArr[j];
inputArr[j] = inputArr[j + 1];
inputArr[j + 1] = tmp;
}
}
}
return inputArr;
};
toggleOn = () =>{
this.setState({
columns: this.props.columns,
buttonPressed: !this.state.buttonPressed,
});
let numbers = this.props.columns;
this.bubbleSort(numbers);
}
render() {
//riformulare queste tre righe di codice
//sarebbe opportuno pensare ad un componente bar?
const numbers = this.props.columns;
const bar = numbers.map((number) =>
<div className="bar" style={{height: number+'px' }}>{number}</div>
);
return (
<div>
{numbers.length > 0 ?
<div>
<button className="sortButton" onClick={this.toggleOn}>Sort with Bubblesort</button>
<div className="chart">
{bar}
</div>
</div>
: null
}
</div>
);
}
}
export default Chart;
Considerations
1) the this.props.columns is an array filled of random numbers.
I saw something similar in this link, could you please give me a little help about some starting point ?
https://codepen.io/lonekorean/pen/bqjzqr
2) this is the repository where you will find all the components:
github repository
Related
I am trying to build a sorting visualizer in React.
Unfortunately, my useState doesn't update its value after a random array gets generated.
But in an earlier project it worked just fine.
I tried different ways to solve this issue but none of them worked.
import React, { useState } from "react";
// import "./SortingVisualizer.css";
export default function SortingVisualizer() {
const [numberArray, setNumberArray] = useState(null);
function resetArray() {
const array = [];
for (let i = 0; i < 100; i++) {
let num = randomIntFromInterval(5, 1000);
array.push(num);
console.log(array);
}
setNumberArray(array);
}
if (numberArray === null) resetArray();
return (
<div className="array-container">
{numberArray.map((value, idx) => {
return (
<div className="array-bar" key={idx}>
{value}
</div>
);
})}
</div>
);
}
function randomIntFromInterval(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
I'm trying to expand on the official react Tic-Tac-Toe tutorial: https://reactjs.org/tutorial/tutorial.html#completing-the-game by creating a linked list to search for the win condition. However, I am having issues accessing the information. Does anyone know where I'm going wrong? I keep getting undefined with my console.log on line 138
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
function Square(props) {
return (
\<button className="square" onClick={props.onClick}\>
{props.value}
{props.rightmiddle}
{props.righttop}
{props.rightbottom}
{props.lefttop}
{props.leftbottom}
{props.top}
{props.bottom}
\</button\>
);
}
class Board extends React.Component {
renderSquare(i) {
return (
\<Square
value={this.props.squares\[i\]}
rightmiddle = {null}
righttop = {null}
rightbottom = {null}
leftmiddle = {null}
lefttop = {null}
leftbottom = {null}
top = {null}
bottom = {null}
onClick={() =\>
this.props.onClick(i)
}
/\>
);
}
forloop(x){
const numcolumns = 3;
const options = [];
for (let i = 0; i < numcolumns; i++) {
options.push(this.renderSquare(i + x));
}
return (
<div className="board-row">
{options}
</div>
)
}
render() {
const numrows = 3;
const linklistTRow = [];
const linklistBRow = [];
const linklistMRow = [];
const rows = [];
for(let i = 0; i < numrows; i++)
{
rows.push(this.forloop(i*numrows));
if (i === 0) { linklistTRow.push(rows[0])};
if (i === 1) { linklistMRow.push(rows[1])};
if (i === 2) { linklistBRow.push(rows[2])};
};
return (
<div> {rows} </div>
);
}
}
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
history: \[{
squares: Array(9).fill(null),
}\],
stepNumber: 0,
xIsNext: true,
};
}
handleClick(i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1);
const current = history\[history.length - 1\];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares\[i\]){
return;
}
squares\[i\] = this.state.xIsNext ? 'X' : 'O';
this.setState({
history: history.concat(\[{
squares: squares,
}\]),
stepNumber: history.length,
xIsNext: !this.state.xIsNext,
});
}
jumpTo(step) {
this.setState({
stepNumber: step,
xIsNext: (step % 2) === 0,
});
}
render() {
const history = this.state.history;
const current = history[this.state.stepNumber];
const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => {
const desc = move ?
'Go to move #' + move :
'Go to game start';
return (
<li key={move}>
<button onClick = {() => this.jumpTo(move)}>{desc}
</button>
</li>
);
});
let status;
if (winner) {
status = 'Winner: ' + winner;
}
else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
return (
<div className="game">
<div className="game-board">
<Board
squares = {current.squares}
onClick={(i) => this.handleClick(i)}
log = {console.log(this.props.value)}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
</div>
</div>
);
}
}
// ========================================
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(\<Game /\>);
function calculateWinner(squares) {
const lines = \[
\[0, 1, 2\],
\[3, 4, 5\],
\[6, 7, 8\],
\[0, 3, 6\],
\[1, 4, 7\],
\[2, 5, 8\],
\[0, 4, 8\],
\[2, 4, 6\],
\];
for (let i = 0; i \< lines.length; i++) {
const \[a, b, c\] = lines\[i\];
if (squares\[a\] && squares\[a\] === squares\[b\] && squares\[a\] === squares\[c\]) {
return squares\[a\];
}
}
return null;
}
I have been trying to use props.value to call the square number (0 to 8) however, this is showing undefined results.
I am learning react and decided to try create a sorting visualizer. I started with bubble sort and pretty much succeeded creating a basic visualizer. I added the option to change the speed of the sorting and the length of the array that is sorted. I tested it a few times and what I found out is that sometimes when displaying the sorted array some elements are not in place. This can be seen when you have a tall element not in the place it should be (the array itself behind the scene is sorted properly). So something happening to the display elements and I do not know what.
After the sorting is completed and if some of the elements are not in place, if I change the speed suddenly the elements jump back to where they suppose to be. I guess this is because the speed is part of the state and a re-redering is happeing.
What should I do to fix this?
Here is my code:
import React, { useContext, useState, useEffect } from 'react';
const NUMBER_OF_ELEMENTS = 10;
const DEFAULT_COLOR = 'white';
const COMPARE_COLOR = 'darkred';
const DONE_COLOR = 'green';
const SPEED = 4;
const SPEEDS = [1, 5, 10, 25, 50, 100, 150, 200, 250, 300];
const randomIntFromInterval = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min);
}
const Dummy = () => {
const [arr, setArr] = useState([]);
const [numberOfElements, setNumberOfElements] = useState(NUMBER_OF_ELEMENTS);
const [speed, setSpeed] = useState(SPEED);
const timeout_id = [];
useEffect(() => {
generateArray();
}, [numberOfElements]);
const reset = () => {
resetColors();
generateArray();
}
const generateArray = () => {
const arr1 = [];
for(let i = 0; i < numberOfElements; i++)
{
arr1[i] = randomIntFromInterval(5, 100);
}
console.log(arr1);
setArr(arr1);
}
const resetColors = () => {
const arrayBars = document.getElementsByClassName('array-bar');
for(let i = 0; i < arrayBars.length; i++) {
arrayBars[i].style.backgroundColor = DEFAULT_COLOR;
}
}
const bubbleSort = (arr, n) => {
let i, j, temp, swapped, delay = 1;
for(i = 0; i < n - 1; i++)
{
swapped = false;
for(j = 0; j < n - i - 1; j++)
{
createColor([j, j + 1], COMPARE_COLOR, delay++);
if(arr[j] > arr[j + 1])
{
// swap arr[j] and arr[j+1]
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
createAnimation(j, j + 1, delay++);
}
createColor([j, j + 1], DEFAULT_COLOR, delay++);
}
createColor([n - i - 1], DONE_COLOR, delay++);
// If no two elements were
// swapped by inner loop, then break
if(swapped === false) break;
}
const leftovers = [];
for(let k = 0; k < n - i - 1; k++) {
leftovers.push(k);
}
createColor(leftovers, DONE_COLOR, delay++);
}
const createAnimation = (one, two, delay) => {
const arrayBars = document.getElementsByClassName('array-bar');
const id = setTimeout(() => {
const barOneHeight = arrayBars[one].style.height;
const barTwoHeight = arrayBars[two].style.height;
arrayBars[two].style.height = `${barOneHeight}`;
arrayBars[one].style.height = `${barTwoHeight}`;
}, SPEEDS[speed - 1] * delay);
timeout_id.push(id);
}
const createColor = (indexes, color, delay) => {
const arrayBars = document.getElementsByClassName('array-bar');
const id = setTimeout(() => {
for(let i = 0; i < indexes.length; i++) {
arrayBars[indexes[i]].style.backgroundColor = color;
}
}, SPEEDS[speed - 1] * delay);
timeout_id.push(id);
}
const handleSort = (arr) => {
bubbleSort(arr, arr.length);
}
const handlerRange = (e) => {
setNumberOfElements(e.target.value);
}
const stopTimeOuts =() => {
for(let i = 0; i < timeout_id.length; i++) {
clearTimeout(timeout_id[i]);
}
}
const handleSpeed = (e) => {
setSpeed(e.target.value);
}
const maxVal = Math.max(...arr);
return (
<div>
<div className="array-container" style={{height: '50%', backgroundColor: 'black'}}>
{arr.map((value, idx) => (
<div className="array-bar"
key={idx}
style={{
backgroundColor: DEFAULT_COLOR,
height: `${(value * 100 / maxVal).toFixed()}%`,
width: `${85 / arr.length}%`,
display: 'inline-block',
margin: '0 1px'
}}>
</div>
))}
</div>
<div className="buttons-container">
<button onClick={() => handleSort(arr)}>Sort!</button>
<button onClick={() => reset()}>Reset</button>
<button onClick={() => stopTimeOuts()}>Stop!</button>
</div>
number of elements: {numberOfElements}
<div className="slider-container">
1
<input type="range"
min="1"
max="100"
onChange={(e) => handlerRange(e)}
className="slider"
id="myRange"
/>
100
</div>
speed: {speed}
<div className="slider-container">
1
<input type="range"
min="1"
max="10"
onChange={(e) => handleSpeed(e)}
className="slider"
id="myRange"
/>
10
</div>
</div>
);
}
export default Dummy;
Set the key inside of the div to be a unique identifier instead of idx - in this case you can use value:
/* -- snip -- */
<div className="array-bar"
key={value}
/* -- snip -- */
This will stop react recycling each div with their respective indexes and instead re-render and re-order based on the new array.
The reason you see the unexpected behaviour is because react uses keys to identify each element. So if you sort your array then apply an index as the id on render, react will get the element that was first rendered with that index as it's key and put it in that place. By changing the key to a unique identifier, react does not get the elements mixed up (since the unique identifier never changes with respect to its element) and can now accurately render each item in the order you intend.
Example:
We render a list:
<div id=1>Foo</div> // Id "1" is now bound to this element
<div id=2>Bar</div>
So if you then reorder the list like this (note the id changes):
<div id=1>Bar</div> // this will be transformed to <div id=1>Foo</div>
<div id=2>Foo</div> // this will be transformed to <div id=2>Bar</div>
Then react will transform the elements into whatever id they were assigned to first - This is why it's important to have unique identifiers. You can generate a unique identifier via a library that generates uuids or other sufficiently random strings.
You can read more here: https://medium.com/#robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318
I am creating an app, where users should compose a correct sentence from shuffled one. Like on the following picture. The problem is that I am not implementing the app in a right way. For example, I do not change inputs through state. And my main question is how to clear input of a particular line (a bunch of inputs) ? I need it when a user types something wrong and needs to start again. I know that input field should be controlled through state, but it is impossible since all inputs are generated according to number of words. And in one line there maybe more than one input. Let me explain how my code works, so that you have an idea my implementation.
This is where I get data from.
export default {
id:'1',
parts:[
{
speaker:'Speaker1',
words:'Hello how are you?'
},
{ speaker:'Speaker2',
words:'I am OK, thanks'
},
{ speaker:'Speaker1',
words:'What are your plans for Saturday?'
}
]
}
import React, { Component } from 'react';
import {CopyToClipboard} from 'react-copy-to-clipboard';
import { MdDoneAll } from "react-icons/md";
// This is the array where data from inputs is pushed to
var pushArr = [];
// Initial points
var points = 0;
class DialogueShuffleFrame extends Component {
constructor(props) {
super(props)
this.state = {
// Variable to show correct or incorrect notification
showCorrect:false
}
this.writeSometihng = this.writeSometihng.bind(this)
}
// Function that is triggered when a tick is clicked
// Pushes value to the pushArr array when onBlur function is triggered
writeSometihng(e) {
e.preventDefault()
pushArr.push(e.target.value)
console.log(pushArr)
}
// Function check if array of value from input matches to an array from
// initial data source
checkLines(arr, lines) {
let joinedStr = arr.join(' ');
//console.log(joinedStr);
lines[0].parts.map((obj) => {
let line = obj.words
if (joinedStr === line) {
this.setState({
showCorrect:true
})
pushArr = [];
points += 80;
} else {
pushArr = [];
}
})
}
// Resets pushArr array
reset() {
pushArr.length = 0
console.log('clicked')
}
// Shuffles words
formatWords(words) {
const splittedWords = words.split(' ')
const shuffledArray = this.shuffle(splittedWords)
return (
shuffledArray.map((word, index) => (
<>
<input className="word-to-drop-input" id={index} onBlur={this.writeSometihng} size={2} />
<CopyToClipboard text={word}>
<span key={uid(word)} value={word} className="word-to-drop">{word}</span>
</CopyToClipboard>
</>
))
)
}
shuffle(a) {
var j, x, i;
for (i = a.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = a[i];
a[i] = a[j];
a[j] = x;
}
return a;
}
render() {
const {lines} = this.props
const shuffles = lines[0].parts && (
lines[0].parts.map((element,i) => (
<>
<li className="line" key={i}><span>{element.speaker}{": "}</span><span>{this.formatWords(element.words)}</span></li>
<MdDoneAll type="button" onClick={() => {this.checkLines(pushArr, lines)}} style={{color:'white'}}/>
</>
))
)
return (
<>
<h1 className="centered" style={{color:'white'}}>Dialogue shuffle frame</h1>
<ul className="lines-container">
{shuffles}
</ul>
{<div className="reactangular">{this.state.showCorrect ? 'Correct' : 'Incorrect'}</div>}
<div>{points}</div>
<div className="reactangular" onClick={() => this.reset()}>Reset</div>
</>
)
}
}
export default DialogueShuffleFrame;
I'd recommend to use proper data structure with state management, but that's a different story.
To solve your specific issue of clearing input elements, you can use ReactDOM.findDOMNode to access the DOM node and traverse the input elements and set the value to empty string.
Something like this:
class App extends React.Component {
check = (ref) => {
const inputElements = ReactDOM.findDOMNode(ref.target).parentNode.getElementsByTagName('input');
[...inputElements].forEach(el => el.value = '')
}
render() {
return (
<div>
<ul>
<li>
<input />
<input />
<button onClick={this.check}>Check</button>
</li>
<li>
<input />
<input />
<input />
<button onClick={this.check}>Check</button>
</li>
</ul>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
I am trying to create a dynamic element. whatever user input in an input box, the same number of LI element will create.
I get this output when I enter 2 in input box.
<li key=0>input 0</li><li key=1>input 1</li>
where I want two li elements.
import React, { Component } from "react";
import logo from "./logo.svg";
import "./App.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
times: 0
};
}
createList = event => {
let times = event.target.value;
this.setState({
times
});
};
getData = times => {
let str = "";
for (let i = 0; i < times; i++) {
str += "<li key=" + i + ">input " + i + "</li>";
}
return <ul>{str}</ul>;
};
render() {
return (
<div className="App">
<p className="App-intro">
<input type="number" onChange={this.createList} />
{this.getData(this.state.times)}
</p>
</div>
);
}
}
export default App;
This is because you are treating them as string instead of DOM node elements. Use an array and push all the elements inside that and return the final result.
Don't forgot you are writing JSX, and <li> input: {i+1} </li> will get converted into:
React.createElement(
"li",
null,
"input: ",
i+1
);
But if you write: "<li>input: {i+1}</li>", it will be treated as a string.
Check the complied code result of these two cases by Babel REPL.
Solution:
Write it like this:
getData = times => {
let str = [];
for (let i = 0; i < times; i++) {
str.push(<li key={i}> input: {i+1} </li>);
}
return <ul>{str}</ul>;
};