Issue animating ui based on reducer state changes - javascript

I am building a react project for visualizing insertion sort using redux. I am using react-redux to create and handle actions. However, the problem is that in my insertionSort algorithm, I dispatch an updateArray action every time the array being sorted changes. I put print statements inside the reducer and saw that the state was in fact changing and the action was being dispatched correctly, however, my actual array does not re-render. I put prints inside the relevant UI component's render() function and saw that it was only being called once or twice rather than every time the reducer receives the action. I tried restructuring my code multiple times and reading about similar problems that people have had but their answers did not help me.
Am I just structuring this the wrong way? Should I not be using dispatches every second or so to update my array?
I have a main.js file which is used to render the UI components including my array:
class Main extends React.Component {
setArray = () => {
this.props.setArray(50, window.innerHeight / 1.4)
startSort = () => {
this.props.startSorting(this.props.algorithm, this.props.array)
}
render() {
let { array} = this.props
return (
<div>
<Navbar
startSort={this.startSort}
setArray={this.setArray}
/>
<MainWrapper>
<Row />
</MainWrapper>
</div>
)
}
}
const mapStateToProps = state => {
return {
array: state.array,
}
}
const mapDispatchToProps = dispatch => {
return {
setArray: (length, height) => {
let array = Array.from({ length: length }, () =>
Math.floor(Math.random() * height))
dispatch(setArray(array))
},
startSorting: (algorithm, array) => {
var doSort
if (algorithm == 'insertionSort') {
doSort = insertionSort
}
doSort(array, dispatch)
}
}
}
My actual array is generated with Row.js
class Row extends React.Component {
generateNodes(array) {
var elements = []
array.forEach((value, index) => {
elements.push(
<CenteredColumn>
<ArrayNode idx={index} value={value} />
</CenteredColumn>
)
})
return elements
}
render() {
let { array } = this.props
console.log('UPDATED ARRAY: ' + array)
var arrayElements = this.generateNodes(array)
return <RowWrapper>{arrayElements}</RowWrapper>
}
}
const mapStateToProps = state => {
return {
array: state.array
}
}
And finally, my actual algoritm is in insertionSort.js in which I import my actions from their reducers and pass in a dispatch function from main.js:
function delayedInsertion(array, dispatch) {
let n = array.length
var i = 0
function loop() {
setTimeout(function() {
var temp = array[i]
var j = i - 1
while (j >= 0 && array[j] > temp) {
array[j + 1] = array[j]
j--
}
array[j + 1] = temp
// console.log('ARRAY: ' + array)
dispatch(updateArray(array))
i++
if (i < n) {
loop()
}
}, 200)
}
loop()
console.log('DONE')
}

It seems that you are mutating your state.
You are passing this.props.array to your doSort action and as I understand your idea correctly, you are just calling delayedInsertion from that action (you did not post source code of that action).
But in delayedInsertion you are mutating the passed array when you are changing positions of you items, here:
while (j >= 0 && array[j] > temp) {
array[j + 1] = array[j]
j--
}
array[j + 1] = temp
You need to perform immutable change of the array.

Related

Element is not inserting into array in a loop and also looping twice JS/React

I try to insert a string into a particular index of object if condition is true inside a forloop but its not inserting of some reason. I tried to use push and append and splice but splice just inserting entire string as an new object into the array and i need it to just append to existing object. Any ideas how to make it work?
Data looks like that:
const [concerts, setConcerts] = useState([]);
const [tickets, setTickets] = useState([]);
const [limit, setLimit] = useState(25);
const navigate = useNavigate();
const [button, setButton] = useState(false);
const [array, setArray] = useState([]);
//Raw JSON Date example: "2023-02-08T23:15:30.000Z"
let currentDate = new Date().toJSON().slice(0, 10);
const json = { available: "true" };
useEffect(() => {
const loadConcerts = async () => {
const resConcerts = await axios.get("/data/concerts");
const resTickets = await axios.get("/data/tickets");
let table = [];
setTickets(resTickets.data);
// getting all concerts above today
const filteredData = resConcerts.data.filter((concert) => {
return concert.datum >= currentDate;
});
filteredData.forEach((element) => {
table.push(element);
// table.splice(10, 0, { status: "available" });
});
setArray(table);
for (let i = 0; i < resTickets.data.length; i++) {
for (let j = 0; j < filteredData.length; j++) {
if (
resTickets.data[i].concertid == filteredData[j].id &&
resTickets.data[i].booked == 0
) {
table.push({ status: "avaiable" });
// table.splice(10, 0, { status: "available" });
}
}
}
setArray(table);
// filteredData.forEach((concert) => {
// for (const ticket of tickets) {
// if (concert.id == ticket.concertid && ticket.booked == 0) {
// table.push(json);
// }
// }
// });
setConcerts(
filteredData.sort((a, b) =>
a.datum > b.datum ? 1 : a.datum < b.datum ? -1 : 0
)
);
};
console.log("from use effect: " + array.length);
loadConcerts();
}, []);
After using splice method:
Update
Problem is solved. I used Object.assign() helped to append string to existing object in array. Actually i had to insert another object, not a single variable.
The problem is you are trying to push a string "available" into an array-of-objects.
Here you see the object with a property datum:
const filteredData = resConcerts.data.filter((concert) => {
return concert.datum >= currentDate;
});
Yet below when you push, you are not pushing an object into the array which is problematic. It should probably be something like this but you have to verify:
Instead of this:
filteredData.push("available");
Domething like this:
filteredData.push({ datum: '', status: 'available' );
I don't know what your data object is but it's an object not a string you need to add to that array.
The looping twice is likely from React 18 New Strict Mode Behaviors. It intentionally unmounts/remounts components to fire your useEffect calls twice - so that you can identify problematic side effects. If you remove <StrictMode> or run in production that double-looping should not occur.
Problem solved. Push() neither splice() method didn't helped. What helped me to append my object to another object without changing the data was Object.assign() function
for (let i = 0; i < resTickets.data.length; i++) {
for (let j = 0; j < filteredData.length; j++) {
if (
resTickets.data[i].concertid == filteredData[j].id &&
resTickets.data[i].booked == 0
) {
Object.assign(filteredData[j], obj);
}
}
}

Uncaught TypeError: Cannot read properties of undefined (reading '0'), Summing useState arrays

const [lnames, setlNames] = React.useState();
const [lnums, setlNums] = React.useState();
React.useEffect(() => {
axios.get("http://localhost:7001/lunch").then(response => {
let arr1 = [];
let arr2 = [];
response.data.forEach(c => {
arr1.push(c.table_id);
arr2.push(c.table_num_count);
});
setlNames(arr1);
setlNums(arr2);
});
}, []);
const [dnums, setdNums] = React.useState();
React.useEffect(() => {
axios.get("http://localhost:7001/dinner").then(response => {
let arr1 = [];
response.data.forEach(c => {
arr1.push(c.table_num_count);
});
setdNums(arr1);
});
}, []);
const [bnums, setbNums] = React.useState();
React.useEffect(() => {
axios.get("http://localhost:7001/breakfast").then(response => {
let arr1 = [];
response.data.forEach(c => {
arr1.push(c.table_num_count);
});
setbNums(arr1);
});
}, []);
const customer_count_breakfast = bnums;
const customer_count_lunch = lnums;
const customer_count_dinner = dnums;
let sumArray = []
if (lnums & lnums.length > 0) {
//sumArray = lnums.map((l, i) => l + bnums[i] + dnums[i]);
for (let i = 0; i < lnums.length; i++) {
sumArray[i] = bnums[i] + lnums[i] + dnums[i];
}
}
// IF COMMENT ABOVE AND UNCOMMENT HERE THE ERROR IS GONE BUT sumArray is not computed
// if (lnums && lnums[0]) {
// //sumArray = lnums.map((l, i) => l + bnums[i] + dnums[i]);
// for (let i = 0; i < lnums.length; i++) {
// sumArray[i] = bnums[i] + lnums[i] + dnums[i];
// }
// }
Hello I have the above code giving me the following error at the if loop condition : Uncaught TypeError: Cannot read properties of undefined (reading '0'). I think I know why the error is being displayed. It is indicating that I am trying to access a property or index of a variable that is undefined, this is most likely due to the useEffect, where when I call for the if condition the value is not set yet, so it is undefined and thus does not have a length. When I try to comment the first if condition and uncomment the second, I get no more errors but the summation does not work, it returns an empty array.
How would I be able to sum the values of .useState() variables in this case?
You need to look into react lifecycle and useMemo.
read about lifecycle: https://www.w3schools.com/react/react_lifecycle.asp
read about useMemo: https://www.w3schools.com/react/react_usememo.asp
Here is a quick example where useEffect puts numbers into an array, useMemo has dependency on those arrays and adds all the numbers together.
import { useEffect, useState, useMemo } from "react";
export default function App() {
const [arr1, setArr1] = useState([]);
const [arr2, setArr2] = useState([]);
// called everytime arr1 or arr2 changes. Which causes react to rerender.
// first run sum = 0
const sum = useMemo(() => {
let res = 0;
if (arr1.length && arr2.length) {
for (let i = 0; i < arr1.length; i++) {
res += arr1[i] + arr2[i];
}
}
return res;
}, [arr1, arr2]);
// called once, updated setArr1 causing sum to be called
useEffect(() => {
setArr1([1,2,3]);
}, []);
// called once, updated setArr2 causing sum to be called
useEffect(() => {
setArr2([4,5,6]);
}, []);
return (
<div>
<p> sum: {sum} </p>
</div>
);
}
Essentially In your code you are calling the arrays before useEffect has finished setting their values.
I solved the issue by adding a try-catch statement, not sure if it is feasible at all
try {
for (let i = 0; i < lnums.length; i++) {
sumArray[i] = bnums[i] + lnums[i] + dnums[i];
}
console.log("Statement was successful");
} catch (error) {
}

How can I update individual elements of an array using state hook?

This is the section of code I am dealing with,
This is my hook
const [array, setArray] = useState(
JSON.parse(localStorage.getItem("notes")) ?? []
);
And this is the function,
const save = () => {
let info = one.current.value;
console.log(newIndex, " and ", info);
console.log("this si array element -> ", array[newIndex - 1]);
array[newIndex - 1] = info;
if (newIndex !== undefined) {
setArray((e) => {
return e.map((e1, i) => {
if (i + 1 === newIndex) {
return [...e ];
}
});
});
}
};
info has the correct input I want to update to and newIndex have the index of the element I wish to update.
Can you suggest to me what I need to change in this section, this is part of the above function,
setArray((e) => {
return e.map((e1, i) => {
if (i + 1 === newIndex) {
return [...e ];
}
});
To update your array you do not need to map anything. You only need to copy the array, modify the copied array at the right index and return the copied array. React will take care of the rest.
It's important, that you copy the array, else react won't notice the change. useState only remembers the array's address in memory.. Only through a new array with a new address will useState actually fire.
Here is my take on what I think your save() function should look like:
const save = () => {
let info = one.current.value;
if (newIndex !== undefined) {
console.log(newIndex, " and ", info);
console.log("this is array element -> ", array[newIndex - 1]);
setArray((e) => {
let temp = [...e];
temp[newIndex - 1] = info;
return temp;
});
}
};
Tried the code in codesandbox and I think this is what you want.

How to properly deconstruct an array of arrays using hooks in react

I'm trying to turn a long list of objects (accounts) into smaller arrays of 6 or less. I'm doing this because I have a table in my react component that is listing all the accounts, however, it cannot hold any more than 6 accounts. I created the pagination and everything, and thought that it would work, but the current page doesn't ever seem to update.
const [pageNum, setPageNum] = useState(1);
const [numOfPages, setNumOfPages] = useState(Math.ceil(accounts.length / 6));
const [page, setPage] = useState([]);
const [pages, setPages] = useState([null]);
const onClick = (e) => {
setPageNum(pageNum + 1);
setPage(pages[pageNum]);
};
useEffect(() => {
if (numOfPages > 1) {
for (let i = 0; i < 6; i++) {
setPages(pages.push(accounts.slice(i * 6, i * 6 + 6)));
}
}
console.log(pages[1][1]);
console.log(pages[2][2]);
setPage([pages[1][1], pages[1][2]]);
console.log(page);
}, []);
This isn't the final code, but rather part of my troubleshooting and I also think its where the issue is coming up. When I console log pages[1][1] and pages[1][2], I get two separate objects. So how come after using setPage and placing the two 'objects' inside of an array, does the console log of page come out as an empty array?? I've tried refactoring so many things. I've changed pages to an object instead of an array, i've tried doing it in another method instead of useEffect, all kinds of things. But the root of the issue seems to be that often my 'setPage' and my 'setPageNum' methods fail for seemingly no reason.
Prepare the pages array with useMemo (it will only change when accounts changes).
const { useState, useMemo, useCallback } = React;
const Pager = ({ accounts }) => {
const [pageNum, setPageNum] = useState(0);
const pages = useMemo(() => {
const pages = [];
for (let i = 0; i < accounts.length; i += 6) {
pages.push(accounts.slice(i, i + 6));
}
return pages;
}, [accounts]);
// console.log(pages);
const numOfPages = pages.length;
const page = pages[pageNum];
const next = useCallback(() => {
setPageNum(pageNum => pageNum + 1);
}, []);
const prev = useCallback(() => {
setPageNum(pageNum => pageNum - 1);
}, []);
return (
<div>
<button disabled={pageNum === 0} onClick={prev}>Prev</button>
<button disabled={pageNum === numOfPages - 1} onClick={next}>Next</button>
<ul>
{pages[pageNum].map((s, i) => <li key={i}>{s}</li>)}
</ul>
</div>
);
};
const accounts = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
ReactDOM.render(
<Pager accounts={accounts} />,
root
)
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
The main problem in your code is the way you use setPages:
setPages(pages.push(accounts.slice(i * 6, i * 6 + 6)));
The Array.push() returns the new length of the array. So your pages state is actually the last number. It's the last number because setState replaces the state, and doesn't add it.
The term you are looking for is chunk.
Since the data you are chunking comes from props, you don't need chunk inside the useEffect hook. It's not a side effect.
Here is a chunk function I found on the internet:
function chunkArray(myArray, chunk_size){
var index = 0;
var arrayLength = myArray.length;
var tempArray = [];
for (index = 0; index < arrayLength; index += chunk_size) {
myChunk = myArray.slice(index, index+chunk_size);
// Do something if you want with the group
tempArray.push(myChunk);
}
return tempArray;
}

Generate random number different from the last item N in the array

I have the following, which works fine. It generates a unique random number for a given empty array and a Max determined by the another array (data) length. I would like to add a check that does:
when the array length is = MaxN, I want to store the last value of the array inside a variable so that if it is = to a new random generated number I will call "generateRandomNumber(array, maxN)" again.
const generateRandomNumber = (array, maxN, lastN) => {
let randomN = Math.floor(Math.random() * maxN) + 0;
console.log(lastN)
if(lastN == randomN) {
// do your thing
}
if(array.includes(randomN)) {
return generateRandomNumber(array, maxN, lastN);
}
if(array.push(randomN) == maxN) {
lastN = array.length - 1
array.length = 0;
}
return randomN
}
export default generateRandomNumber
however I am always getting undefined inside the console.log. I am passing lastN like so:
let lastN;
I would think that that value which is undefined at first would later get updated inside:
if(array.push(randomN) == maxN) {
lastN = array.length - 1
array.length = 0;
}
component where generateRandomNumber is used:
...
const utilityArray = []
const tempQuestions = []
let lastN
class App extends Component {
constructor(props) {
super(props);
this.state = {
collection: gridItemsCollection,
intro: false,
instructions: false,
grid: true,
questions: this.props.questions,
selectedQuestion: ""
}
}
getRandomN = (arr, max, lastN) => {
let s = generateRandomNumber(arr, max, lastN)
return s
}
hideGridItem(e) {
let //index = this.getRandomN(utilityArray, gridItemsCollection.length),
collection = this.state.collection,
newCollection,
//updatedGridItem = collection[index].hidden = true,
questions = this.state.questions.questions,
n = this.getRandomN(tempQuestions, questions.length, lastN);
console.log(lastN)
// this.setState({
// newCollection: [ ...collection, updatedGridItem ]
// })
// if(this.getAnswer(e)) {
this.generateNewQuestion(questions[n])
// }
// else {
// console.log('no')
// }
}
generateNewQuestion(selectedQuestion) {
this.setState({
selectedQuestion
})
}
componentDidMount = () => {
const questions = this.state.questions.questions
let randomNumber = this.getRandomN(tempQuestions, questions.length, lastN)
this.generateNewQuestion(questions[randomNumber])
}
getAnswer = (e) =>
e.target.getAttribute('data-option') == this.state.selectedQuestion.correct_option
render() {
const state = this.state
const { collection, grid, intro, selectedQuestion } = state
console.log(tempQuestions)
return (
<div className="wrapper">
<div className="wrapper-inner">
<View isVisible={state.intro}>
<p> intro screen </p>
</View>
<View isVisible={state.grid}>
<Grid gridItemsCollection={collection}/>
<Question question={selectedQuestion.question} />
<Controls
onClick={this.hideGridItem.bind(this)}
gridItemsCollection={collection}
answers={selectedQuestion}
answer={selectedQuestion.correct_option}
/>
</View>
</div>
</div>
);
}
}
export default App;
It looks like you declare lastN but it is never actually declared for the first time. This means the first time you access it, it'll always be undefined.
You have two options to solve this:
Define lastN to some suitable default value (I would think something like -1 may be suitable based on the code presented).
let lastN = -1;
Just ignore it. From your code, it doesn't look like lastN being undefined should even be a problem since the only check you do is lastN == randomN, which will always be false if lastN is undefined.
It looks like it should get updated, but possibly not on the first call. It looks like it depends on how many questions you have. Unless you have 1 question, it won't update for a few tries. Also, if you have zero questions, it'll never update (since array.push() will be 1 and maxN will be 0).
When you use lastN as a parameter, the local variable lastN takes precedence over global lastN and you are actually updating the local variable, not the global. Just change your argument name.
const generateRandomNumber = (array, maxN, lastN) => {
let randomN = Math.floor(Math.random() * maxN) + 0;
console.log(lastN)
if(lastN == randomN) {
// do your thing
}
if(array.includes(randomN)) {
return generateRandomNumber(array, maxN, lastN);
}
if(array.push(randomN) == maxN) {
lastN = array.length - 1 //-> Here you update the local copy, the global is not affected. So rename you argument to... say lastNArg
array.length = 0;
}
return randomN
}
export default generateRandomNumber
And don't forget to initialize your global lastN var like: let lastN = 0;
So, here it is modified:
const generateRandomNumber = (array, maxN, lastNArg) => {
let randomN = Math.floor(Math.random() * maxN) + 0;
console.log(lastNArg)
if(lastNArg == randomN) {
// do your thing
}
if(array.includes(randomN)) {
return generateRandomNumber(array, maxN, lastNArg);
}
if(array.push(randomN) == maxN) {
lastN = array.length - 1; //make sure lastN is initialized and you have access to it
array.length = 0;
}
return randomN
}
export default generateRandomNumber

Categories