Ok, I'm new to React and need help in reading/referencing HTML element data attributes or even the content within an HTML tag - normally with plain JS (and this is what I am doing in my React app) I read like this:
const postRow = document.getElementById(tableRowId);
;
const author = postRow.getElementsByTagName('td')[0].getAttribute('data-fieldvalue');
const title = postRow.getElementsByTagName('td')[1].getAttribute('data-fieldvalue');
const description = postRow.getElementsByTagName('td')[2].getAttribute('data-fieldvalue');
I have a functional ponent called Table that I use like this:
<Table
headers={this.state.postsHeaders}
rows={this.state.posts}
editable={this.state.editable}
deletable={this.state.deletable}
onUpdateIconClicked={this.toggleUpdatePostModalView}
onDeleteIconClicked={this.toggleDeletePostModalView}
/>
where rows property is the data that I am retrieving with axios
. My table gets generated find with several tr > td
rows. I have an 'edit' CTA for each row that on click I open up a modal where I pass my data to be edited for each row. Onclick of the CTA calls this function that works just fine:
toggleUpdatePostModalView = (postId, tableRowId) => {
// toggle the confirm delete post view
let showUpdatePostModal = !this.state.showUpdatePostModal;
// when postId and tableRowId are both null that means
// that the view delete confirm modal must remain not
// visible (closed) so have to override the toggle
if (postId === null && tableRowId === null) {
showUpdatePostModal = false;
}
const postRow = document.getElementById(tableRowId);
;
const author = postRow.getElementsByTagName('td')[0].getAttribute('data-fieldvalue');
const title = postRow.getElementsByTagName('td')[1].getAttribute('data-fieldvalue');
const description = postRow.getElementsByTagName('td')[2].getAttribute('data-fieldvalue');
// dont get the elements directly like above .html
this.setState({
...this.state,
showUpdatePostModal: showUpdatePostModal,
postToUpdate: {
postId: postId,
tableRowId: tableRowId,
author: author,
title: title,
description: description
}
});
}
The issue that someone pointed out is that I should NOT use the JS way of getting the data (the getElementById
and getElementsByTagName
functions because of virtual DOM and real DOM sync issues. So I was pointed toward .html but this seems to work if my tr
were to be a ponent itself but as it is, it is just HTML in my Table render function as so:
const table = (props) => {
// the following code creates an array from props.header object
// that is an indexed array (0, 1, ..., n) and each value
// contains the key properties that pose object props.header,
// and so, even though in .map() the parameter says 'key'
// this is misleading because in reality it is the VALUE
// (since the key of the array is 0 or 1 or n) but it is called
// 'key' because it is the key in the props.headers object that we
// need to get the info for (.map(function(currentValue, index, arr))
const headersArray = Object.keys(props.headers);
const tableHeaders = headersArray.map(key => {
return <th key={key}>{props.headers[key]}</th>;
});
const editHeader = props.editable === true ? <th key="edit">Edit</th> : null;
const deleteHeader = props.deletable === true ? <th key="delete">Delete</th> : null;
let tableRows = null;
if (props.rows) {
tableRows = props.rows.map((row, key) => {
return (
<tr id={`tr-${key}`} key={key}>
{/* inner loop to dynamically generate the <td>
depending on how many headers there are since
each header corresponds to a key or column in
the table */}
{headersArray.map(tdKey => {
return <td key={tdKey} data-fieldname={tdKey} data-fieldvalue={row[tdKey]} >{row[tdKey]}</td>
})}
{props.editable === true ? <td key="edit"><PencilIcon onClick={() => props.onUpdateIconClicked(row.postId, `tr-${key}`)} /></td> : null}
{props.deletable === true ? <td className="delete-icon-container" key="delete"><TrashIcon onClick={() => props.onDeleteIconClicked(row.postId, `tr-${key}`)} /></td> : null}
</tr>
);
});
}
return (
<table className="table is-striped">
<thead>
<tr>
{tableHeaders}
{editHeader}
{deleteHeader}
</tr>
</thead>
<tbody>
{tableRows}
</tbody>
</table>
);
}
I also read that these refs should not be used to often - so what if I have a table with 100 rows? 200? I am not sure how to proceed and do this the React way...can anyone help?
Ok, I'm new to React and need help in reading/referencing HTML element data attributes or even the content within an HTML tag - normally with plain JS (and this is what I am doing in my React app) I read like this:
const postRow = document.getElementById(tableRowId);
;
const author = postRow.getElementsByTagName('td')[0].getAttribute('data-fieldvalue');
const title = postRow.getElementsByTagName('td')[1].getAttribute('data-fieldvalue');
const description = postRow.getElementsByTagName('td')[2].getAttribute('data-fieldvalue');
I have a functional ponent called Table that I use like this:
<Table
headers={this.state.postsHeaders}
rows={this.state.posts}
editable={this.state.editable}
deletable={this.state.deletable}
onUpdateIconClicked={this.toggleUpdatePostModalView}
onDeleteIconClicked={this.toggleDeletePostModalView}
/>
where rows property is the data that I am retrieving with axios
. My table gets generated find with several tr > td
rows. I have an 'edit' CTA for each row that on click I open up a modal where I pass my data to be edited for each row. Onclick of the CTA calls this function that works just fine:
toggleUpdatePostModalView = (postId, tableRowId) => {
// toggle the confirm delete post view
let showUpdatePostModal = !this.state.showUpdatePostModal;
// when postId and tableRowId are both null that means
// that the view delete confirm modal must remain not
// visible (closed) so have to override the toggle
if (postId === null && tableRowId === null) {
showUpdatePostModal = false;
}
const postRow = document.getElementById(tableRowId);
;
const author = postRow.getElementsByTagName('td')[0].getAttribute('data-fieldvalue');
const title = postRow.getElementsByTagName('td')[1].getAttribute('data-fieldvalue');
const description = postRow.getElementsByTagName('td')[2].getAttribute('data-fieldvalue');
// dont get the elements directly like above https://reactjs/docs/refs-and-the-dom.html
this.setState({
...this.state,
showUpdatePostModal: showUpdatePostModal,
postToUpdate: {
postId: postId,
tableRowId: tableRowId,
author: author,
title: title,
description: description
}
});
}
The issue that someone pointed out is that I should NOT use the JS way of getting the data (the getElementById
and getElementsByTagName
functions because of virtual DOM and real DOM sync issues. So I was pointed toward https://reactjs/docs/refs-and-the-dom.html but this seems to work if my tr
were to be a ponent itself but as it is, it is just HTML in my Table render function as so:
const table = (props) => {
// the following code creates an array from props.header object
// that is an indexed array (0, 1, ..., n) and each value
// contains the key properties that pose object props.header,
// and so, even though in .map() the parameter says 'key'
// this is misleading because in reality it is the VALUE
// (since the key of the array is 0 or 1 or n) but it is called
// 'key' because it is the key in the props.headers object that we
// need to get the info for (.map(function(currentValue, index, arr))
const headersArray = Object.keys(props.headers);
const tableHeaders = headersArray.map(key => {
return <th key={key}>{props.headers[key]}</th>;
});
const editHeader = props.editable === true ? <th key="edit">Edit</th> : null;
const deleteHeader = props.deletable === true ? <th key="delete">Delete</th> : null;
let tableRows = null;
if (props.rows) {
tableRows = props.rows.map((row, key) => {
return (
<tr id={`tr-${key}`} key={key}>
{/* inner loop to dynamically generate the <td>
depending on how many headers there are since
each header corresponds to a key or column in
the table */}
{headersArray.map(tdKey => {
return <td key={tdKey} data-fieldname={tdKey} data-fieldvalue={row[tdKey]} >{row[tdKey]}</td>
})}
{props.editable === true ? <td key="edit"><PencilIcon onClick={() => props.onUpdateIconClicked(row.postId, `tr-${key}`)} /></td> : null}
{props.deletable === true ? <td className="delete-icon-container" key="delete"><TrashIcon onClick={() => props.onDeleteIconClicked(row.postId, `tr-${key}`)} /></td> : null}
</tr>
);
});
}
return (
<table className="table is-striped">
<thead>
<tr>
{tableHeaders}
{editHeader}
{deleteHeader}
</tr>
</thead>
<tbody>
{tableRows}
</tbody>
</table>
);
}
I also read that these refs should not be used to often - so what if I have a table with 100 rows? 200? I am not sure how to proceed and do this the React way...can anyone help?
Share Improve this question edited Feb 27, 2020 at 15:29 Yevhen Horbunkov 15.6k3 gold badges27 silver badges45 bronze badges asked Feb 26, 2020 at 17:22 Pippy LongstockingPippy Longstocking 3036 silver badges12 bronze badges 01 Answer
Reset to default 5Refs are NOT an appropriate tool to use here.
Instead, you should do lifting state up (a lot).
In order to do that, I would remend
- to break your table ponent into smaller pieces (like
<Header />
,<Row />
, etc) within mon parent<App />
and a separate<EditDialog />
/<DeleteDialog />
ponents to edit/delete row data) - smaller ponents are easier to maintain and troubleshoot - store your table data (preferably, with unique record
id
's) within parent (<Table />
) ponent and pass data entries that correspond to table rows as params to<Row />
ponents - pass abstract
onEdit()
andonDelete()
event handlers as props to<Row />
ponents and attach those toonClick()
handlers of Edit/Delete buttons - bind those child props (
onEdit()
,onDelete()
) to the callbacks within parent that will trigger editing/deleting dialogs - upon record editing/deletion update state of the
<Table />
accordingly.
Here's the full-blown demo of what's described above (I have used MaterialUI for styling not to overburden the demo with tons of CSS, you may proceed with your custom ponents, hopefully, that doesn't make my example less clear for you):
const { useState } = React,
{ render } = ReactDOM,
{ TableContainer, Table, TableHead, TableBody, TableRow, TableCell, IconButton, Dialog, DialogTitle, DialogContent, DialogContentText, Button, TextField, FormGroup } = MaterialUI
const srcData = [{id:0, author: 'Author1', title: 'Post 1', description: 'Some description'},{id:1, author: 'Author2', title: 'Post 2', description: 'Some other description'},{id:2, author: 'Author3', title: 'Post 3', description: 'Something else'}],
dataFields = [{id: 0, title: 'Author', key: 'author'},{id: 1, title: 'Title', key: 'title'},{id:2, title: 'Description', key: 'description'}]
const EditButton = ({handleClick}) => (
<IconButton onClick={handleClick} >
<i className="material-icons">create</i>
</IconButton>
)
const DeleteButton = ({handleClick}) => (
<IconButton onClick={handleClick} >
<i className="material-icons">delete</i>
</IconButton>
)
const DeleteDialog = ({isOpen, onDialogClose, onConfirmDelete, recordId}) => (
<Dialog open={isOpen} onClose={onDialogClose} >
<DialogTitle>Delete record</DialogTitle>
<DialogContent>
<DialogContentText>Are you sure you want to delete this record?</DialogContentText>
<FormGroup>
<Button onClick={() => onConfirmDelete(recordId)}>Yes</Button>
<Button onClick={onDialogClose}>No</Button>
</FormGroup>
</DialogContent>
</Dialog>
)
const EditDialog = ({isOpen, onDialogClose, onSubmitEdit, recordData, fields}) => {
const [data, setData] = useState(),
handleEdit = (key,value) => setData({...data, [key]:value})
return (
<Dialog open={isOpen} onClose={onDialogClose} >
<DialogTitle>Edit record</DialogTitle>
<DialogContent>
<FormGroup>
{
fields.map(({key,title}) => (
<TextField
key={key}
defaultValue={recordData[key]}
label={title}
onChange={({target:{value}}) => handleEdit(key,value)}
/>
))
}
</FormGroup>
<FormGroup>
<Button onClick={() => onSubmitEdit({...recordData,...data})}>Submit</Button>
<Button onClick={() => onDialogClose()}>Cancel</Button>
</FormGroup>
</DialogContent>
</Dialog>
)
}
const Header = ({columnTitles}) => (
<TableHead>
<TableRow>
{columnTitles.map(({title,id}) => <TableCell key={id}>{title}</TableCell>)}
<TableCell>Action</TableCell>
</TableRow>
</TableHead>
)
const Row = ({rowData, columns, onEdit, onDelete}) => (
<TableRow>
{columns.map(({key}, i) => <TableCell key={i}>{rowData[key]}</TableCell>)}
<TableCell>
<EditButton handleClick={() => onEdit(rowData.id)} />
<DeleteButton handleClick={() => onDelete(rowData.id)} />
</TableCell>
</TableRow>
)
const App = ({data,fields}) => {
const [tableData, setTableData] = useState(data),
[dataFields, setDataFields] = useState(fields),
[deleteDialogOn, setDeleteDialogOn] = useState(false),
[editDialogOn, setEditDialogOn] = useState(false),
[recordIdToDelete, setRecordIdToDelete] = useState(),
[recordIdToEdit, setRecordIdToEdit] = useState(),
onEditDialogOpen = (id) => (setRecordIdToEdit(id),setEditDialogOn(true)),
onDeleteDialogOpen = (id) => (setRecordIdToDelete(id), setDeleteDialogOn(true)),
handleEdit = (data) => {
setEditDialogOn(false)
const tableDataCopy = [...tableData],
editedItemIdx = tableDataCopy.findIndex(({id}) => id == data.id)
tableDataCopy.splice(editedItemIdx,1,data)
setTableData(tableDataCopy)
},
handleDelete = (idRecordToDelete) => {
setDeleteDialogOn(false)
const tableDataCopy = [...tableData]
setTableData(tableDataCopy.filter(({id}) => id!=recordIdToDelete))
}
return (
<div>
<DeleteDialog
isOpen={deleteDialogOn}
onDialogClose={() => setDeleteDialogOn(false)}
onConfirmDelete={handleDelete}
recordId={recordIdToDelete}
/>
<EditDialog
isOpen={editDialogOn}
onDialogClose={() => setEditDialogOn(false)}
onSubmitEdit={handleEdit}
recordData={tableData.find(({id}) => id==recordIdToEdit)||{}}
fields={dataFields}
/>
<TableContainer>
<Table>
<Header columnTitles={dataFields} />
<TableBody>
{
tableData.map(data => (
<Row
key={data.id}
rowData={data}
columns={dataFields}
onEdit={onEditDialogOpen}
onDelete={onDeleteDialogOpen}
/>
))
}
</TableBody>
</Table>
</TableContainer>
</div>
)
}
render (
<App data={srcData} fields={dataFields} />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare./ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><script src="https://unpkg./@material-ui/core@latest/umd/material-ui.development.js"></script><script src="https://cdnjs.cloudflare./ajax/libs/axios/0.19.1/axios.min.js"></script><link href="https://fonts.googleapis./icon?family=Material+Icons" rel="stylesheet"><div id="root"></div>