I have a very basic usage of the React Virtualized MultiGrid component where I simply render a table of numbers going from 1 to 100.
For some reason tho, the first row will not get rendered. In other words, the table always starts at the number 2.
Here is my code.
const Container = {
width: "90%",
height: "100vh",
margin: "auto",
};
class App extends Component {
state = {
data: [],
sort: {
column: "",
descending: false,
},
};
componentDidMount() {
const numbers = [];
for (let i = 0; i < 100; i++) {
numbers.push(i + 1);
}
const final = numbers.map(n => {
return {
single: n,
double: n * 2,
triple: n * 3
};
});
this.setState({ data: final });
}
cellRenderer = (data, columns) => {
if (data.rowIndex === 0) {
return (
<span
style={data.style}
key={data.key}
>
{columns[data.columnIndex]}
</span>
);
}
const column = columns[data.columnIndex];
return (
<span
style={data.style}
key={data.key}
>
{this.state.data[data.rowIndex][column]}
</span>
);
}
render() {
const columns = ["single", "double", "triple"];
return (
<div style={Container}>
<AutoSizer>
{({ width, height }) => (
<MultiGrid
columnWidth={70}
width={width}
height={height}
cellRenderer={(data) => this.cellRenderer(data, columns)}
fixedRowCount={1}
rowHeight={70}
columnCount={3}
rowCount={this.state.data.length}
/>
)}
</AutoSizer>
</div>
);
}
}
And here is a screenshot of my output.
Thanks to Doron's comment, I got it working.
Here is the code with the relevant changes.
const Container = {
width: "90%",
height: "100vh",
margin: "auto",
};
class App extends Component {
state = {
data: [],
sort: {
column: "",
descending: false,
},
};
componentDidMount() {
const numbers = [];
for (let i = 0; i < 100; i++) {
numbers.push(i + 1);
}
const final = numbers.map(n => {
return {
single: n,
double: n * 2,
triple: n * 3
};
});
this.setState({ data: final });
}
cellRenderer = (data, columns) => {
if (data.rowIndex === 0) {
return (
<span
style={data.style}
key={data.key}
>
{columns[data.columnIndex]}
</span>
);
}
const column = columns[data.columnIndex];
return (
<span
style={data.style}
key={data.key}
>
{this.state.data[data.rowIndex - 1][column]}
</span>
);
}
render() {
const columns = ["single", "double", "triple"];
return (
<div style={Container}>
<AutoSizer>
{({ width, height }) => (
<MultiGrid
columnWidth={70}
width={width}
height={height}
cellRenderer={(data) => this.cellRenderer(data, columns)}
fixedRowCount={1}
rowHeight={70}
columnCount={3}
rowCount={this.state.data.length + 1}
/>
)}
</AutoSizer>
</div>
);
}
}
Notice that now the total row count is actually 1 more than the length of the data array, and then when indexing into the array in my cellRenderer, I index by 1 less than the current index.
Related
I am trying to build a sorting Visualizer using React. Right now, I am implementing BubbleSort. The program looks like this:
Here's the code:
class Sorter extends Component {
state = {
array: [100,4,214,55,11,22,10,33],
color: "blueviolet",
}
bubblesorter = async () => {
let arr = this.state.array
var len = arr.length,
i, j, stop;
for (i=0; i < len; i++){
for (j=0, stop=len-i; j < stop; j++){
if (arr[j] > arr[j+1]){
swap(arr, j, j+1);
}
await new Promise(resolve => setTimeout(resolve, 100));
this.setState({array:arr})
}
}
}
render() {
const array = this.state.array
return (
<div>
<h1>This is a sorter</h1>
<div className="container">
{array.map((value, id) => (
<span>
<div className="bar" key={id} style={{height: value+"px", backgroundColor: this.state.color}} >
</div>
</span>
))}
</div>
<button onClick={this.bubblesorter}>Sort</button>
</div>
)
}
The sorting functionality works correctly. But I would like to change the color of bars (array elements being compared). Can someone help with the Logic which can be used to implement this...Thanks
You can make the colors array and update the color which was only changed.
class Sorter extends Component {
state = {
array: [100, 4, 214, 55, 11, 22, 10, 33],
colors: Array(8).fill('blueviolet'), // Array of colors for each bar
};
bubblesorter = async () => {
...
await new Promise((resolve) => setTimeout(resolve, 100));
// Set different item's color as `red`
const colors = arr.map((item, index) => (this.state.array[index] === item ? 'blueviolet' : 'red'));
this.setState({ array: arr, colors });
}
}
};
render() {
const { array, colors } = this.state;
return (
<div>
<h1>This is a sorter</h1>
<div className='container'>
{array.map((value, id) => (
<span>
<div
className='bar'
key={id}
// <- Use the color at the same index of the item
style={{ height: value + 'px', backgroundColor: colors[id] }}
></div>
</span>
))}
</div>
<button onClick={this.bubblesorter}>Sort</button>
</div>
);
}
}
Tried this...it works but can be improved using modern javascript (not that familiar with it, suggestions welcome)
class Sorter extends Component {
state = {
array: [],
colors: Array(8).fill('blueviolet'),
}
bubblesorter = async () => {
...
await new Promise(resolve => setTimeout(resolve, 1000));
const colors = []
for (let k=0; k<8; k++) {
let color = k==j || k==j+1 ? 'red':'blueviolet'
colors[k] = color
}
// console.log(colors)
this.setState({ array: arr, colors: colors });
}
}
render()
{
// const array = this.state.array
const { array, colors } = this.state;
// console.log(array)
return (
<div>
<h1>This is a sorter</h1>
<div className="container">
{array.map((value, id) => (
<span>
<div
className="bar"
key={id}
style={{ height: value + 'px', backgroundColor: colors[id]
}} >
</div>
{/* <span>{value}</span> */}
</span>
))}
</div>
<button onClick={this.bubblesorter}>Sort</button>
</div>
)
}
}
I tried to get a grid list of video files saved on my device as video gallery in my react native app, but I think there's something am not doing right, I used react-native-fs to fetch the files from my custom folder I created. I get a typeError message.
state = {
index: null
}
componentWillMount() {
RNFS.readDir(RNFS.ExternalStorageDirectoryPath + "CustomFolder Videos")
.then((result) => {
console.log('GOT RESULT', result);
return Promise.all([RNFS.stat(result[0].path), result[0].path]);
})
.then((statResult) => {
let videos = [];
var allowedExtensions = /(\.avi|\.mp4|\.mov|\.wmv|\.avi)$/i;
statResult.forEach(item => {
if (item.isFile() && !allowedExtensions.exec(item.originalFilepath)) {
videos.push(item);
}
});
console.log(videos)
})
}
setIndex = (index) => {
if (index === this.state.index) {
index = null;
}
this.setState({
index
})
}
render() {
return ( <
View style = {
styles.container
} >
<
ScrollView contentContainerStyle = {
styles.scrollview
} {
...this.state.videos.map((p, i) => {
const isSelected = i === this.state.index;
const divide = isSelected && this.share === true ? 1 : 3;
return ( <
Video source = {
{
uri: videos
}
}
style = {
{
opacity: i === this.state.index ? 0.5 : 1,
width: width / divide,
height: width / divide
}
}
key = {
i
}
underlayColor = 'transparent'
onPress = {
() => this.setIndex(i)
}
ref = {
ref => {
this.player = ref;
}
} // Store reference
onError = {
this.videoError
} // Callback when video cannot be loaded
/>
)
})
} >
<
/ScrollView> <
/View>
);
}
}
change you render methods like below, that will works,
state = {
index: null,
videos:[]
}
render() {
return (
<View style={styles.container}>
<ScrollView
contentContainerStyle = {styles.scrollview}
{
this.state.videos&& this.state.videos.length>0 && this.state.videos.map((p, i) => {
const isSelected = i === this.state.index;
const divide = isSelected && this.share === true ? 1 : 3;
return(
<Video
source={{uri: videos}}
style={{opacity: i === this.state.index ? 0.5 : 1, width: width/divide, height: width/divide}}
key={i}
underlayColor='transparent'
onPress={() => this.setIndex(i)}
ref={ref => {
this.player = ref;
}} // Store reference
onError={this.videoError} // Callback when video cannot be loaded
/>
)
})
}
>
</ScrollView>
</View>
);
}
}
the point is that this.state.videos is empty till the api response will get
videos is not defined in the state. You need to initialise state first and then setstate to update the values.
I am updating properties of a state element inside of map
computePiePercentages(){
var denominator = 1600
if (this.state.total < 1600){
denominator = this.state.total
}
return this.state.pieChartData.map((item, index) => {
return {
...item,
y: Number((item.y / denominator).toFixed(1)) * 100
}
})
}
However, when I display the chart using pieChartData - y hasn't updated. I checked my math to make sure I am setting to the correct value inside computePiePercentages
Is the map function asynchronous like setState? How can I make sure to wait for the update to happen before I display my results?
Here is the rest of relevant code:
class Budget extends React.Component {
computePiePercentages(){
var denominator = 1600
if (this.state.total < 1600){
denominator = this.state.total
}
return this.state.pieChartData.map((item, index) => {
return {
...item,
y: Number((item.y / denominator).toFixed(1)) * 100
}
})
}
computeTotals(){
var runningTotal = 0
var pieArray = []
var beyondBudget = {}
Object.entries(this.state.data.selectedQuestions).map((element, j) => {
console.log("hi here")
console.log(element)
//return selectedQuestions.map((val, j) => {
const value = Object.values(element[1])[0]
const name = element[0]
runningTotal += value
if(runningTotal <= 1600){
let pieSlice =
{
x: name,
y: value
};
pieArray = pieArray.concat(pieSlice)
}
else {
if (Object.keys(beyondBudget).length == 0) {
beyondBudget[name] = {};
beyondBudget[name] = runningTotal - 1600;
let pieSlice =
{
x: name,
y: value - (beyondBudget[name])
};
pieArray = pieArray.concat(pieSlice)
}
if (!beyondBudget[name]) {
beyondBudget[name] = {};
}
if (Object.keys(beyondBudget).length > 1) {
beyondBudget[name] = value;
}
}
});
this.setState({
pieChartData: pieArray,
total: runningTotal,
beyondBudget: beyondBudget,
}, () => {
this.computePiePercentages();
});
}
render() {
const {
data,
pieChartData,
beyondBudget,
showResults,
total
} = this.state;
const questions = data.questions;
return (
<div>
{questions.map((q, i) => (
<UL key={i}>
<li>
<h4>{q.text}</h4>
</li>
<li>
<Options
state={this.state}
q={q}
i={i}
handler={this.handleInputChange}
/>
</li>
</UL>
))}
<button onClick={(event) => {
this.computeTotals();
this._showResults(true);
}}>Click</button>
{console.log(this.state.showResults)}
{this.state.showResults &&
(<div>
<VictoryPie
colorScale="blue"
data={pieChartData}
labels={d => `${d.x}: ${d.y}%`}
style={{ parent: { maxWidth: '50%' } }}
/>
{Object.keys(beyondBudget).length > 0 && (
<div>
<Table>
<tbody>
<tr>
<th>Out of Budget</th>
</tr>
<BrokeBudget
beyondBudget={beyondBudget}
/>
</tbody>
</Table>
</div>
)}
</div>
)
}
</div>
);
}
}
As others have mentioned, an array's .map() function returns a new array without changing the old array. So this.computePiePercentages() as it currently stands, creates a new array based on this.state.pieChartData and returns that new array. This action does not change this.state.pieChartData.
You're calling this.computePiePercentages() from the callback function of this.setState(). This is just a function, it has no special properties other than that it's called when the setState() is done changing the state. So to update the state further inside computePiePercentages() you need to call setState() again.
There are two options:
Update the state in the callback function, using the return value of this.computePiePercentages:
this.setState({
pieChartData: pieArray,
total: runningTotal,
beyondBudget: beyondBudget,
}, () => {
this.setState({
pieChartData: this.computePiePercentages()
});
});
Update the state in this.computePiePercentages:
this.setState({
pieChartData: this.state.pieChartData.map((item, index) => {
return {
...item,
y: Number((item.y / denominator).toFixed(1)) * 100
}
})
});
I am trying to create a div for each item in an array that is a property of this.state
However, I am getting Cannot read property 'map' of undefined on the line, return outOfBudget.values.map((val, j)
Most of the posts on this subject have an issue because the data doesn't actually exist. I tried their solutions by wrapping the problematic line in an if(outOfBudget) statement, but the error persisted. I also log outOfBudget to console and see that it indeed exists.
Am I defining it incorrectly?
const BrokeBudget = ({outOfBudget}) => {
return outOfBudget.values.map((val, j) => {
return (
<div>
<p>{val.name}</p>
<p>{val.value}</p>
</div>
);
});
};
class Budget extends React.Component {
state = {
remainingBudget: 1600,
data,
pieChartData: [],
outOfBudget: []
};
handleInputChange = event => {
let { value, id, name } = event.target;
value = parseInt(value, 10);
const selectedQuestions = Object.assign(
{},
this.state.data.selectedQuestions
);
if (!selectedQuestions[name]) {
selectedQuestions[name] = {};
}
selectedQuestions[name][id] = value;
let newBudget = this.state.remainingBudget - value;
if( newBudget >= 0){
let pieSlice =
{
x: name,
y: value
};
console.log(pieSlice);
this.setState({
data: {
...this.state.data,
selectedQuestions
},
remainingBudget: newBudget,
pieChartData: this.state.pieChartData.concat(pieSlice),
});
}
else{
let beyondBudget = {genre: name, amount: value}
this.setState({
data: {
...this.state.data,
selectedQuestions
},
remainingBudget: newBudget,
pieChartData: this.state.pieChartData,
outOfBudget: {...this.state.outOfBudget, beyondBudget}
});
}
};
render() {
const { data, remainingBudget, pieChartData, outOfBudget } = this.state;
const questions = data.questions;
return (
<div>
{questions.map((q, i) =>
<UL key={i}>
<li>
<h4>{q.text}</h4>
</li>
<li>
<Options
state={this.state}
q={q}
i={i}
handler={this.handleInputChange}
/>
</li>
</UL>
)}
{Object.keys(data.selectedQuestions).length === 3 &&
<div>
<VictoryPie
colorScale = "blue"
data = {this.state.pieChartData}
labels= {d => `${d.x}: ${d.y}%`}
style={{ parent: { maxWidth: '50%' } }}
/>
< BrokeBudget
outOfBudget={outOfBudget}
/>
</div>
}
</div>
);
}
}
Please ignore any strange cases (like UL I use Emotion for styling)
What is .values ?
Looks like you need return outOfBudget.map((val, j) => { without the .values
Hope this helps.
I have many pages in my app. How can I make another button and make it shorter?
Ex <<1,2,3,4,5,6,7,8,9,10 >> after I click >> it will become 11,12,13,14,15,16,17,18,19,20
this is my design
I hope can look like this
enter image description here
I only know how to do the basic one...
const indexOfLastTodo = this.state.currentPage * this.state.todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - this.state.todosPerPage;
const currentTodos = filteredDatas.slice(indexOfFirstTodo, indexOfLastTodo);
const renderTodos = currentTodos.map((todo, index) => {
return <SearchResults item={todo} testkey={index} account={this.state.accountData} key={index}></SearchResults>
});
Collapse
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(filteredDatas.length / this.state.todosPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map(number => {
return (
<PaginationItem key={number}>
<PaginationLink key={number}
id={number}
onClick={this.handleClick} href=“#”>
{number}
</PaginationLink>
</PaginationItem >
);
});
<Pagination aria-label=“Page navigation example”>
{renderPageNumbers}
</Pagination>
Here my approach.
https://codesandbox.io/s/xpnp2k0214
Demo : https://xpnp2k0214.codesandbox.io/
Hope it will help u.
Edited
Explanation:
//Data source
const allUsers = [
{
id: "1130",
employee_name: "muaaa",
employee_salary: "123",
employee_age: "23",
profile_image: ""
}
.....
];
//Local state for users array and tableOptions object.
this.state = {
users: [],
isLoaded: false,
tableOptions: {
perpage: "10",
page: 1,
tot_count: 1
}
};
//Set the default page to 1 when the component mounted.
componentDidMount() {
//Update the size of allUsers array
let tableOptions = this.state.tableOptions;
tableOptions.tot_count = allUsers.length;
//Update the State
this.setState({ tableOptions }, () => {
this.setPage(1);
});
}
//Update the users array and current page
setPage = p => {
let tableOptions = this.state.tableOptions;
let page_size = parseInt(tableOptions.perpage);
//Finding the limits of allUsers array from current page
let from = (p - 1) * page_size;
let to = from + page_size;
//Slice the allUsers array with limit
let users = allUsers.slice(from, to);
tableOptions.page = p;
//Update the State
this.setState({ tableOptions, users, isLoaded: true });
};
//Generate the pagination from state.tableOptions
getPagination = () => {
let tOptions = this.state.tableOptions;
let per_page = tOptions.perpage;
let count = tOptions.tot_count;
let pages = count / per_page;
if (pages > 1) {
let pgarr = [];
pgarr[pages - 1] = 0;
var _pages = [];
let i = 0;
for (; i < pages; i++) {
_pages.push(i + 1);
}
let current_page = tOptions.page;
let new_pages = [];
if (_pages.length > 10) {
let start = 0,
end = 10;
let _end = _pages[_pages.length - 1];
let prev_page = parseInt(current_page - 5);
if (prev_page > 0) {
start = prev_page;
end = parseInt(current_page + 5);
end = end > _end ? _end : end;
}
for (i = start; i < end; i++) {
new_pages.push(i + 1);
}
new_pages[0] = _pages[0];
new_pages[new_pages.length - 1] = _pages[_pages.length - 1];
} else {
new_pages = _pages;
}
return (
<div className="row">
<div className="col" style={{ textAlign: "left" }}>
{new_pages.map((p, i) => (
<button
key={i}
style={
tOptions.page !== p
? { backgroundColor: "red" }
: { backgroundColor: "green" }
}
onClick={() => this.setPage(p)}
>
{p}
</button>
))}
</div>
</div>
);
} else {
return <div />;
}
};
//Finally the rendering the users table with pagination.
render() {
if (!this.state.isLoaded) {
return <div>Loading...</div>;
} else {
return (
<div className="App">
<div className="row" style={{ marginBottom: "15px" }}>
<div className="col">
<table border="1">
<thead>
<tr>
{Object.keys(this.state.users[0]).map((tr, i) => (
<td key={i}>{tr.split("_").join(" ")}</td>
))}
</tr>
</thead>
<tbody>
{this.state.users.map((user, i) => (
<tr key={i}>
<td>{user.id}</td>
<td>{user.employee_name}</td>
<td>{user.employee_salary}</td>
<td>{user.employee_age}</td>
<td>{user.profile_image}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{this.getPagination()}
</div>
);
}