Prevent React table from scrolling to top on update - javascript

I have a react table that contains information in my Electron application. However, whenever data in the table is updated or a button is clicked, the table scrolls to the top, frustrating users.
Example code is as follows:
const tableContent = listItem.map((item: any, index: number) => {
return (
<Tr key={index.toString()} className="section">
<Td>{item.<item1>}</Td>
<Td>
<Icon
onClick={() => exampleFunction()}
/>
</Td>
</Tr>
);
});
return (
<Div className="round-card page-content-table table-responsive padding-20">
{<Table className="table table-striped">
<Thead>
<Tr>
<Th>TH1</Th>...
<Th>TH2</Th>
</Tr>
</Thead>
{<Tbody>{tableContent}</Tbody>}
</Table>}
</Div>)
How can I avoid these scroll jumps in the element during updates?
Update:
I was able to get the scroll position to save however, when the table updates, the scroll is stuck to the previous point, making it impossible for users to scroll when the table is updating. Any way to avoid this?
const [scrollPostion, setScrollPosition] = useState(
parseInt(localStorage.getItem('scrollPos')) || 0
);
const TableRef = useRef(null);
const scrollEvent = (e) => {
setScrollPosition(e.target.scrollTop);
localStorage.setItem('scrollPos', scrollPostion.toString());
};
React.useEffect(() => {
localStorage.setItem('scrollPos', scrollPostion.toString());
}, [scrollPostion]);

For anyone who runs into this issue in the future, I solved by moving the table into new component and putting it in the div
const myTable = () => {
const tableContent = listItem.map((item: any, index: number) => {
return (
<Tr key={index.toString()} className="section">
<Td>{item.<item1>}</Td>
<Td>
<Icon
onClick={() => exampleFunction()}
/>
</Td>
</Tr>
);
};
return (
<Table className="table table-striped">
<Thead>
<Tr>
<Th>TH1</Th>...
<Th>TH2</Th>
</Tr>
</Thead>
{<Tbody>{tableContent}</Tbody>}
</Table>}
)
}
const pageContent = () = {
return (
<Div className="round-card page-content-table table-responsive padding-20">
<myTable></myTable>
</Div>)
)
}

Related

How to color the rows and headers of a table with bootstrap

I need to display 3 types of data with different fields in the same table. To do this, I want to have 3 headers with a different color each.
I use bootstrap to make my design and my code is in Javascript with React.
I wrote the following code to do this (I tried to simplify it but it is normally reproducible)
import * as React from "react";
import { useEffect, useState } from "react";
import { nanoid } from "nanoid";
//props object
type IPropsTable={
currentDatas: (DataType1 | DataType2 | DataType3 | undefined;
}
const TableRequest: React.FC<IPropsTable> = ({ currentDatas }) => {
const [existData1, setExistData1] = useState(false);
const [existData2, setExistData2] = useState(false);
const [existData3, setExistData3] = useState(false);
useEffect(()=>{
if (currentDatas) {
currentDatas.map((currentData) => {
if (currentData.type === "data1") {
setExistData1(true);
} else if (currentData.type === "data2") {
setExistData2(true);
} else if (currentData.type === "data3") {
setExistData3(true);
}
})
}
},[currentDatas])
function renderTableHeaderData1() {
let header = ['someField1', 'someField2']
return header.map((key, index) => {
return <th key={index} scope="col">{key.toUpperCase()}</th>
})
}
function renderTableHeaderData2() {
let header = ['someOtherField1', 'someOtherField2']
return header.map((key, index) => {
return <th key={index} scope="col">{key.toUpperCase()}</th>
})
}
function renderTableHeaderData3() {
let header = ['someOtherOtherField1', 'someOtherOtherField2']
return header.map((key, index) => {
return <th key={index} scope="col">{key.toUpperCase()}</th>
})
}
function renderTableData() {
if(currentDatas){
return currentDatas.map((session) => {
if (session.type === "data1") {
return (
<tr key={nanoid()} className="warning">
<td>{session.someField1}</td>
<td>{session.someField2}</td>
</tr>
)
} else if (session.type === "data2") {
return (
<tr key={nanoid()} className="info">
<td>{session.someOtherField1}</td>
<td>{session.someOtherField2}</td>
</tr>
)
} else if (session.type === "data3") {
return (
<tr key={nanoid()} className="success">
<td>{session.someOtherOtherField1}</td>
<td>{session.someOtherOtherField2}</td>
</tr>
)
}
})
} else{return undefined}
}
return (
<>
<div>
<table className="table table-sm">
<caption>Result Search</caption>
<thead>
{existData1?
<tr className="thead-warning">{renderTableHeaderData1()}</tr>
: <></>
}
{existData2?
<tr className="thead-info">{renderTableHeaderData2()}</tr>
: <></>
}
{existData3?
<tr className="thead-success">{renderTableHeaderData3()}</tr>
: <></>
}
</thead>
<tbody>
{renderTableData()}
</tbody>
</table>
</div>
</>
)
}
export default TableRequest;
As you can see in the code above, I assign a css class to each of my <tr> (warning for data1, info for data2 and success for data3). But when my component is rendered, no color appears and the table is completely white, either for each of the three headers or for the data contained in each row of the table.
I tried using the thead-warning, thead-info and thead-success classes for my table header tr css classes, they seemed to be more suitable. But same result, no color is displayed.
Does anyone see what I'm doing wrong and could guide me in the right direction, I really don't understand where my problem is.
Use <tr class="success"> or <tr class="warning"> (depends what color you want).
Example:
<tr class="success">
<td>Success</td>
<td>Doe</td>
<td>john#example.com</td>
</tr>
My problem was with the name of the className I was using to color my table. By using bg-success instead of table-success everything works normally. But I don't understand why the table-success class doesn't work as in this example: example with table-success

How to Convert a <td></td> into <input /> onClick in React

I want to convert <td>MyData</td> into <input value="MyData" /> when I click on <td>Edit</td>. When I click on <td>Edit</td> I have the id of complete row. Actually I want to add Edit function to my code. Whenever I click on edit it should change all the <td>MyData</td> into input field where I can change it. Following is my code.
function ManagerGrid(props)
{
function handleClick(value)
{
console.log(value)
}
const listItems = props.tasks.map((task)=>
{
return(
<tr key={task._id}>
<td>{task._id}</td>
<td>{task.title}</td>
<td>{task.detail}</td>
<td>{task.assignee}</td>
<td>{task.time}</td>
<td onClick={(e)=>{handleClick(task._id)}}>Edit</td>
</tr>
)
})
return(
<table id="myTable">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Detail</th>
<th>Assignee</th>
<th>Time</th>
</tr>
</thead>
<tbody>
{listItems}
</tbody>
</table>
)
}
Your render code needs to chnage based on its current state, i.e. editing or not editing. You need an isEditing state. Here's an example, you'll have to work out how to save the changes. This is based on hooks, but you could also pass it in with your props and keep track of it in a parent component.
function ManagerGrid(props)
{
const [editingId, setEditingId] = useState(-1)
function handleClick(value)
{
setEditingId(value)
// TODO: cancel or save edits
}
const listItems = props.tasks.map((task)=>
{
if (editingId === task._id) {
return(
<tr key={task._id}>
<td><input value={task._id} /></td>
<td><input value={task.title} /></td>
<td><input value={task.detail} /></td>
<td><input value={task.assignee} /></td>
<td><input value={task.time} /></td>
<td onClick={(e)=>{handleSave(/*TODO*/)}}>Save</td>
</tr>)
else {
return (<tr key={task._id}>
<td>{task._id}</td>
<td>{task.title}</td>
<td>{task.detail}</td>
<td>{task.assignee}</td>
<td>{task.time}</td>
<td onClick={(e)=>{handleClick(task._id)}}>Edit</td>
</tr>)
}
})
return(
<table id="myTable">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Detail</th>
<th>Assignee</th>
<th>Time</th>
</tr>
</thead>
<tbody>
{listItems}
</tbody>
</table>
)
}
You can achieve it by creating custom component like this:
function ManagerGrid(props) {
const [editedRowId, setEditedRowId] = useState(null);
function handleClick(value) {
setEditedRowId(value);
}
const listItems = props.tasks.map((task) => {
const CellContent = ({ children }) => {
const isCurrentTaskEdit = task._id === editedRowId;
return (
<td>
{isCurrentTaskEdit ? (
<input style={{ width: "50px" }} value={children} />
) : (
children
)}
</td>
);
};
return (
<tr key={task._id}>
<CellContent>{task._id}</CellContent>
<CellContent>{task.title}</CellContent>
<CellContent>{task.detail}</CellContent>
<CellContent>{task.assignee}</CellContent>
<CellContent>{task.time}</CellContent>
<td
onClick={(e) => {
handleClick(task._id);
}}
>
Edit
</td>
</tr>
);
});
These kinds of problems can usually be solved by creating smaller components:
const ReadListItem = ({
task,
onEdit
}) => {
return (
<tr>
<td>{task._id}</td>
<td>{task.title}</td>
<td>{task.detail}</td>
<td>{task.assignee}</td>
<td>{task.time}</td>
<td onClick={onEdit}>Edit</td>
</tr>
);
};
const EditListItem = ({
task,
onDone
}) => {
const [title, setTitle] = useState(task.title);
const [detail, setDetail] = useState(task.detail);
const [assignee, setAssignee] = useState(task.assignee);
const [time, setTime] = useState(task.time);
const finished = useCallback(() => {
onDone({
_id: task._id,
title,
detail,
assignee,
time
});
}, [task, title, detail, assignee, time]);
useEffect(() => {
setTitle(task.title);
setDetail(task.detail);
setAssignee(task.assignee);
setTime(task.time);
}, [task]);
return (
<tr>
<td>{task._id}</td>
<td><input type='text' value={title} onChange={e => setTitle(e.target.value)} /></td>
<td><input type='text' value={detail} onChange={e => setDetail(e.target.value)} /></td>
<td><input type='text' value={assignee} onChange={e => setAssignee(e.target.value)} /></td>
<td><input type='text' value={time} onChange={e => setTime(e.target.value)} /></td>
<td onClick={finished}>Done</td>
</tr>
);
}
const ListItem = ({
task,
onEdited
}) => {
const [editing, setEditing] = useState(false);
return editing
? <EditListItem task={task} onDone={(item) => onEdited(item)}>
: <ReadListItem task={task} onEdit={() => setEditing(true)} />
}
You could have another component for the inputs to reduce the amount of code, and could make better use of useCallback for the onChange handlers.
The usage would be a lot simpler:
const ManagerGrid = ({
tasks
}) => {
const onEdited = useCallback((task) => {
console.log({task});
}, []);
return (
<table id="myTable">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Detail</th>
<th>Assignee</th>
<th>Time</th>
</tr>
</thead>
<tbody>
{ tasks.map(task => <ListItem key={task._id} task={task} onEdited={onEdited} />) }
</tbody>
</table>
);
}
You need to note that I've made it so the tasks are not changed anywhere, because it could come from a data source that is considered immutable. However, it can be handled in the final onEdited callback, where you could either modify the task list, or dispatch an action to handle it.
The useEffect inside the EditListItem is in case the task changes from outside of the component.
I think that this covers most of the things you've asked but please do add comments if there's anything I've missed.

how to update integer value imported from a module in react

I'm trying a simple interactive table using react, where the table displays a set of details of Books, namely name, price, and stock. The details of the books are stored in a separate JS fine as a module and imported into the component file. Also 'stock' value is saved as a number type in the module. When I try to add or reduce the 'stock' value by one using a button, it doesn't make any changes to the value count.
Below is my code:
import React, { Component } from 'react';
import bookStock from '../Models/books'
class Books extends Component {
state = {
bookList: bookStock
}
totalCopies(){
let sum = 0
this.state.bookList.forEach(book => (
sum = sum + book.stock
))
return sum;
}
addBookCount = (book) => {
this.setState({book.stock = book.stock + 1})
}
reduceBookCount = book => {
this.setState({book.stock = book.stock - 1})
}
render() {
const {length} = this.state.bookList
return (
<React.Fragment>
<table className="table table-borderless">
<thead>
<tr>
<th>Title</th>
<th>Price</th>
<th>No.of Copies</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{this.state.bookList.map(book => (
<tr key = {book.id}>
<td>{book.name}</td>
<td>{book.Price}</td>
<td>{book.stock}</td>
<td><button onClick={() => this.addBookCount(book)} className="btn btn-success btn-sm">ADD</button></td>
<td><button onClick={() => this.reduceBookCount(book)} className="btn btn-danger btn-sm">REDUCE</button></td>
</tr>
))}
</tbody>
</table>
<br/>
<h1>Total No.of Books: {length}</h1>
<h1>Total No.of Copies: {this.totalCopies()}</h1>
</React.Fragment>
);
}
}
export default Books;
How can I increment and decrement the stock value? The totalCopies method is working fine and it displays the total number of copies
Your state has a bookList state that maintains the bookStock.However
In the addBookCount function:
You are trying to update the property book.stock which doesn't exist in your state.
Moreover, this isn't the correct way to update the stock.
First, make a copy of the state in a local variable.
Then, find the book passed as an argument into the function in the local variable (bookList).
Update the stock there.
Now, set the local variable as the new state value.
import React, { Component } from 'react';
import bookStock from '../Models/books'
class Books extends Component {
state = {
bookList: bookStock,
bookcount: 0,
}
totalCopies(){
let sum = 0
this.state.bookList.forEach(book => (
sum = sum + book.stock
))
this.setState({bookcount: sum})
}
addBookCount = (book) => {
this.setState({book.stock = book.stock + 1})
}
reduceBookCount = book => {
this.setState({book.stock = book.stock - 1})
}
render() {
const {length} = this.state.bookList
return (
<React.Fragment>
<table className="table table-borderless">
<thead>
<tr>
<th>Title</th>
<th>Price</th>
<th>No.of Copies</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{this.state.bookList.map(book => (
<tr key = {book.id}>
<td>{book.name}</td>
<td>{book.Price}</td>
<td>{book.stock}</td>
<td><button onClick={() => this.addBookCount(book)} className="btn btn-success btn-sm">ADD</button></td>
<td><button onClick={() => this.reduceBookCount(book)} className="btn btn-danger btn-sm">REDUCE</button></td>
</tr>
))}
</tbody>
</table>
<br/>
<h1>Total No.of Books: {length}</h1>
<h1>Total No.of Copies: {this.state.bookcount}</h1>
</React.Fragment>
);
}
}
export default Books;
Can you try this
I have finally found the way. Thanks everyone for responding me
addBookCount = (book) => {
this.state.bookList.filter(b => b.id === book.id).forEach(bk => bk.stock = bk.stock + 1)
this.setState({})
}
reduceBookCount = book => {
this.state.bookList.filter(b => b.id === book.id).forEach(bk => bk.stock = bk.stock - 1)
this.setState({})
}

Add multiple rows to a table using `forEach` without affecting browser performance

I generate dynamic table column and rows in a parent component by adding a name from a model (child component).
Parent component HTML.
<table class="table table-striped table-hover table-condensed">
<thead>
<tr>
<th>Words</th>
<th *ngFor="let wordList of wordList">{{wordList.word}}
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let keys of workModel">
<td>{{keys.key}}</td>
<td *ngFor="let translation of keys.translation">
<input type="text" [name]="keys.key" [(ngModel)]="translation.value">
</td>
</tr>
</tbody>
</table>
In service I'm calling this function.
addWord( newWordName : string ) {
const addWord = {
language: newWordName,
value: ''
};
this._workModel.forEach( ( data : EditorInstance ) => {
data.translation.push( { ...addWord } );
} );
}
Whenever I add a new word, browser is going to not responsive mode i.e., it hangs the browser. Here this._workModel may also contains some values.
Additional thing below.
So I tried implementing changeDetectionin it parent component.
addNewWord( newWord : string ) {
this.cdr.detach();
setInterval(() => {
this.cdr.detectChanges();
this._translatorEditorService.addLanguage( newLanguage )
.then( () => {
} ).catch( () => {
} );
}, 5000);
}
But Still the same. Is there any other way of implementing to achieve smooth handling of adding the rows in forLoop.

How can I integrate searching functionality on table?

At the moment, all the available flights that was received from API are successfully loaded on the page. However, I would like to enable the end user to search specific flight, let's say, by flight number and departure date. How can I integrate this searching functionality in the existing codes?
FlightPage.js
render() {
return (
<>
<h2>Flights</h2>
{this.props.loading ? (
<div>Loading...</div>
) : (
<FlightList flights={this.props.flights} />
)}
</>
);
}
}
As you can see the bellow code, I have used table to present the results.I would like to show only one result or blank table when searching is applied. Can you help me to achieve this?
FlightList.js
const FlightList = ({ flights }) => (
<table className="table">
<thead>
<tr>
<th />
<th>Date</th>
<th>Provider</th>
<th>Dest</th>
</tr>
</thead>
<tbody>
{flights.map((f, i) => {
return (
<tr key={i}>
<td>
<input type="checkbox" name="flightListCheckbox" />
</td>
<td>{f.date}</td>
<td>{f.pnr}</td>
<td>{f.flightNumber}</td>
</tr>
);
})}
</tbody>
</table>
);
You could use filter to create a searching functionality like
I would at first add an input where I can insert my filter values
FlightPage.js
handleInput: (event) => {
const { name, value } = event.target
this.setState({ [name]: value })
}
render () {
const { filter } = this.state
return (
<>
<input onChange=(this.handleInput) value={filter} name='filter' />
<FlightList flights={this.props.flights} filterValues={filter} />
</>
)
}
Then I would use my state to filter my Object like
FlightList.js
const FlightList = ({ flights, filterValue }) => {
const filterdFlights = flights.filter(flight => Object.values(flight).includes(filterValue))
return (
<table className="table">
<thead>
<tr>
<th />
<th>Date</th>
<th>Provider</th>
<th>Dest</th>
</tr>
</thead>
<tbody>
{filterdFlights.map((f, i) => {
return (
<tr key={i}>
<td>
<input type="checkbox" name="flightListCheckbox" />
</td>
<td>{f.date}</td>
<td>{f.pnr}</td>
<td>{f.flightNumber}</td>
</tr>
);
})}
</tbody>
</table>
)};
You need an input for search and filter flights by value of input. Try this
class FlightPage extends React.Component {
state = {
keyword: '',
}
...
getFlights = () => {
const { keyword } = this.state
const { flights } = this.props
return flights.filter(flight => flight.name.includes(keyword)) // name or something else
}
onInputChange = e => {
this.setState({ keyword: e.target.value })
}
render () {
return (
<>
<input onChange=(this.onInputChange) value={this.state.keyword} />
<FlightList flights={this.getFlights()} />
</>
)
}
}
You can filter your flights array using flights.filter or sort it using flights.sort.
You could try to use jquery datatable. It adds a lot of funcionality to tables easy to implement.
DataTable doc

Categories

Resources