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.
Related
I am building a simple app to visually construct mazes to be solved using different search algorithms. I have a state maze that contains an array of arrays of the states of each block and I pass this to a child Grid which renders the maze from the array. However, whenever I update the state using the function updateMaze(y, x, newState) the maze state is update but the child Grid does not re-render. Why is that?
App.js:
import './App.css';
import Block from './components/Block'
import Row from './components/Row'
import Grid from './components/Grid'
import MazeView from './components/MazeView';
import SideBarItem from './components/SideBarItem';
import New from './assets/new-page.png'
import Checkmark from './assets/checkmark.png'
import Maximize from './assets/maximize.png'
import Random from './assets/random.png'
import Square from './assets/square-measument.png'
import { useState, useEffect } from 'react'
const useForceUpdate = () => {
const [count, setCount] = useState(0)
const increment = () => setCount(prevCount => prevCount + 1)
return [increment, count]
}
function App() {
const [forceUpdate] = useForceUpdate()
const [size, setSize] = useState({
width: 15,
height: 8
})
const defaultDimensions = 85
const [dimensions, setDimensions] = useState(defaultDimensions)
const [scale, setScale] = useState(1)
const [MazeViewStyle, setMazeViewStyle] = useState(String())
const [maze, setMaze] = useState([])
const [globalW, globalH] = [window.innerWidth * 0.9 - 35, window.innerHeight * 0.85]
const getAttrib = (columns, rows, defaultDimensions) => {
let scale = defaultDimensions
// If there are more columns than rows
if (columns >= rows) {
// Sets the scale to fill the height with rows
scale = globalH / (rows * defaultDimensions)
// Unless the columns do not fill the entire width of the screen
if (columns * defaultDimensions * scale < globalW) {
scale = globalW / (columns * defaultDimensions)
}
}
// In the opposite scenario (rows more than columns)
if (rows > columns) {
// Sets the scale to fill the width with columns
scale = globalW / (columns * defaultDimensions)
// Unless the rows do not fill the height
if (rows * defaultDimensions * scale < globalH) {
scale = globalH / (rows * defaultDimensions)
}
}
// Compute flags
const flags = {
centerWidth: columns * defaultDimensions < globalW,
centerHeight: rows * defaultDimensions < globalH
}
// Sets maximum result 1 and minimum 0
if (scale >= 1) return { scale: 1, flags: flags }
else if (scale <= 0.1) return { scale: 0.1, flags: {centerWidth: false, centerHeight: false} }
else return {scale: scale, flags: {centerWidth: false, centerHeight: false}}
}
const getMazeViewAuxStyle = (flags) => {
// Unpack a flag
let [centerWidth, centerHeight] = [flags.centerWidth, flags.centerHeight]
// If both flags are false return an empty string
if (!centerWidth && !centerHeight) { return String() }
// If the columns and rows are not sufficient
if (dimensions * size.width < globalW && dimensions * size.height < globalH) return "small smallw smallh"
// Otherwise find the necessary class names
let style = "small"
if (centerWidth) style = style + " smallw"
if (centerHeight) style = style + " smallh"
return style
}
const populateArea = () => {
// Fetch attributes of the current maze
const fetchedAttrib = getAttrib(size.width, size.height, defaultDimensions)
// Update the scale and dimensions
setScale(fetchedAttrib.scale)
setDimensions(defaultDimensions * fetchedAttrib.scale)
// Update flags
setMazeViewStyle(["maze-view", getMazeViewAuxStyle(fetchedAttrib.flags)].join(" "))
// Initialize maze space
initializeMazeSpace(size.height, size.width)
populateRandom()
// renderMaze()
}
// Populates the maze in the right dimensions
// only when a new maze is loaded
useEffect(() => {
populateArea()
}, [true])
// Updates the dimensions based on scale
useEffect (() => {
setDimensions(defaultDimensions * scale)
}, [scale])
const initializeMazeSpace = (rows, columns) => {
let newMaze = maze
for (let i = 0; i < rows; i++) {
newMaze[i] = []
for (let j = 0; j < columns; j++) {
newMaze[i][j] = "empty"
}
}
setMaze(newMaze)
}
const updateMaze = (i, j, blockState) => {
if (maze.length === 0) {
initializeMazeSpace(size.height, size.width)
}
setMaze(() =>
maze.map((row, a) =>
i === a ? (row.map((block, b) => b === j ? blockState : block)) : row
)
)
}
const populateRandom = (height = size.height, width = size.width) => {
let newMaze = maze
const classes = ["empty", "wall", "steel"]
for (let i = 0; i < height; i++) {
for (let j = 0; j < width; j++) {
let pick = classes[Math.floor(Math.random() * 3)]
newMaze[i][j] = pick
}
}
setMaze(newMaze)
}
const renderMaze = () => {
console.log(maze)
let grid = []
for (let i = 0; i < maze.length; i++) {
let row = []
for (let j = 0; j < size.width; j++) {
row.push(<Block inheritedType={maze[i][j]} dimensions={defaultDimensions * scale} />)
}
grid.push(<Row columns={row}/>)
}
return grid
}
// const renderMaze = () => {
// let mazeComponents = maze.map((row) => {
// return <Row columns={row.map(block => (<Block inheritedType={block} dimensions={defaultDimensions * scale} onAction={() => console.log("running")} onDoubleClick={(e, p) => e.target.classList.remove(p)}/>))}/>
// })
// return mazeComponents
// }
return (
<>
<div className='view'>
<MazeView style={MazeViewStyle} grid={<Grid rows={renderMaze()} />}/>
<div className='sidebar'>
<SideBarItem icon={New} onClick={() => {
updateMaze(0,0,"steel")
}}/>
<SideBarItem icon={Square} onClick={() => console.log(maze)}/>
<SideBarItem icon={Maximize} onClick={() => setScale(0.5)} />
<SideBarItem icon={Random} onClick={() => populateRandom()}/>
<SideBarItem icon={Checkmark} />
</div>
</div>
</>
);
}
export default App
Grid.js:
import React from 'react'
import Row from './Row'
import Block from './Block'
const Grid = ({ rows }) => {
return (
<div className='grid-view'>
{rows}
</div>
)
}
export default Grid
Note: setScale triggers a re-render.
I think your initializeMazeSpace function is the problem,
let newMaze = maze
referenced the same array. So your state is mutated then compared to itself, therefore the comparison's result is unchanged, and it didn't trigger the re-render action. If you want to copy a state, try this instead
let newMaze = [...maze]
My goal is to loop through an array of characters and end on each letter of a given word. My code is currently displaying all of these elements at once, but I want them to display sequentially. Here's what I currently have:
Current view
I'd like to return the array that ends with h(wait a few moments), array that ends with e (wait a few moments), and so on. I can't figure out to to attach the arrayIndex to the nested map though.
DisplayName.js
import React, { useState, useEffect } from "react";
const DisplayName = ({ characters, first }) => {
const [charIndex, setCharIndex] = useState(0);
const [arrayIndex, setArrayIndex] = useState(0);
let arrayContainer = [];
first.map((letter, i) => {
arrayContainer.push([]);
arrayContainer[i].push(characters.concat(first[i]));
return arrayContainer;
});
// I can't figure out how to attach arrayIndex here. I am
// also not using j currently, but kept it for now in case I need
// a key for the return statements.
const fullList = arrayContainer.map((letterArr, j) => {
return letterArr.map(char => {
return (char[charIndex])
})
});
useEffect(() => {
let timer;
let secondTimer;
if (charIndex < characters.length) {
timer = setTimeout(() => {
setCharIndex(charIndex + 1)
}, 75)
}
if (arrayIndex < first.length - 1) {
secondTimer = setTimeout(() => {
setArrayIndex(arrayIndex + 1)
}, 75)
}
return () => {
clearTimeout(timer);
clearTimeout(secondTimer);
};
}, [charIndex, characters, arrayIndex, first]);
return (
<div>{fullList}</div>
)
};
export default DisplayName;
App.js
import React from 'react';
import DisplayName from './DisplayName';
import './App.css';
function App() {
const first = 'hello'.split('');
const funChars = [
'⏀', '⎷', '⌮', '⋙', '⊠', '⎳', '⍼',
'⍣', '╈', '╳', '☀', '★', '☍', 'ↂ','▅'];
return (
<div className="glow" style={{ minHeight: '100vh'}}>
<span style={{ letterSpacing: 12}}><DisplayName first={first} characters={funChars}/></span>
</div>
);
}
export default App;
I've also tried something like const [rendered, setRendered] = useState(false); without success, which I tried attaching to the j key.
If I understand your question, you want to iterate over the first string up to an index and display a "rolling" fun character while iterating the string.
Intuitively I think it is easier to think of of slicing the front of the first string to an index, and appending the fun character.
iteration
index
text.substring(0, index)
result(s)
0
0
""
'⏀', '⎷', '⌮',...
1
1
"h"
'h⏀', 'h⎷', 'h⌮',...
2
2
"he"
'he⏀', 'he⎷', 'he⌮',...
3
3
"hel"
'hel⏀', 'hel⎷', 'hel⌮',...
4
4
"hell"
'hell⏀', 'hell⎷', 'hell⌮',...
5
5
"hello"
'hello'
The tricky issue is using two separate timers/intervals to increment the index for the first string and to increment an index into the fun characters array. Here is a solution I came up with.
Use a React ref to hold a interval timer reference for the rolling fun characters.
Single useEffect hook to start the "rolling" fun character index incrementing on an interval. Start a timeout on incrementing over the first string char array, if there is still length to iterate, enqueue another timeout, otherwise run clean up functions to clear timers and state.
Slice the first string up to index arrayIndex and conditionally append a "rolling" fun character.
Code:
const DisplayName = ({ characters, first }) => {
const charTimerRef = useRef(null);
const [charIndex, setCharIndex] = useState(null);
const [arrayIndex, setArrayIndex] = useState(0);
useEffect(() => {
let timerId;
const cleanupTimerRef = () => {
setCharIndex(null);
clearInterval(charTimerRef.current);
charTimerRef.current = null;
};
if (!charTimerRef.current) {
setCharIndex(0);
charTimerRef.current = setInterval(() => {
setCharIndex((i) => i + 1);
}, 75);
}
if (arrayIndex < first.length) {
timerId = setTimeout(() => {
setArrayIndex((i) => i + 1);
}, 1000);
} else {
cleanupTimerRef();
}
return () => {
clearTimeout(timerId);
cleanupTimerRef();
};
}, [arrayIndex, first]);
const fullList =
first.substring(0, arrayIndex) +
(charIndex ? characters[charIndex % characters.length] : "");
return <div>{fullList}</div>;
};
Demo
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
I am trying to use a for loop in order to run a function 10 times, which relies on the previous state to update. I know I am not supposed to setState in a loop since state changes are batched, so what are my options if I want to run the function 10 times with a single click handler? I am using hooks (useState), if that matters.
Below is my relevant code :
export default function Rolls(props) {
const [roll, setRoll] = useState(null)
const [tenPityCount, setTenPityCount] = useState(0)
const [ninetyPityCount, setNinetyPityCount] = useState(0)
// handler for single roll
const handleSingleRoll = () => {
// sets the main rolling RNG
const rng = Math.floor(Math.random() * 1000) +1
// pulls a random item from the data set for each star rating
const randomFiveStar = Math.floor(Math.random() * fiveStarData.length)
const randomFourStar = Math.floor(Math.random() * fourStarData.length)
const randomThreeStar = Math.floor(Math.random() * threeStarData.length)
// check if ten pity count has hit
if (tenPityCount === 9) {
setRoll(fourStarData[randomFourStar].name)
setTenPityCount(0)
return;
}
// check if 90 pity count has hit
if (ninetyPityCount === 89) {
setRoll(fiveStarData[randomFiveStar].name)
setNinetyPityCount(0)
return;
}
// check if rng hit 5 star which is 0.6%
if (rng <= 6) {
setRoll(fiveStarData[randomFiveStar].name)
setNinetyPityCount(0)
// check if rng hit 4 star which is 5.1%
} else if (rng <= 51) {
setRoll(fourStarData[randomFourStar].name)
setTenPityCount(0)
// only increment for 90 pity counter b/c 10 pity resets upon hitting 4 star
setNinetyPityCount(prevState => prevState +1)
// anything else is a 3 star
} else {
setRoll(threeStarData[randomThreeStar].name)
// pity counter increment for both
setTenPityCount(prevState => prevState + 1)
setNinetyPityCount(prevState => prevState +1)
}
}
const handleTenRoll = () => {
for (let i = 0; i < 10; i++) {
handleSingleRoll()
}
}
return (
<>
<div>
<span>
<button onClick={handleSingleRoll} className='btn btn-primary mr-2'>Wish x1</button>
<button onClick={handleTenRoll} className='btn btn-primary mr-2'>Wish x10</button>
<button className='btn btn-danger'>Reset</button>
</span>
</div>
</>
)
}
My suggestion would be to use a proxy functions:
const [roll, setRollState] = useState(null)
const [tenPityCount, setTenPityCountState] = useState(0)
const [ninetyPityCount, setNinetyPityCountState] = useState(0)
const newState = useRef({});
const consolidatedUpdates = useRef({setRollState, setTenPityCountState, setNinetyPityCountState})
function presetStateUpdate(name, state){
newState.current[name] = state
}
function pushPresetState(){
Object.entries(newState.current).forEach(([fnName, value]) => {
const fn = consolidatedUpdates[fnName];
if(typeof fn == 'function') fn(value);
});
newState.current = {};
}
const setRoll = v => presetStateUpdate('setRoll', v);
const setTenPityCount = v => presetStateUpdate('setTenPityCount', v);
const setNinetyPityCount = v => presetStateUpdate('setNinetyPityCount', v);
respectively:
const handleTenRoll = () => {
for (let i = 0; i < 10; i++) {
handleSingleRoll()
}
pushPresetState()
}
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.