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

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.

Related

Fill Json table with checkbox in react and check them according to JSON data

I am creating a table in React from a JSON like this:
[
{
"Id_side": 123,
"Name_side": "R4",
"Name_cycle": "C1"
},
{
"Id_side": 345,
"Name_side": "M1",
"Name_cycle": "C2"
},
{
"Id_side": 567,
"Name_side": "V5",
"Name_cycle": "C3"
},
{
"Id_side": 45,
"Name_side": "U4",
"Name_cycle": "C4"
}
]
The table, I am rendering it like this:
import tableData from "./actions/tableData.json"
const BrandTable = () => {
let tb_headers = tableData.map((item)=>{
return(
<td key={item.Id_side}>{item.Name_cycle}</td>
)
})
// this function is only for testing, I know it does not achieve anything.
function renderChecks() {
console.log("checkbox")
for (var i = 0; i < tableData.length; i++){
return <td><input type="checkbox" value="Test" /></td>
} }
let tb_data = tableData.map((item)=>{
return(
<tr key={item.Id_side}>
<td>{item.Name_side}</td>
{renderChecks()}
</tr>
)
})
return(
<table id="table">
<thead>
<tr>
<td></td>
{tb_headers}
</tr>
</thead>
<tbody>
{tb_data}
</tbody>
</table>
)
};
export default BrandTable;
For now I am only able to get a table like this:
Table1
but what I'm looking to do is to make a table with checkboxes in all the cells and have them checked as they come in the JSON.
For example, according to the JSON I show above the table should look like this:
table2
I need that each header has checkboxes in each of the cycles that exist and that these are activated or not depending on whether they come together in the json.
You can do it like below :
const cells = ['C1', 'C2', 'C3', 'C4'];
export default function App() {
return (
<div>
<table border="1" style={{ width: '100%' }}>
<thead>
<th></th>
<th> C1</th>
<th> C2</th>
<th> C3</th>
<th> C4</th>
</thead>
<tbody>
{data.map((item) => {
return <TableRow data={item} key={item.id} />;
})}
</tbody>
</table>
</div>
);
}
const TableRow = ({ data }) => {
return (
<tr>
<td> {data.Name_side} </td>{' '}
{cells.map((cell) => {
return (
<td key={cell}>
<input
type="checkbox"
checked={data.Name_cycle === cell}
// add event listener
/>
</td>
);
})}
</tr>
);
};
Example : Working demo
First step would be to create a basic map of Name_side & Name_cycles
var map = data.reduce((a,b) => {
a[b.Name_side] = a[b.Name_side] || [];
a[b.Name_side].push(b.Name_cycle);
return a;
}, {});
This would give an output like
[object Object] {
M1: ["C2"],
R4: ["C1"],
U4: ["C4", "C2"],
V5: ["C3", "C1", "C2"]
}
Now we have unique Name_side mapped to the Name_cycle values. We can now iterate on the object keys & create the rows.
<tbody>
{Object.keys(map).map((row) => (
<tr>
<td>{row}</td>
{cycleNames.map((cell) => {
const checked = map[row].includes(cell);
return (
<td>
<input checked={checked} type="checkbox" value="Bike" />{" "}
{cell}
</td>
);
})}
</tr>
))}
</tbody>

How to send table values to another component which will be visible later in React?

I have a table and edit/delete button on that table(each row) to edit/delete corresponding row.
I want to open a popup when the edit is clicked but I want to open the popup with some parameters to show like "old value, new value" etc.
Here is my code for table and I put an EditUserPopup component at bottom.
function MainPanel(props) {
const [isEditPopupOpen, setEditPopup] = useState(true);
const deleteCustomer = async (id) => {
await service.deleteCustomerById(id);
props.refreshTableParam();
}
const editCustomer = async (id, name, surname) => {
setEditPopup(true);
//WHAT I NEED HERE ?
props.refreshTableParam();
}
return (
<>
<ReactBootStrap.Table striped bordered hover>
<thead>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Edit</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{props.param &&
props.param.map((item) => (
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.firstName}</td>
<td>{item.lastName}</td>
<td><Button className='editButton' onClick={() => editCustomer(item.id, item.firstName, item.lastName)}><FontAwesomeIcon icon={faUserEdit} /></Button></td>
<td><Button className='deleteButton' onClick={() => deleteCustomer(item.id)}><FontAwesomeIcon icon={faTrashRestore} /></Button></td>
</tr>
))}
</tbody>
</ReactBootStrap.Table>
{
//HOW TO OPEN THAT COMPONENT WITH PARAMS
isEditPopupOpen && <EditUserPopup someParamHere={null}/>
}
</>
);
}
I am calling editCustomer() function by the button on table and I am thinking to make EditPopup somehow visible with some param, and in other component(popup's itself) I'll do some logic.
But I cannot reach the id,firstName,lastName values in popup. How can I send corresponding table row values to the popup ?
The page is this:
You can create a react state and set them inside the edit function. Then you should send them as props to your pop up.
function MainPanel(props) {
const [isEditPopupOpen, setEditPopup] = useState(true);
const [customerInfo, setCustomerInfo] = useState({id: '', name: '', surname: ''})
const deleteCustomer = async (id) => {
await service.deleteCustomerById(id);
props.refreshTableParam();
}
const editCustomer = async (id, name, surname) => {
setCustomerInfo({id: id, name: name, surname: surname})
setEditPopup(true);
//WHAT I NEED HERE ?
props.refreshTableParam();
}
return (
<>
<ReactBootStrap.Table striped bordered hover>
<thead>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Edit</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{props.param &&
props.param.map((item) => (
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.firstName}</td>
<td>{item.lastName}</td>
<td><Button className='editButton' onClick={() => editCustomer(item.id, item.firstName, item.lastName)}><FontAwesomeIcon icon={faUserEdit} /></Button></td>
<td><Button className='deleteButton' onClick={() => deleteCustomer(item.id)}><FontAwesomeIcon icon={faTrashRestore} /></Button></td>
</tr>
))}
</tbody>
</ReactBootStrap.Table>
{
//HOW TO OPEN THAT COMPONENT WITH PARAMS
isEditPopupOpen && <EditUserPopup customerInfo={customerInfo} someParamHere={null}/>
}
</>
);
}

Prevent React table from scrolling to top on update

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>)
)
}

How to get cell properties value in onClick event - react JS?

This is the table:
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>
{column.render('Header')}
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map(row => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return (
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
);
})}
</tr>
);
})}
</tbody>
</table>
Column format:
{
Header:'STUDENT ID',
accessor:'studentid',
},
{
Header:'Name',
accessor:'name',
},
{
Header:'ADDRESS',
accessor:'address',
},
{
Header: "Actions",
Cell: ({ cell }) => (
<span>< button>Edit<button>
/> < button onClick ={ ()=>{handleClick(cell.getCellProps())}} >Delete
<button/> </span>
)
}
Table format:
STUDENTID NAME ADDRESS ACTIONS
154 xyx xyx EDIT&DELETE buttons
564 abc abc EDIT&DELETE buttons
On clicking delete button, need to get student Id for API call to delete a particular record of student. I have used functional component for react table.
Handle click function:
const handleClick = (cell) => {
alert("clicked",cell.studentid);
console.log(cell);
}

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