I have a basic components with working pagination displaying props in a table.
This however shows all the page numbers which is not ideal I would like only a few to be seen.
e.g 1-2-3-4-5-20 so that it does not take up too much space.
This is the component itself.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
class AllAlerts extends Component {
state = {
currentPage: 1,
alertsPerPage: 8,
};
componentDidMount() {}
handleClick(number) {
this.setState({
currentPage: number,
});
}
render() {
// Logic for displaying alerts
const { currentPage, alertsPerPage } = this.state;
const indexOfLastAlerts = currentPage * alertsPerPage;
const indexOfFirstAlerts = indexOfLastAlerts - alertsPerPage;
const currentAlerts = this.props.alerts.slice(indexOfFirstAlerts, indexOfLastAlerts);
const renderAlerts = currentAlerts.map((alerts, index) => {
return (
<li key={index}>
<div>
All Title {alerts.title}
<li>All ID {alerts.id}</li>
<li>All user Id {alerts.userId}</li>
</div>
</li>
);
});
// Logic for displaying page numbers
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(this.props.alerts.length / alertsPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map((number) => {
return (
<button key={number} id={number} onClick={() => this.handleClick(number)}>
{number}
</button>
);
});
return (
<>
<>All Alerts</>
<br />
<form class="example" action="/action_page.php">
<input type="text" placeholder="Search.." name="search" />
<button type="submit">
<i class="fa fa-search"></i>
</button>
</form>
<>{renderAlerts}</>
<>{renderPageNumbers}</>
</>
);
}
}
export default AllAlerts;
From my understanding I would change this in this part of the code.
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(this.props.alerts.length / alertsPerPage); i++) {
pageNumbers.push(i);
//Only push 1-5 and last which has to change depending on page number
}
I have tried a few different approaches all not achieving what I would like.
Thank you for your help :)
Just check if the index i of the loop fits your condition
const pageNumbers = [];
const numPages = Math.ceil(this.props.alerts.length / alertsPerPage);
for (let i = 1; i <= numPages; i++) {
//Only push 1-5 and last which has to change depending on page number
if (i <= 5 || i == numPages)
pageNumbers.push(i);
}
But probably you should show the currentPage and the one before and after to, otherwise you won't be able to jump to the next or previous page
const pageNumbers = [];
const numPages = Math.ceil(this.props.alerts.length / alertsPerPage);
for (let i = 1; i <= numPages; i++) {
if (i <= 5 || //the first five pages
i == numPages || //the last page
Math.abs(currentPage - i) <= 1 //the current page and the one before and after
)
pageNumbers.push(i);
}
The best way would be to loop over page numbers, using the counter that react map method gives you can check how many buttons you have created and when then are more than five you stop creating buttons
{
pageNumbers.map((number,count)=>
count <5?
<button
key={number}
id={number}
onClick={() =>this.handleClick(number)}
>
{number}
</button>:
null
)
}
{pageNumbers.length >5? //next button here : null }
You then can go ahead and check the length of number of pages if creater than button you created you add next button
You shoulld update current page to state then check the current page if it is the first element in yhe page numbers else add previous button
Related
I have filtered out procedures that I am interested on, and now want to display their average patient_cost. I have the calculation working, but am unsure how to display it in the screen for users to see it. This is what I have:
import React, { useState, useEffect, useMemo } from "react";
function Average ({posts, setUser, user}){
const [procedures, setProcedures] = useState ([])
const [filteredProcedure, setFilteredProcedure] = useState ("")
const [averageCost, setAverageCost] = useState (0)
const uniques = procedures.map(procedure => procedure.procedure)
.filter((value, index, self) => self.indexOf(value) === index )
useEffect(() => {
fetch(`/posts/${procedures}`)
.then((r) => r.json())
.then(setProcedures);
}, [posts]);
function handleProcedureChange(procedure) {
setFilteredProcedure(procedure);
let filteredObject = procedures.filter (p=> p.procedure === procedure)
console.log("lenght of filtered procedure:", filteredObject)
let total = 0;
for (let ii = 0; ii < filteredObject.length; ii++) {
total = total + filteredObject[ii].patient_cost
}
console.log("total:", total)
let avg = total / filteredObject.length
console.log("average:", avg)
}
function handleChange(e) {
handleProcedureChange(e.target.value);
console.log(e.target.value)
}
return (
<div >
<div >
<label htmlFor="procedure">Procedure:</label>
<select
id="procedure"
name="procedure"
value={filteredProcedure}
onChange={handleChange} >
<option>Select Procedure</option>
{uniques.map((procedure) => (
<option key={procedure} value={procedure}>
{procedure}
</option>
))}
</select>
</div>
average: {averageCost}
</div>
)
}
export default Average;
How can I tie the "let avg = total / filteredObject.length" to the state "average: {averageCost}" at the end? I'm new at this and do not know if there is a better way to write this code.
Appreciate the help here.
I'm creating a grid of client logos using Tailwind, react, and GSAP. Client logo paths are loaded in via a json file. The client logo list is pretty long, so I thought it would be interesting to have the images in each grid col-spans ('cells') fade to a different image every few seconds.
My solution thus far is to map through all the logos and stack a certain number of them on top of each other as absolute before moving onto the next col span and then animate them in and out using the ids of the col-spans. I'm having trouble wrapping my mind around the approach. Especially with responsibly changing the grid-cols.
The approach so far (some pseudo-code some irl code):
const maxCols = 4
const maxRows = 3
const itemsPerRow = Math.floor( logosList.length()/maxCols/maxRows)
const isExtra = () =>{
if(logosList.length() % itemsPerRow >0) return true
else return false
}
const numRows = Math.floor( logosList.length()/maxCols )
export default function ClientImages(){
useEffect(() => {
for(i = 0; i <= i/maxCols/maxRows; i++ )
// to be figured out gsap method
gsap.to(`img-${i}`, {animate stuff})
},);
function setLogos(){
let subset
for ( index = 0; index == maxCols * maxRows; index++ ){
if(isExtra){
subset = logosList.slice(index, itemsPerRow + 1)
}
else subset = logosList.slice(index, itemsPerRow)
return(
<div className="col-span-1 relative" id={`clientColSpan-${index}`}>
{subset.map((logo) => {
<Image className='absolute' src={logo.src} yada yada yada />
})}
</div>
)
}
}
return(
<div className="grid grid-cols-2 md:grid-cols-4 2xl:grid-cols-6">
{setLogos}
</div>
)
}
Here's a visual representation of my thought process
Here's my solution based on your visual representation.
Create the grid
As we need two different grids for desktop and mobile, it's better to have a function that does this job specifically.
// spread images to grid
const createGrid = (images, col, row) => {
const totalItems = images.length;
const totalCells = col * row;
const itemsInCell = Math.floor(totalItems / totalCells);
let moduloItems = totalItems % totalCells;
// create grid
const grid = [];
for (let i = 0; i < totalCells; i++) {
let cell = [];
for (let j = 0; j < itemsInCell; j++) {
const index = i * itemsInCell + j;
cell.push(images[index]);
}
grid.push(cell);
}
// handle modulo items
while (moduloItems > 0) {
grid[totalCells - 1].push(images[totalItems - moduloItems]);
moduloItems--;
}
return grid;
};
const images = [1,2,3,4,...,37];
cosnt grid = createGrid(images, 2, 3); // [ [1,2,3,4,5,6], [7,8,9,10,11,12], ... [] ]
The createGrid() will return an array of grid cells, each cell will contain a number of items that meet your expectation. With this grid cells array, you have enough data to create your HTML.
Handle the responsive grid
With the provided grid array we can create responsive HTML grid layouts based on the window's width.
const createHTMLFromGrid = gridArray =>{// your function for HTML};
let html =
window.innerWidth > 1024
? createHTMLFromGrid(createGrid(images, 2, 3))
: createHTMLFromGrid(createGrid(images, 4, 3));
// append the HTML to your DOM
$(html).appendTo("body");
You can also change the grid based on the window resize event. Once you've got the HTML ready, you can play with the GSAP animation.
See CodePen
Hi i have been building a sorting algorithms visualization it works so far but i have a doubt regarding state object.
consider the below code:
import React,{Component} from 'react';
import getMergeSortAnimations from './Person/Person';
import bubbleSortAnimations from './Person/BubbleSort';
import './App.css';
class App extends Component{
state = {
array: [],
bar_width:2
};
componentDidMount() {
this.generateArray();
}
generateArray = ()=> {
const array = [];
let val = document.querySelector('#size').value;
if(val<=10)
{
this.setState({bar_width:8});
}
else if(val<=20 && val>10)
this.setState({bar_width:7});
else if(val<=50 && val>20)
this.setState({bar_width:6});
else if(val<=100 && val>50)
this.setState({bar_width:5});
else if(val<=150 && val>100)
this.setState({bar_width:3});
else
this.setState({bar_width:2});
for (let i = 0; i < val; i++) {
array.push(this.randomIntFromInterval(5, 450));
}
this.setState({array});
}
randomIntFromInterval = (min, max)=> {
return Math.floor(Math.random() * (max - min + 1) + min);
}
mergeSort = ()=>{
let t ;
console.log(this.state);
const animations = getMergeSortAnimations(this.state.array);
console.log(this.state);
for (let i = 0; i < animations.length; i++) {
const arrayBars = document.getElementsByClassName('element');
const isColorChange = i % 3 !== 2;
if (isColorChange) {
const [barOneIdx, barTwoIdx] = animations[i];
const barOneStyle = arrayBars[barOneIdx].style;
const barTwoStyle = arrayBars[barTwoIdx].style;
const color = i % 3 === 0 ? 'red' : '#007bff';
setTimeout(() => {
barOneStyle.backgroundColor = color;
barTwoStyle.backgroundColor = color;
}, i*10);
} else {
setTimeout(() => {
const [barOneIdx, newHeight] = animations[i];
const barOneStyle = arrayBars[barOneIdx].style;
barOneStyle.height = `${newHeight}px`;
}, i*10);
}
}
}
render() {
return (
<div>
<header>
<input className="slider" onChange={this.generateArray} type="range" min="5" max="200"
id='size'/>
<nav>
<ul>
<li><button onClick={this.generateArray} id="new" >New array</button></li>
<li><button onClick={this.mergeSort} id="mergesort" >Merge Sort</button></li>
<li><button onClick={this.bubbleSort} id="bubbleSort" >Bubble sort</button></li>
</ul>
</nav>
</header>
<div className="container">
<br></br>
{this.state.array.map((value, idx) => (
<div
className="element"
key={idx}
style={{
width:`${this.state.bar_width}px`,
height:`${value}px`
}}></div>
))}
</div>
</div>
);
}
}
Merge sort Code:
export default function getMergeSortAnimations(array) {
const animations = [];
mergeSort(array, 0, array.length - 1,animations);
return animations;
}
function mergeSort(array,low, high,animations) {
if(low<high)
{
const mid = Math.floor((low + high) / 2);
mergeSort(array, low, mid,animations);
mergeSort(array, mid + 1, high,animations);
merge(array, low, high,animations);
}
}
function merge(array,low,high,animations) {
let a = [];
let k = low;
let i = low;
let mid = Math.floor((low+high)/2);
let j = mid + 1;
while (i <= mid && j <= high) {
animations.push([i, j]);
animations.push([i, j]);
if (array[i] <= array[j]) {
animations.push([k, array[i]]);
a[k++] = array[i++];
} else {
animations.push([k, array[j]]);
a[k++] = array[j++];
}
}
while (i <= mid) {
animations.push([i, i]);
animations.push([i, i]);
animations.push([k, array[i]]);
a[k++] = array[i++];
}
while (j <= high) {
animations.push([j, j]);
animations.push([j, j]);
animations.push([k, array[j]]);
a[k++] = array[j++];
}
for(let o=low;o<k;o++)
{
array[o] = a[o];
}
}
The merge sort function is under src->Person->Person as mentioned in the import section,it just returns animations array which i use for visualization.
Now the generateArray function generates an array and sets it to state using setSate() Method.When this is done the user can select mergesort and the code runs.But as you can see getMergesortAnimations() returns the animations array after the actual mergesort happens.But the question is:
"""When i console the state array before calling getMergesortAnimations() it displays a sorted array.It happens even before the mergesort is called and how is the state set to the sorted array without acutally using setState method?"""
This is very confusing to me ....
Thanks.
// From Generate random number between two numbers in JavaScript
export default App;
From a quick look at your code I can see that at the very end of your mergeSort function you are doing:
for(let o=low;o<k;o++)
{
array[o] = a[o];
}
Which is modifying the array in place. So after you in your component call:
const animations = getMergeSortAnimations(this.state.array);
this.state.array will be modified in place. This is something you should not do in React, from React docs:
Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
In order to fix this just fix the last couple lines of your mergeSort function so that it does not assign to array but rather creates a new array and returns that.
I'm coding a sorting visualizer in ReactJS, and I use a state to hold the delay between each render.
When I change the slider of the delay, the sorting does not update.
I made it log the updated value, and in each loop I made it log the value it reads.
for some reason, when I read the getDelay inside the loop, and outside of it, they are different.
Here is the code:
import React, { useState, useEffect } from "react";
import "./SortingVisualizer.css";
class Bar {
constructor(value, className) {
this.value = value;
this.className = className;
}
}
const SortingVisualizer = () => {
const [getArray, setArray] = useState([Bar]); //array to hold the bars
const [getSlider, setSlider] = useState(50);
const [getDelay, setDelay] = useState(2);
//reset the array at the start
useEffect(() => {
resetArray(10);
}, []);
//function to reset the array
const resetArray = () => {
const array = [];
for (let i = 0; i < getSlider; i++) {
array.push(new Bar(randomInt(20, 800), "array-bar"));
}
setArray(array);
};
//a delay function. use like this: `await timer(time to wait)`
const timer = delay => {
return new Promise(resolve => setTimeout(resolve, delay));
};
//function to do buuble sort with given delay between each comparison
const bubbleSort = async () => {
let temp,
array = Object.assign([], getArray); // defining a temporary variable, and a duplicate array the the bars array
//looping from the array size to zero, in cycles
for (let i = array.length; i > 0; i--) {
//looping from the start of the section from the first loop to the end of it.
for (let j = 0; j < i - 1; j++) {
//changing the colors of the compared bares
array[j].className = "array-bar compared-bar";
array[j + 1].className = "array-bar compared-bar";
if (getDelay > 0) await timer(getDelay / 2);
setArray([...array]);
//comparing and switching if needed
if (array[j].value > array[j + 1].value) {
temp = array[j].value;
array[j].value = array[j + 1].value;
array[j + 1].value = temp;
setArray([...array]);
}
//updating the array and moving to the next pair
if (getDelay > 0) await timer(getDelay / 2);
array[j].className = "array-bar";
array[j + 1].className = "array-bar";
// Wait delay amount in ms before continuing, give browser time to render last update
}
array[i - 1].className = "array-bar completed-bar";
}
setArray([...array]);
console.log("done.");
};
const combSort = async () => {
let temp,
swapped,
array = Object.assign([], getArray); // defining a temporary variable, and a duplicate array the the bars array
//looping from the array size to zero, in cycles
for (let i = array.length; i > 0; i = Math.floor(i / 1.3)) {
//looping from the start of the section from the first loop to the end of it.
swapped = false;
for (let j = 0; j < array.length - i; j++) {
//changing the colors of the compared bares
array[j].className = "array-bar compared-bar";
array[j + i].className = "array-bar compared-bar";
setArray([...array]);
await timer(getDelay / 2);
//comparing and switching if needed
if (array[j].value > array[j + i].value) {
temp = array[j].value;
array[j].value = array[j + i].value;
array[j + i].value = temp;
setArray([...array]);
swapped = true;
await timer(getDelay / 2);
}
//updating the array and moving to the next pair
array[j].className = "array-bar";
array[j + i].className = "array-bar";
// Wait delay amount in ms before continuing, give browser time to render last update
console.log(getDelay);
}
//array[i - 1].className = "array-bar completed-bar";
if (i === 1 && swapped) i = 2;
}
setArray([...array]);
};
const sliderUpdate = e => {
setSlider(e.target.value);
resetArray(getSlider);
};
const delayUpdate = e => {
setDelay(e.target.value * 1);
console.log(getDelay);
};
return (
<>
<div className="menu">
<button onClick={() => resetArray()}>Geneate new array</button>
<button onClick={() => bubbleSort()}>Do bubble sort</button>
<button onClick={() => combSort()}>Do comb sort</button>
</div>
<div class="slide-container">
<input
type="range"
min="3"
max="250"
value={getSlider}
class="slider"
id="sizeSlider"
onChange={sliderUpdate}
/>
<input
type="range"
min="0"
max="1000"
value={getDelay}
class="slider"
id="delaySlider"
onChange={delayUpdate}
/>
</div>
<div className="array-container">
{getArray.map((bar, i) => (
<div
className={getArray[i].className}
key={i}
style={{ height: `${bar.value * 0.1}vh` }}
></div>
))}
</div>
</>
);
};
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
export default SortingVisualizer;
I don't know what the best solution is, but a solution would be to use useRef.
The problem is related to Why am I seeing stale props or state inside my function? : On each render you are creating new functions for bubbleSort and combSort. Those functions use the value of getDelay that existed at the moment those functions have been created. When one of the buttons is clicked the "version" of the function of the last render will be executed, so the value of getDelay that existed then and there will be used.
Now, changing the slider will cause a rerender, and thus new versions of bubbleSort and combSort are created ... but those are not the versions that are currently running!
useRef solves that problem because instead of directly referring to the delay, we are referring to an object whose current property stores the delay. The object doesn't change, but the current property does and every time it's accessed we get the current value. I highly encourage you to read the documentation.
After your state variables, add
const delayRef = useRef(getDelay);
delayRef.current = getDelay
The second line keeps the ref in sync with the state.
Everywhere else where you reference getDelay, except value of the slider itself, use delayRef.current instead. For example:
if (delayRef.current > 0) await timer(delayRef.current / 2);
Demo (couldn't get it to work on SO): https://jsfiddle.net/wuf496on/
I'm trying to implement Minesweeper in React and whenever the player clicks on a mine, the board is reset and re-rendered, but the cell that the player initially clicked containing the mine appears to fire onClick again after the board resets.
I've noticed additionally that if I don't reset the board after hitting a mine, but instead call alert() and then return without changing state, then game loops until a stack overflow occurs.
This is how my stateful board component looks when I display an alert after game over and do not change state:
render() {
let squareGrid = this.state.currentGrid.slice();
return (
squareGrid.map((row, y) => { //For each row
return ( //Create a division
<div key={y}>
{
row.map((state, x) => {//Render a square for each index
let value = (state.touched) ? state.minedNeighbors :"_";
return <Square mine={squareGrid[y][x].mine} key={x} disabled={state.touched} val={value}
onClick={() => this.handleClick(y, x)}> </Square>
})}
</div>
)
}
)
)
}
handleClick(row, column) {
// Get copy of grid
const grid = this.state.currentGrid.slice();
//If the player clicks a mine, game over.
if (grid[row][column].mine) {
//this.resetGame(); //This function does cause a state change
alert("You have died.");
return;
}
//Non-pure function that mutates grid
this.revealNeighbors(row, column, grid);
this.setState({
currentGrid: grid
})
}
My Square component is a function
function Square(props) {
return (
<button className={"gameButton"} disabled={props.disabled} onClick={props.onClick}>
{props.val}
</button>
);
}
The code, as is, will repeatedly display an alert over and over again once the player clicks a mine.
If I uncomment the line in handleClick that resets the game, the board will be correctly reset, but the cell that the player last clicked will be revealed as if the player had clicked it again after the board reset.
A lot of the other posts that have had my issue are due to the onClick attribute containing a function call instead of a function pointer, but as far as I can tell, I'm not calling the function directly in render; I'm providing a closure.
Edit:
Here is the full code for my Board component.
class Board extends React.Component {
constructor(props) {
super(props);
let grid = this.createGrid(_size);
this.state = {
size: _size,
currentGrid: grid,
reset: false
}
}
createGrid(size) {
const grid = Array(size).fill(null);
//Fill grid with cell objects
for (let row = 0; row < size; row++) {
grid[row] = Array(size).fill(null);
for (let column = 0; column < size; column++) {
grid[row][column] = {touched: false, mine: Math.random() < 0.2}
}
}
//Reiterate to determine how many mineNeighbors each cell has
for (let r = 0; r < size; r++) {
for (let c = 0; c < size; c++) {
grid[r][c].minedNeighbors = this.countMineNeighbors(r, c, grid)
}
}
return grid;
}
handleClick(row, column) {
const grid = this.state.currentGrid.slice();
//If the player clicks a mine, game over.
if (grid[row][column].mine) {
//this.resetGame();
//grid[row][column].touched = true;
alert("You have died.");
return;
}
//Non-pure function that mutates grid
this.revealNeighbors(row, column, grid);
this.setState({
currentGrid: grid
})
}
//Ensure cell is in bounds
checkBoundary(row, column) {
return ([row, column].every(x => 0 <= x && x < this.state.size));
}
revealNeighbors(row, column, grid) {
//Return if out of bounds or already touched
if (!this.checkBoundary(row, column) || grid[row][column].touched) {
return;
}
//Touch cell
grid[row][column].touched = true;
if (grid[row][column].minedNeighbors === 0) {
//For each possible neighbor, recurse.
[[1, 0], [-1, 0], [0, 1], [0, -1]]
.forEach(pos => this.revealNeighbors(row + pos[0], column + pos[1], grid));
}
}
countMineNeighbors(row, column, grid) {
let size = grid.length;
//Returns a coordinate pair representing the position of the cell in the direction of the angle, eg, Pi/4 radians -> [1,1]
let angleToCell = (angle) => [Math.sin, Math.cos]
.map(func => Math.round(func(angle)))
.map((val, ind) => val + [row, column][ind]);
return Array(8)
.fill(0)
.map((_, ind) => ind * Math.PI / 4) //Populate array with angles toward each neighbor
.map(angleToCell)
.filter(pos => pos.every(x => 0 <= x && x < size))//Remove out of bounds cells
.filter(pos => grid[pos[0]][pos[1]].mine)//Remove cells that aren't mines
.length //Return the length of the array as the count
}
resetGame() {
this.setState({
currentGrid: this.createGrid(this.state.size)
}
)
}
render() {
let squareGrid = this.state.currentGrid.slice();
return (
squareGrid.map((row, y) => { //For each rows
return ( //Create a division
<div key={y}>
{
row.map((state, x) => {//Render a square for each index
let value = (state.touched) ? state.minedNeighbors : "_";
return <Square mine={squareGrid[y][x].mine} key={x} disabled={state.touched} val={value}
onClick={() => this.handleClick(y, x)}/>
})}
</div>
)
}
)
)
}
}
All the time when you change your state your render method is called.
this.setState({
currentGrid: grid
})
probably you should implement a method called shouldComponentUpdate to prevent this to happen. Also, your slice is not being resolved. I'd suggest you to try with async/await.
I've had a problem like this if you click on a element that becomes rerendered. I'm not sure if this will solve the problem in your particular situation but I've found 2 solutions that have worked for me in the past.
One is to put a flag in your mouse click event,
if(!mouseDownFlag){
mouseDownFlag = true;
//the rest of your onetime code
}
and then have the flag removed on the mouseupevent
Alternatively, sometimes using the mousedown event instead of mouseclick, can be more predictable.
Hopefully one of these solutions help.
You need to change the way you pass and use the click function (when passing a function as a prop you only want to pass a reference to the function not call it, hence excluding the ())
return
<Square
mine={squareGrid[y][x].mine}
key={x} disabled={state.touched}
val={value}
// **** change line below
onClick={this.handleClick}
> </Square>
And when you call it
<button
className={"gameButton"}
disabled={props.disabled}
// **** change line below
onClick={() => props.onClick()}>
{props.val}
</button>