React - appending an empty row to a table - reactjs

I am currently building an application that renders a table but in the first instance the only thing that renders are the column headers. What I would like to know is how I would then go about giving a user the choice to add a row with no data that they can then edit to add data that they want to put in the cells of the row. The headers are generated by a form with a dynamic amount of inputs so theoretically there could be a lot and the added row would need to reflect this also. I would like to try and avoid using a plugin if possible as well.
The app is being built with React and for the moment I am using flux architecture.
Fast answers with examples would be very much appreciated!
Thanks for your time
Here is where the whole table renders:
import React from 'react';
import TableHeader from './TableHeader.jsx';
import TableBody from './TableBody.jsx';
export default class Table extends React.Component {
render() {
return (
<table className="table table-striped dali-table">
<thead><TableHeader /></thead>
<tbody><TableBody /></tbody>
</table>
);
}
}
Here is the code for rendering the column headers:
import React from 'react';
import AppStore from '../../stores/AppStore';
export default class TableHeader extends React.Component {
addHeaders() {
let headerArray = AppStore.getTable().columns;
let headerList = headerArray.map((element, index) => {
return (
<th key={index} id={element.id} className="text-center">{element.name}</th>
);
});
return headerList;
}
render() {
return (
<tr>{this.addHeaders()}</tr>
);
}
}
And I don't really have any code for the body of the table, as that is the bit which I am struggling with!
Okay so I think I'm making some headway! Thanks for the help so far! Here is what I have at the moment (I will be separating it out later, just want to get it working first!):
import React from 'react';
import AppStore from '../../stores/AppStore';
export default class Table extends React.Component {
state = {rows: [], isEditing: false};
render() {
let {rows, isEditing} = this.state;
let headerArray = AppStore.getTable().columns;
const handleEdit = (row) => {
this.setState({isEditing: true});
};
const handleAddRowClickEvent = () => {
let rows = this.state.rows;
rows.push({isNew: true, isEditing: false});
this.setState({rows: rows});
};
return (
<div>
<div className="row" id="table-row">
<table className="table table-striped">
<thead>
<tr>{headerArray.map((element, index) => {
return (
<th key={index} id={element.id} className="text-center">{element.name}</th>
);})}
</tr>
</thead>
<tbody>
{rows.map((row, index) => row.isEditing ?
<RowForm formKey={index} key={index} /> :
<tr key={index}>
{headerArray.map((element, index) => {
return (
<td key={index} id={element.id}></td>
);
})}
<td>
<button onClick={handleEdit(row)}>Edit</button>
</td>
</tr>)}
</tbody>
</table>
</div>
<div className="row">
<div className="col-xs-12 de-button">
<button type="button" className="btn btn-success" onClick={handleAddRowClickEvent}>Add Row</button>
</div>
</div>
</div>
);
}
}
Need help on adding the row don't know what to push into the empty rows array! Thanks

The main idea is add flag to each row so you can know the status for each row, is it in edit mode or display mode
render(){
const handleEdit = (row) => {
// toggle isEditing flag
};
return (<tbody>
{
rows.map((row) => row.isEditing ?
<RowForm formKey={row.id} key={row.id}/> :
<tr key={row.id}>
<td>{row.id}</td>
<td>{row.name}</td>
<button onClick={handleEdit(row)}>
Edit
</button>
</td>
</tr>
}
</tbody>);
}
Check this example source code / demo

Posting some code snippets will help one provide better answers
So, to create a dynamic row you could do something like:
import React from 'react';
import AppStore from '../../stores/AppStore';
export default class TableBody extends React.Component {
addRows() {
// This gets the number of columns in your header from your backend
let noOfColumns = AppStore.getColumnSize();
// loop from 1 to noOfColumns and return:
return (
<td><input placeholder='Enter Value'/></td>
);
}
render() {
return (
<tr>{this.addRows()}</tr>
);
}
}
Then add TableBody to your Table Class.
PS - The above is just code snippets and is somewhat pseudo-code. You also need to handle the 'input field' correctly as per React Documentation recommendation: https://facebook.github.io/react/docs/forms.html

Related

Component output by pressing the ReactJS button using the props

I have 2 buttons, when I click on one of them, the component will be displayed in tr className = "info". The code is made so that it is universal, if there are 50 different components, then there will be 50 different buttons. And the active button will change the class. I'm trying to do it now with the help of a props, I'm just learning, tell me, please, where is the mistake?
import React, { Component } from 'react';
import './App.css';
import Donald from '/.Donald';
import John from '/.John';
const Counter =({buttonType,id})=>{
const {
buttonType,
children,
} = props;
return (
<div
className = {`${buttonType}`}
onClick={id}
>
{children}
</div>
);
}
Counter.defaultProps={
className = 'notActive'
}
class MyName extends Component {
state = {
array:[
{id:1,component:<Donald/>, name:"My name Donald"},
{id:2,component:<John/>, name:"My name John"},
],
};
render() {
const selectedElement = this.state.array.find(item => item.id === this.state.currComponentId);
return(
<table>
<tbody>
<tr className="trButton">
{
this.state.array.map( (element) => {
return (
<Counter>
<td
className={'active'}
onClick={ () => this.element.id}
>{element.name}
</td>
</Counter>
)
})
}
</tr>
<tr className="info">
{selectedElement.component}
</tr>
</tbody>
</table>
)
}
}
export default MyName;
I see couple of issues with the code
(1) Firstly, I think you are using incorrect path for import these components i.e. Use ./Donald instead of /.Donald (notice the . before / not after), similarly './John'.
(2) Secondly you're not setting initial value for currComponentId in state. Lets set that too
state = {
array:[
{id: 1, component: <Donald/>, name:"My name Donald"},
{id: 2, component: <John/>, name:"My name John"},
],
currComponentId: 1
};
(3) Also I think you are trying to change the currComponentId in onClick. So it will be
<Counter>
<td
className={'active'}
onClick={ () => this.setState({currComponentId: element.id})}
>
{element.name}
</td>
</Counter>

Keys should be unique so that components maintain their identity across updates

I have A react component which renders a list of items that have been called from an API and set to setOfAllBooks state. Any time I search for the item, setOfAllBooks state is filtered through by the search ternm and the results are held in searchedBooks state. The results of searchedBooks are then passed to Table component and rendered in a list. At this point it works correctly, but when I search for another item it gets clustered in the Table. What I want to do is anytime I search a new Item after I have searched for a previos term I want the list-items in the Table component to be cleared to make way for the new items that have been searched.
import React, { Component } from 'react';
import './Home.css'
import axios from 'axios';
import Autosuggest from 'react-autosuggest';
var books = []
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
return inputLength === 0 ? [] : books.filter(book =>
book.title.toLowerCase().slice(0, inputLength) === inputValue);
};
const getSuggestionValue = suggestion => suggestion.title;
const renderSuggestion = suggestion => (
<div>
{suggestion.title}
</div>
);
const Table = ({ data }) => (
<table class="table table-hover">
<thead>
<tr class="table-primary">
<th scope="col">Title</th>
<th scope="col">Author</th>
<th scope="col">ISBN</th>
<th scope="col">No. Of Copies</th>
</tr>
</thead>
<tbody>
{data.map(row =>
<TableRow row={row} />
)}
</tbody>
</table>
)
const TableRow = ({ row }) => (
<tr class="table-light">
<th scope="row" key={row.title}>{row.title}</th>
<td key={row.author}>{row.author}</td>
<td key={row.isbn}>{row.isbn}</td>
<td key={row.isbn}>24</td>
</tr>
)
class Home extends Component {
constructor(props) {
super(props);
this.state = {
value: '',
suggestions: [],
setOfAllBooks: [],
searchedBooks: []
};
this.searchBook = this.searchBook.bind(this);
}
componentDidMount(){
axios.get('/api/book/viewAll')
.then(res => {
this.setState({ setOfAllBooks: res.data });
books = this.state.setOfAllBooks;
console.log(this.state.setOfAllBooks)
})
}
onChange = (event, { newValue }) => {
this.setState({
value: newValue
});
};
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: getSuggestions(value)
});
};
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
}
searchBook(event){
event.preventDefault();
this.setState({value: this.state.value});
this.state.searchedBooks = this.state.setOfAllBooks.filter(book => book.title == this.state.value);
this.setState({searchedBook: []})
console.log(this.state.searchedBook);
}
render() {
const { value, suggestions } = this.state;
const inputProps = {
placeholder: 'Enter the name of the book',
value,
onChange: this.onChange
}
return (
<div class="form-group col-lg-4">
<label for="exampleInputEmail1">Email address</label>
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
id="searchFor"
/>
<div className=" form-group">
<label htmlFor="searchFor"> </label>
<button class="form-control btn btn-success" type="submit" onClick={this.searchBook}>Search</button>
</div>
<Table data={this.state.searchedBooks} />
</div>
)
}
}
export default Home;
The results
The Error
You need to add the key prop to the TableRow component as <TableRow key={row.title} row={row} />. Remove the key where you have right now.
.... A good rule of thumb is that elements inside the map() call need keys.
... keys used within arrays should be unique among their siblings. . Doc.
So, it seems title what you used for key will still throw warnings, as they are not uniqe. If you have ID attribute in the row object use that. Adding key to TableRow will remove the first warning, but other warning still be there until title doesn't have the uniq values across all the data.

React JS assign separate onclick event to every row of table

I have a table which have a setting icon as the last column and whenever a user clicks on it, it should pop open a setting menu. To toggle between active class I used state and passed it to the array.map function, but what is happening is whenever a user clicks on one setting icon all the menus open simultaneously and the reason is they all have same click event and same state variable. How can I change it to where only the clicked setting icon should have its menu opened? My code is given below.
import React, { Component, PropTypes } from 'react';
import '../../../assets/custom_css/tables/unstackable_very_basic_striped_users_table.css';
import { v4 } from 'node-uuid';
import Language from '../../../assets/language';
class UnstackableVeryBasicStripedUsersTable extends Component {
static propTypes = {
rows: PropTypes.array.isRequired
};
constructor(props) {
super(props);
this.getTableRows = this.getTableRows.bind(this);
this.open_setting_menu = this.open_setting_menu.bind(this);
this.state = {
is_action_menu_active: false
};
}
getTableRows() {
const { rows } = this.props;
return rows.map(row => {
let drop_down_class = (this.state.is_action_menu_active) ? "active" : "";
let menu_class = (this.state.is_action_menu_active) ? "transition visible" : "";
return <tr key={v4()}>
{row.map(info => {
return <td key={v4()}>
{info}
</td>
})}
<td>
<div className={"ui right pointing dropdown icon " + drop_down_class} onClick={this.open_setting_menu}>
<i className="setting icon"/>
<div className={"menu " + menu_class}>
<div className="item">Edit</div>
<div className="item">Delete</div>
</div>
</div>
</td>
</tr>
});
}
open_setting_menu() {
this.setState({
is_action_menu_active: !this.state.is_action_menu_active
});
}
render() {
return <table className="ui unstackable celled very basic striped table unstackable_very_basic_striped_table">
<thead>
<tr>
<th>{Language.name}</th>
<th>{Language.role}</th>
<th>{Language.department}</th>
<th>{Language.action}</th>
</tr>
</thead>
<tbody>
{this.getTableRows()}
</tbody>
</table>
}
}
export default UnstackableVeryBasicStripedUsersTable;
If you want to use a single component, you can save the index of the selected row in the state:
import React, { Component, PropTypes } from 'react';
import '../../../assets/custom_css/tables/unstackable_very_basic_striped_users_table.css';
import { v4 } from 'node-uuid';
import Language from '../../../assets/language';
class UnstackableVeryBasicStripedUsersTable extends Component {
static propTypes = {
rows: PropTypes.array.isRequired
};
constructor(props) {
super(props);
this.getTableRows = this.getTableRows.bind(this);
this.open_setting_menu = this.open_setting_menu.bind(this);
this.state = {
selected_row_index: 0,
is_action_menu_active: false
};
}
getTableRows() {
const { rows } = this.props;
return rows.map((row, index) => {
let drop_down_class = (this.state.is_action_menu_active && this.state.selected_row_index === index) ? "active" : "";
let menu_class = (this.state.is_action_menu_active && this.state.selected_row_index === index) ? "transition visible" : "";
return <tr key={v4()}>
{row.map(info => {
return <td key={v4()}>
{info}
</td>
})}
<td>
<div className={"ui right pointing dropdown icon " + drop_down_class} onClick={() => this.open_setting_menu(index)}>
<i className="setting icon"/>
<div className={"menu " + menu_class}>
<div className="item">Edit</div>
<div className="item">Delete</div>
</div>
</div>
</td>
</tr>
});
}
open_setting_menu(index) {
this.setState({
is_action_menu_active: !this.state.is_action_menu_active,
selected_row_index: index
});
}
render() {
return <table className="ui unstackable celled very basic striped table unstackable_very_basic_striped_table">
<thead>
<tr>
<th>{Language.name}</th>
<th>{Language.role}</th>
<th>{Language.department}</th>
<th>{Language.action}</th>
</tr>
</thead>
<tbody>
{this.getTableRows()}
</tbody>
</table>
}
}
export default UnstackableVeryBasicStripedUsersTable;

React - why is this component not rendering anything?

I am trying to render some child components in a parent component but nothing is rendering. I'm not getting any console errors but there is no render. I can't figure out why this may be happening. The application I am working on is built with React, using a flux architecture.
Here is my code:
Parent Component:
import React from 'react';
import TableWithDataHeader from './TableWithDataHeader.jsx';
import TableWithDataBody from './TableWithDataBody.jsx';
import TableWithDataRowForm from './TableWithDataRowForm.jsx';
import AppStore from '../../stores/AppStore';
export default class TableWithData extends React.Component {
state = {rows: [], isEditing: false};
componentDidMount() {
let json = AppStore.getCells();
let rows = this.state.rows;
for (let key in json) {
{rows[key] = json[key]};
}
this.setState({rows});
console.log(rows);
}
handleEdit = (row) => {
this.setState({isEditing: true});
};
editStop = (formKey) => {
this.setState({isEditing: false});
};
handleSubmit = () => {
console.log('hello');
};
render() {
let {rows, isEditing} = this.state;
console.log(rows);
return (
<div>
<div className="row">
<table className="table table-striped">
<thead>
<TableWithDataHeader />
</thead>
<tbody>
{rows.map(row => this.state.isEditing ?
<TableWithDataRowForm formKey={row.id} key={row.id} editStop={this.editStop(formKey)} handleSubmit={this.handleSubmit} /> :
<TableWithDataBody key={row.id} value={row.historycells.contents} handleEdit={this.handleEdit(row)} />
)}
</tbody>
</table>
</div>
</div>
);
}
}
RowForm:
import React from 'react';
export default class TableWithDataRowForm extends React.Component {
editStop = () => {
this.props.editStop();
};
handleSubmit = (e) => {
e.preventDefault();
this.props.handleSubmit();
};
render() {
return (
<tr>
<td></td>
<td>
<button className=""><i className="btn btn-default" onClick={this.editStop}></i>Cancel</button>
<button className="btn btn-success"><i className="fa fa-cloud" onClick={this.handleSubmit}></i>Save</button>
</td>
</tr>
);
}
}
Table Head:
import React from 'react';
import AppStore from '../../stores/AppStore';
export default class TableWithDataHeader extends React.Component {
addHeaders() {
let headerArray = AppStore.getTable().columns;
let headerList = headerArray.map((element, index) => {
return (
<th key={index} id={element.id} className="text-center">{element.name}</th>
);
});
return headerList;
}
render() {
return (
<tr>
{this.addHeaders()}
<th></th>
</tr>
);
}
}
Table Body:
import React from 'react';
export default class TableWithDataBody extends React.Component {
handleEdit() {
this.props.handleEdit();
}
render() {
return (
<tr>
{this.props.histroycells.map(cell => {
return <Cell key={cell.id} value={cell.contents} />
})}
<td>
<button className="btn btn-primary" onClick={this.handleEdit}><i className="fa fa-pencil"></i>Edit</button>
</td>
</tr>
);
}
}
The table header renders fine but neither the body of the table or the edit form shows up at all!
Any help would be much appreciated, especially examples!
Thanks for you time!
Maybe this will help:
Inside your <TableWithDataBody>component, you try to access this.props.historycells, but this isn't passed as a prop.
You render your table rows with:
<TableWithDataBody
key={row.id}
value={row.historycells.contents}
handleEdit={this.handleEdit(row)} />
Maybe if you change render to:
<TableWithDataBody
key={row.id}
historycells={row.historycells} // changed this parameter
handleEdit={this.handleEdit(row)} />
UPDATE:
The line which loops over the props should still read:
{this.props.historycells.map(cell => {
PS: please also fix typo in histroycells.
You have a typo in your code. histroycells in TableWithDataBody should be historycells.

React - mapping data to rows that are formed during a map function

I have a component called Cells which renders with data that is gotten from a flux store. My problem is that I want to render this data to a specific row but because of the way I am rendering the rows (they are dynamic so you can add them to the table), I am struggling to give the rows identifiers which the cell component can render into. I hope that makes sense!
Here is the code:
Cells Component:
import React from 'react';
export default class Cells extends React.Component {
render() {
return (
<td>{this.props.value}</td>
);
}
}
Table Component:
import React from 'react';
import TableHeader from './TableHeader.jsx';
import Cells from './Cells.jsx';
import RowForm from './RowForm.jsx';
import {createRow} from '../../actions/DALIActions';
import AppStore from '../../stores/AppStore';
export default class Table extends React.Component {
state = {rows: [], cellValues: [], isNew: false, isEditing: false};
updateState = () => this.setState({cellValues: AppStore.getCellValues()});
componentWillMount() {
AppStore.addChangeListener(this.updateState);
}
handleAddRowClickEvent = () => {
let rows = this.state.rows;
rows.push({isNew: true});
this.setState({rows: rows});
};
handleEdit = (row) => {
this.setState({isEditing: true});
};
editStop = () => {
this.setState({isEditing: false});
};
handleSubmit = (access_token, id, dataEntriesArray) => {
createRow(access_token, id, dataEntriesArray);
};
componentWillUnmount() {
AppStore.removeChangeListener(this.updateState);
}
render() {
let {rows, cellValues, isNew, isEditing} = this.state;
let headerArray = AppStore.getTable().columns;
let cells = this.state.cellValues.map((value, index) => {
return (
<Cells key={index} value={value.contents} />
);
});
return (
<div>
<div className="row" id="table-row">
<table className="table table-striped">
<thead>
<TableHeader />
</thead>
<tbody>
//////////// This is where the render of the data would happen/////////////
{rows.map((row, index) => this.state.isEditing ?
<RowForm formKey={index} key={index} editStop={this.editStop} handleSubmit={this.handleSubmit} /> :
<tr key={index}>
{this.state.cellValues ? cells : null}
<td>
<button className="btn btn-primary" onClick={this.handleEdit.bind(this, row)}><i className="fa fa-pencil"></i>Edit</button>
</td>
</tr>
)}
///////////////End/////////////////
</tbody>
</table>
</div>
<div className="row">
<div className="col-xs-12 de-button">
<button type="button" className="btn btn-success" onClick={this.handleAddRowClickEvent}>Add Row</button>
</div>
</div>
</div>
);
}
}
I know this isn't probably the best way to achieve what I want (any tips on that would be appreciated as well), but its what I have to work with at the moment!
Any help would be much appreciated, especially examples!
Thanks for you time!
Instead of having one object (rows) that contains row headers, and another object that contains all cells for all rows (cellvalues), I would advise you to put the cell data inside the individual row data in some way, so that your data structure would look something like this:
rows = [
{ rowID: 100, cells: [
{ cellID: 101, value: 'data' },
{ cellID: 102, value: 'data' }
]
},
{ rowID: 200, cells: [
{ cellID: 201, value: 'data' },
{ cellID: 202, value: 'data' }
]
}
]
That way, you pass the cellValues per row, and that allows you to have different cells per row.
Make a separate component for <Row>, which renders:
return
<tr>
{this.props.cells.map( cell => {
return <Cell key={cell.cellID} value={cell.value} />
})}
</tr>
And change the <tr> bit inside your main render to:
<Row key={row.keyID} cells={row.cells}/>
Finally: it is a bad idea to use the index for keys, as in key={i}. Use a key that uniquely identifies the content of the cell/ row.
UPDATE: A typical situation is that cells or rows are first created in front-end, and only get their database ID after posting to database.
Options to still get unique IDs for the keys are:
upon creation of the cell or row in react, give the cell/row a unique ID in the form of a timestamp. After getting back the response from the database, replace the timestamp with the official database key. (this is when you do optimistic updates: show new rows temporarily, until the database gives you the official rows).
do not display the row/cell, until you get response back from server with official IDs
create a unique key by hashing the content of all cell/row contents (if you are pretty sure that cell/row contents are not likely to be identical)
Hope this helps.
You need to pass row to cells render method :
{rows.map((row, index) => this.state.isEditing ?
<RowForm formKey={index} key={index} editStop={this.editStop} handleSubmit={this.handleSubmit} /> :
<tr key={index}>
{(row.cellValues || []).map((value, index) => {
return (
<Cells key={index} value={value.contents} />
)})
}
<td>
<button className="btn btn-primary" onClick={this.handleEdit.bind(this, row)}><i className="fa fa-pencil"></i>Edit</button>
</td>
</tr>
)}
Hope this help!

Resources