Introduction
I am building a sorting algorithms visualizer for studying purposes, and i have a sortArray() function that takes care of animating the array being sorted. As you can see in the code.
and it works fine, but the problem is that i wanted to give the array takes some time to get sorted, and i wanted a way to stop this function while running.
What I've Tried So Far
After i realised that i had this problem, i thought that a "Stop" button would be a good way to solve my problem i added a new state called "Abort", and the original idea was that while the sortArray() function is running, the function would check everytime if the abort state is set to true, and if it is, the function would stop. and of course, it didn't work.
after that my previous attempt had failed, i deleted the "abort" state from the code and transformed it into a normal variable, thinking that the problem maybe was related to the setState function being async, and it didn't work either.
after that i did some research, but i didn't found anything useful.
The Code:
resetArray(){
const arrayToSort = [];
const prevChanged = [];
for (let i = 0; i < this.state.numberOfItems; i++) {
arrayToSort.push(this.RandomIntBetweenRange(5, 1000));
}
this.setState({ arrayToSort, prevChanged, abort: false });
}
generateNewArray(){
this.setState({abort: true},{
this.resetArray();
});
}
async SortArray(algo){
let sortedArrayAnim = algo(this.state.arrayToSort);
let arrayToSort = this.state.arrayToSort;
let prevChanged = this.state.prevChanged;
for (let index = 0; index < sortedArrayAnim.length; index++) {
const [i,j] = sortedArrayAnim[index];
if(this.state.abort){
console.log(abort);
return null;
}
let temp = arrayToSort[i];
arrayToSort[i] = arrayToSort[j];
arrayToSort[j] = temp;
prevChanged.push(i,j);
if(index == sortedArrayAnim.length - 1){
prevChanged.push(arrayToSort.length + 1, arrayToSort.length + 1);
this.setState({prevChanged});
}
this.setState({ arrayToSort,prevChanged });
await sleep(10);
}
}
render() {
const {arrayToSort} = this.state;
return (
<div className="main-div">
{arrayToSort.map((value, idx) => (
<div className="array-item" key={idx} style={{height: value, width: 800 / this.state.numberOfItems, backgroundColor: this.getColor(idx)}}>
</div>
))}
<button onClick={() => this.generateNewArray()}>Generate new array</button>
<button onClick={() => this.SortArray(BubbleSort)}>Bubble Sort</button>
<button onClick={() => this.SortArray(InsertionSort)}>Insertion Sort</button>
<button onClick={() => this.SortArray(QuickSort)}>Quick Sort</button>
<input type="number" min="5" max="1500" onChange={(event) => this.handleOnChange(event)} defaultValue={this.state.numberOfItems}/>
</ div>
);
}
Could you do something involving a Web-worker?
Structure the class something like this
class LongOperation extends React.Component {
constructor(props) {
super(props)
this.state = {
running: false,
results: null,
worker: null,
}
}
startLongOp(){
const worker = new Worker('yourIsolatedOp.js')
//...setup
this.setState({
running: true,
worker: worker,
})
}
cancel() {
if(running) {
this.state.worker.terminate()
}
this.setState({
running: false,
worker: null,
})
}
render() {
//Do your rendering
<button onClick={e => this.startLongOp()}>Start</button>
<button onClick={e => this.cancel()}>Cancel</button>
}
}
//while loop:
while(this.state.running) {
//do stuff
}
You could also probable do it with a while loop, but it would be on the UI thread. I have not tried this, it is just a sketch from the top of my head. Now designing a concurrent sort is going to be the bigger challenge... Workers are going to be the easiest if you can isolate it.
*note: I didn't go into details on how to deal with the web-worker passing data etc.. but you can easily find it. I see no reason you can't update the state from these messages.
Related
So I'm building a drum-pad type of app, and almost everything is working, except this.
Edit: Put the whole thing on codesandbox, if anyone wants to have a look: codesandbox.io/s/sleepy-darwin-jc9b5?file=/src/App.js
const [index, setIndex] = useState(0);
const [text, setText] = useState("");
const [theSound] = useSound(drumBank[index].url)
function playThis(num) {
setIndex(num)
}
useEffect(()=>{
if (index !== null) {
setText(drumBank[index].id);
theSound(index);
console.log(index)
}
}, [index])
When I press a button, the index changes to the value associated with the button and then the useEffect hook plays the sound from an array at that index. However, when I press the same button more than once, it only plays once, because useState doesn't re-render the app when the index is changed to the same value.
I want to be able to press the same button multiple times and get the app to re-render, and therefore useEffect to run every time I press the button. Can anyone help me how to do this?
Here's what I could come up with from your sandbox.
According to the docs each useSound is just a single sound, so when trying to update an index into a soundbank to use via React state the sound played will always be at least one render cycle delayed. I suggest creating a new custom hook to encapsulate your 9 drum sounds.
useDrumBank consumes the drumbank array and instantiates the 9 drum sounds into an array.
const useDrumBank = (drumbank) => {
const [drum0] = useSound(drumbank[0].url);
const [drum1] = useSound(drumbank[1].url);
const [drum2] = useSound(drumbank[2].url);
const [drum3] = useSound(drumbank[3].url);
const [drum4] = useSound(drumbank[4].url);
const [drum5] = useSound(drumbank[5].url);
const [drum6] = useSound(drumbank[6].url);
const [drum7] = useSound(drumbank[7].url);
const [drum8] = useSound(drumbank[8].url);
return [drum0, drum1, drum2, drum3, drum4, drum5, drum6, drum7, drum8];
};
Update the component logic to pass the drumBank array to the new custom hook.
const sounds = useDrumBank(drumBank);
Here's the full code:
function App() {
useEffect(() => {
document.addEventListener("keypress", key);
return () => document.removeEventListener("keypress", key);
}, []);
const [text, setText] = useState("");
const sounds = useDrumBank(drumBank);
function playThis(index) {
drumBank[index]?.id && setText(drumBank[index].id);
sounds[index]();
}
function key(e) {
const index = drumBank.findIndex((drum) => drum.keyTrigger === e.key);
index !== -1 && playThis(index);
}
return (
<div id="drum-machine" className="drumpad-container">
<div id="display" className="drumpad-display">
<p>{text}</p>
</div>
<button className="drum-pad" id="drum-pad-1" onClick={() => playThis(0)}>
Q
</button>
<button className="drum-pad" id="drum-pad-2" onClick={() => playThis(1)}>
W
</button>
<button className="drum-pad" id="drum-pad-3" onClick={() => playThis(2)}>
E
</button>
<button className="drum-pad" id="drum-pad-4" onClick={() => playThis(3)}>
A
</button>
<button className="drum-pad" id="drum-pad-5" onClick={() => playThis(4)}>
S
</button>
<button className="drum-pad" id="drum-pad-6" onClick={() => playThis(5)}>
D
</button>
<button className="drum-pad" id="drum-pad-7" onClick={() => playThis(6)}>
Z
</button>
<button className="drum-pad" id="drum-pad-8" onClick={() => playThis(7)}>
X
</button>
<button className="drum-pad" id="drum-pad-9" onClick={() => playThis(8)}>
C
</button>
</div>
);
}
Demo
Usage Notes
No sounds immediately after load
For the user's sake, browsers don't allow websites to produce sound
until the user has interacted with them (eg. by clicking on
something). No sound will be produced until the user clicks, taps, or
triggers something.
Getting the keypresses to consistently work seems to be an issue and I don't immediately have a solution in mind, but at least at this point the button clicks work and the sounds are played synchronously.
Have you considered combining those discrete state variables (value types) into a single reference type state object?
Instead of having an effect that sets the text when the index changes you just set the entire state at the same time.
As long as you ensure this is a new object/reference then the effect will fire. The effect is then only responsible for playing the sound based on the current state.
Here's some sample code
const [state, setState] = useState({ index: 0, text: '', });
const playThis = (num) => {
setState({ index: num, text: drumBank[num].id, });
};
useEffect(() => {
theSound(state.index);
}, [state]);
I am using hooks in React Native. This is my code:
useEffect(() => {
if (var1.length > 0 ){
let sym = [];
var1.map((items) => {
let count = 0;
state.map((stateItems) => {
if(items === stateItems.name) {
count = count + 1;
}
})
if (count === 0) {
sym.push(items)
}
});
async function getAllStates (sym) {
let stateValues = [];
await Promise.all(sym.map(obj =>
axios.get(ServerURL + "/note?name=" + obj).then(response => {
stateValues.push(response.data[0]);
})
)).then(() =>{
setNewItem(stateValues);
});
}
getAllStates (sym);
}
}, [var1]);
useEffect(() => {
let stateValues = state;
for( let count = 0 ; count < newItem.length; count++ ){
stateValues.push(newItem[count]);
}
setState(stateValues);
}, [newItem]);
This runs successfully without any errors. However, when the state is displayed as below, I am not seeing the latest value added in the state. It shows all the previous values. When I refresh the whole application, I am able to see my value added.
return (
<View style={styles.container}>
<Text style = {{color:"white"}}>
{
state.map( (item, key) =>{
return(
<Text key = {key} style = {{color:"white"}}> {item.name} </Text>
)
})
}
</Text>
</View>
);
Can someone tell me why this is happening? I want to see the data render immediately after the axios call. I am using React Native.
when i force update using :stackoverflow.com/questions/53215285/... it works fine. However, i am looking for a better fix if anyone can provide?
This should do:
useEffect(() => {
var1.forEach(async (name) => {
if (state.some(item => item.name === name)) return;
const response = await axios.get(ServerURL + "/note?name=" + name);
setState(state => [...state, response.data[0]]);
});
}, [var1]);
I still see two issues in your approach:
this code may start the same ajax request multiple times before the result of the firstz ajax-request is added to state; this also means that the result for the same name may be added multiple times to state.
for each item of var1 times each item of state, this is an O(n*m) problem or in this case basically O(n²) as m is pretty fast catching up to n. You should find a better approach here.
And I'm almost certain that [var1] is wrong here as the dependency for useEffect. But you'd need to show where this value comes from to fix that, too.
I've been learning React by doing simple games.
I'm doing a memory game which is, for the most part, finished and I'm not sure how I should refactor the code to be better.
The user task is basically to remember the numbers flashed, and then click them, until the user can't hold them in memory anymore.
How should I divide/refactor the code?
Also, how would you go forward with games like this? Would you perhaps create a gameEngine or such? Although this would be the part of refactoring right. I'm using React for games because I am learning React and improving it.
Code:
import React, { useState, useEffect } from 'react';
import Cell from './sub-components/Cell/Cell';
import './styles.scss';
function BoardScene() {
const [gameNumbers, setGameNumbers] = useState([]);
const [isPlayerTurn, setIsPlayerTurn] = useState(false);
const [currentScore, setCurrentScore] = useState(0);
const [bestScore, setBestScore] = useState(0);
const [flashCard, setFlashCard] = useState(null);
const [clickedNumber, setClickedNumber] = useState(null);
const [currentUserIndex, setCurrentUserIndex] = useState(0)
function startGame() {
addNewNumber()
}
function resetGame() {
setGameNumbers([])
setCurrentUserIndex(0)
if (bestScore < gameNumbers.length) return setBestScore(gameNumbers.length)
}
const blinkCell = () => {
let count = 0;
setIsPlayerTurn(false);
const timerID = setInterval(() => {
console.log("Inside interval before if")
console.log("currentUserIndex", count, gameNumbers.length)
if (count === gameNumbers.length) {
setIsPlayerTurn(true);
console.log("Inside time out if statement")
clearInterval(timerID);
count = 0;
setFlashCard(null);
} else {
setFlashCard(gameNumbers[count]);
count++;
}
}, 500);
};
function generateRandomNumber(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
function addNewNumber() {
console.log("Add new number")
let memoryNumber = generateRandomNumber(1, 9)
setGameNumbers(gameNumbers => [...gameNumbers, memoryNumber])
}
function clickedNumberHandle(number) {
console.log("Clicked number", number)
setClickedNumber(number)
isMatch(number)
}
function isMatch(number) {
if (number === gameNumbers[currentUserIndex]) {
console.log("Correct")
if (currentUserIndex + 1 === gameNumbers.length) {
setCurrentUserIndex(0)
console.log("set current index 0")
addNewNumber()
blinkCell();
} else {
console.log("set current index + 1")
setCurrentUserIndex(currentUserIndex + 1)
}
} else {
resetGame()
console.log("game over")
}
}
useEffect(() => {
blinkCell()
console.log("Use effect start blinkCell")
}, [gameNumbers])
return (
<>
<div className="game-container">
<div className="testing-stuff">
<div>
<button onClick={startGame}>Start Game</button>
</div>
<div onClick={addNewNumber}>
<button>Add new number</button>
</div>
<div>
<span>Game numbers: </span>
{gameNumbers.map((item, i) => {
return <span key={i}>{item}</span>
})}
</div>
<div>
<span>User Turn: {isPlayerTurn ? "True" : "False"}</span>
</div>
<div>
<span>Score: {gameNumbers.length}</span>
</div>
</div>
<div className="board">
{Array(9).fill().map((x, i) => {
return (
<Cell key={i} onClick={() => clickedNumberHandle(i + 1)} number={i + 1} active={i + 1 === flashCard ? true : false} />
)
})}
</div>
<div className="stats">
<div className="stats__score-wrap">
<span>Score: </span><span>{gameNumbers.length}</span>
</div>
<div className="stats__bestScore-wrap">
<span>Best Score: </span><span>{bestScore}</span>
</div>
</div>
</div>
</>
);
}
export default BoardScene;
GitHub Code: https://github.com/AurelianSpodarec/memory-numbers-game/blob/e177dfdaafe5daf393f1ae8fcee0827a16474e8f/src/scenes/BoardScene/BoardScene.js
Live Game: https://memory-numbers-game.netlify.app/
I'm no expert but I would personally re-factor to use ES6 syntax for all your functions. At the moment some of them are using arrow functions and some aren't.
I'd also personally have functions that are performing generic actions you might want to use across your codebase (such as generateRandomNumber) in their own modules.
On a specific point that isn't an opinion. I would not use the index (i) of your array as a key. This can lead to many issues. I would look into using a library like UUID to generate these keys.
Other than that looks again, and again I'm no expert. If you want some more info from someone who is I'd recommend Kent C Dodd blog and Ben Awads Youtube channel.
I am trying to get some data from API of eventbrite.
The data is event's name, and the name will be inserted into the list.
In render, buttons are created as many as the number of name which I got from API
I have got few questions below..
How to add information from API into array list - so I can use index, and value.
How to create buttons in forloop
e.g.
for ( var i =0; i<5; i++){
<Button
onPress={onPressLearnMore}
title="Learn More"
color="#841584"
accessibilityLabel="Learn more about this purple button"/>
} // % buttons are created.
This is my code.
export const renderButtons1 = (numOfBtns,title,site,navigated) => {
const views1 = [];
for ( var i = 0; i < numOfBtns; i++) {
views1.push(
<Button
onPress={(i) => navigate('EventsList', {
Title: title[i]
})
}
title = {title[i]}
color="#841584"
/>);
}
componentDidMount(){
return fetch('https://www.eventbriteapi.com/v3/events/search/location.address=glasgow&token=F7AWKEHKD6BW2TZKWO7N&expand=venue')
.then((response) => response.json())
.then((responseJson) => {
for(var x in responseJson.events){
this.setState({
state : this.state[Events].push(responseJson.events[x][0][0]["text"],"\n",)
});
}})
.catch((error) => {
console.error(error);
});
}
render() {
need to make buttons as many as the number of gotten name from API
}
For question 1, array form of api data will depend on how the data is structured before being sent to your application. You may have to shade more light on that.
Rendering views in a loop, try that
const renderButtons = () => {
const views = [];
for ( var i =0; i<5; i++){
views.push(
<Button
key={i}
onPress={onPressLearnMore}
title="Learn More"
color="#841584"
accessibilityLabel="Learn more about this purple button"
/>);
} // % buttons are created.
return views;
}
call renderButtons() in your render methods.
I am trying to iterate through data and displaying it in my react app. My code works until I store the results in the the components state. Somehow (I marked it insight the code as 'HERE IT DOESNT WORK ANYMORE' as its hard to explain. I have rewrote my actual code because I didn't want to post hundreds of lines of code here :) and also exchanged the data (which I am actually receiving making an http request) with an object (which I only resized) - so please don't pay attention if some parts of the code look weird/too much as my actual code is much bigger. I also added a codepen link here-where you can't see anything (because its not working) but I included console.logs so you can see what I mean. Thank you very much for any help!!
https://codepen.io/helloocoding/pen/vWVLmp?editors=1010
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: PricedItineraries,
result: []
}
this.handleInput = this.handleInput.bind(this);
this.showSearchResults = this.showSearchResults.bind(this);
}
showSearchResults(obj) {
console.log("func", this.state.data)
var allTrips = [];
var TotalPriceTemp = [];
for (var i = 0; i < obj.length; i++) {
var o = obj[i];
allTrips.push(o.AirItinerary.OriginDestinationOptions.OriginDestinationOption)
var totalFair = o.AirItineraryPricingInfo.PTC_FareBreakdowns.PTC_FareBreakdown.PassengerFare.TotalFare;
TotalPriceTemp.push(totalFair.Amount);
}
for (var i = 0; i < allTrips.length; i++) {
var MarketingAirlineTo = [];
var MarketingAirlineBack = [];
var TotalPrice = 0;
var oneWay = allTrips[i][0];
var returnTrip = allTrips[i][1];
oneWay.FlightSegment.forEach(function (flightTo) {
MarketingAirlineTo.push(flightTo.MarketingAirline.Code);
})
returnTrip.FlightSegment.forEach(function (flightTo) {
MarketingAirlineBack.push(flightTo.MarketingAirline.Code)
})
TotalPriceTemp.forEach(function (item) {
TotalPrice += item;
})
var tempFlightsObject = [];
tempFlightsObject.push({
marketingAirlineTo: MarketingAirlineTo,
marketingAirlineBack: MarketingAirlineBack,
TotalPrice: TotalPriceTemp[i]
})
console.log("tempFlightsObject", tempFlightsObject)
this.setState({
result: tempFlightsObject
})
console.log("result", this.state.result)
//HERE IT DOESNT WORK ANYMORE: TEMPFLIGHTOBJECT shows correct data, but result doesn't
}
}
handleInput() {
console.log("handeInput func", this.state.data)
this.showSearchResults(this.state.data);
console.log("handeInput func -result", this.state.result)
}
render() {
const flights = this.state.result.map((item, i) => {
return (
<div>
<div key={i}><p> {item.marketingAirlineTo} </p></div>
</div>
)
})
return (
<div>
<button onClick={this.handleInput}>click me</button>
<div> {flights}</div>
</div>
)
}
}
React.render(<App />, document.getElementById('app'));
setState may be executed asynchronously by react to batch updates and optimize performance.
You can provide a callback to setState, which will be called once the update has actually been completed:
this.setState({
result: tempFlightsObject
}, function () { console.log("result", this.state.result) })
setState is an asynchronous function. React batches updates together for performance reasons. See https://reactjs.org/docs/react-component.html#setstate for more information.
Your component should re-render, but you may not be able to log the state immediately after you set it. If you want, you can add a callback as the second argument to setState, which takes state as its first and only argument.
setState may be batched.
this.setState({ result: tempFlightsObject }, () => {
console.log("result", this.state.result)
})
will work