I am practicing using react to build a simple table. Here my table has three columns. (name, job, delete). There is already some data in the table. In the third column, I want to build a button so the user can click and cancel the whole row
I already fixed several bugs but the table still does not show up
const TableBody = props => {
const rows = props.fillTheData.map((row, index) => {
return (
<tr key={index}>
<td>{row.name}</td>
<td>{row.job}</td>
<td><button onClick={() => props.removeCharacter(index)}>Delete</button></td>
</tr>
);
});
return <tbody>{rows}</tbody>;
}
class App extends React.Component {
state = {
character : [ ]
};
removeCharacter = index => {
const {character} = this.state;
this.setState({
character: character.filter((element, i) => {
return i !== index;
})
});
}
handleSubmit = character => {
this.setState({character:[...this.state.character,character]})
}
render() {
return(
<div class= "container">
<Table characters = {this.state.character} removeCharacter = {this.removeCharacter} />
<Form handleSubmit = {this.handleSubmit}/>
</div>
)
}
}
class Form extends React.Component {
constructor (props) {
super( props );
this.initialState = {
name: '',
job: ''
};
this.state = this.initialState;
}
handleChange = event => {
const{name,job} = event.target;
this.setState(
{
[name]: value
}
);
}
submitForm = () => {
this.props.handleSubmit(this.state);
this.setState(this.initialState);
}
render() {
const { name, job } = this.state;
return (
<div class="container2">
<form>
<label>Name</label>
<input
type="text"
name="name"
value={name}
onChange={this.handleChange} />
<label>Job</label>
<input
type="text"
name="job"
value={job}
onChange={this.handleChange}/>
</form>
<input
type="button"
value="Submit"
onClick={this.submitForm} />
</div>
);
}
}
export default Form;
class Table extends React.Component {
render(){
const {characters, removeCharacter} = this.props;
return(
<table>
<TableHeader />
<TableBody fillTheData = {characters} removeCharacter= {removeCharacter} />
</table>
)
}
}
const TableHeader = () => {
return (
<thead>
<tr>
<th>Name</th>
<th>Job</th>
<th>Delete</th>
</tr>
</thead>
);
}
Right now, we have a cool Table component, but the data is being hard-coded. One of the big deals about React is how it handles data, and it does so with properties, referred to as props, and with state. First, we’ll focus on handling data with props.
Then let’s move all that data to an array of objects, as if we were bringing in a JSON-based API. We’ll have to create this array inside our render().
The handleChange method of Form component is declaring a constant named job which doesn't exist in event.target object and it is setting the state with value variable which doesn't exists in that scope.
So change
const{name,job} = event.target;
to
const{name,value} = event.target;
your code will just work fine.
Related
export default function App() { return(<div><customButton/><customTable/></div>) }
export default function customButton() { return(<div><button>update</button></div>) }
Since the app is the parent component and button and table are child components. I know this is not the best practice but how do I update(re-render) table component from the button component?
The way to do this would be to have some shared state at the closest ancestor. In the example below, I create a value state variable in App and pass it to the customTable element. I have a setValue setter that is passed to customButton. When the button is clicked, the value is updated and passed to customTable, causing the table to re-render with that new value.
export default function App() {
const [value, setValue] = React.useState(0);
return(
<div>
<customButton setValue={setValue} />
<customTable value={value} />
</div>
)
}
export default function customButton({ setValue }) {
return(
<div>
<button onClick={() => setValue(v => v + 1)}>update</button>
</div>
)
}
Sample application using custom table and custom button.
function CustomButton(props) {
return <button onClick={props.addClick}>{props.children}</button>
}
function CustomTable({ table }) {
return (
<table>
<thead>
<tr>
<th>No.</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{table.map((item, i) => (
<tr key={i.toString()}>
<td>{i.toString()}</td>
<td>{item.name}</td>
</tr>
))}
</tbody>
</table>
);
}
function App() {
const [input, setInput] = React.useState('')
const [table, setTable] = React.useState([
{ name: "John" },
{ name: "Bukky" },
{ name: "Behem" }
]);
const handleInput = e=>{
setInput(e.target.value)
}
const addClick = e => {
const update = [...table, { name: input }];
setTable(update);
setInput('')
};
return (
<div>
<input type="text" value={input} onChange={handleInput}/>
<CustomButton addClick={addClick}>Click to Add</CustomButton>
<CustomTable table={table} />
</div>
);
}
Supposing I have this parent component.
Parent.jsx
render() {
const headers = ["id","desc1", "desc2", "actions"];
return(
<div>
<input type = "text" placeholder = "Product Brand" value={ this.state.desc }/>
<input type = "text" placeholder = "Product Brand" value={ this.state.desc2 }/>
<button type = "button" onClick = { this.handleSubmit.bind(this) }>Add</button>
<CustomTable mystate = { this.state } header = { headers } data = { this.props.store.productbrandlist }/>
</div>
)
}
and this CustomTable.jsx
renderHeaders(){
return(
this.props.header.map(function(header, index){
return <th key={index}>{header}</th>
})
)
}
renderRows(){
// console.log("here1");
// return(
// <ListItem datarows = { this.props.data }/>
// )
return this.props.data.map((list, id) => <ListItem mystate = {this.props.mystate} key_id={id} key={id} col = {list}/> )
}
render(){
return(
<table className="table">
<thead>
<tr>{ this.renderHeaders() }</tr>
</thead>
<tbody>
{ this.renderRows() }
</tbody>
</table>
)
}
and this component which render the rows of the table
render(){
return(
<tr key = { this.props.key_id }>
{ this.renderColumns() }
{ this.renderActions() }
</tr>
)
}
renderColumns(){
var columns = []
for (var key in this.props.col)
{
if (this.state.isEditing){
columns.push(<td key = {key.id}><input ref = "txt" type = "text" value = { this.state.itemValue } onChange = { this.onTextChange.bind(this) } /></td>)
}
else
{
columns.push(<td key = {key.id}>{ this.props.col[key] }</td>)
// this.setState({
// itemValue: key,
// isEditing: false
// })
}
}
return columns
}
renderActions(){
if (this.state.isEditing){
return (
<td>
<button type="button" onClick = { this.handleSaveClick.bind(this) }>Save</button>
<button type="button" onClick = { this.handlCancelClick.bind(this) }>Cancel</button>
</td>
)
}
return(
<td>
<button type="button" onClick = { this.handleEditClick.bind(this) }>Edit</button>
<button type="button" onClick = { this.handleDeleteClick.bind(this) }>Delete</button>
</td>
)
}
My question is how do I configure it in such a way that when I click on the button edit which is created in the ListItem Component. The data will be displayed in the inputbox which is created in the parent.jsx
Looking at your code, you just simply need to pass a reference to a parent method to the desired child, through props. I don't have your full code, so this is not tested, but should give you an idea on how to do it.
If there was another child layer between your parent and the ListItem, I would definitely encourage using Redux. Personally I'm ok with passing references two levels deep using props for simple projects.
To get your ListItem values to show up in the parent input fields, make the following changes:
In your Parent:
// you need two-way data binding between state and input values
onChange(){
this.setState({
desc: this.refs.desc1.value,
desc2: this.refs.desc2.value
});
}
// this method will get triggered by ListItem's edit button onClick
onRowEdit(desc1, desc2){
this.setState({
desc: desc1,
desc2: desc2
});
}
render() {
const headers = ["id","desc1", "desc2", "actions"];
return(
<div>
<input ref="desc1" type = "text" placeholder = "Product Brand" value={ this.state.desc } onChange={this.onChange.bind(this)} />
<input ref="desc2" type = "text" placeholder = "Product Brand" value={ this.state.desc2 } onChange={this.onChange.bind(this)/>
<button type = "button" onClick = { this.handleSubmit.bind(this) }>Add</button>
<CustomTable onEdit={ this.onRowEdit } mystate = { this.state } header = { headers } data = { this.props.store.productbrandlist }/>
</div>
)
}
Your custom table renderRows:
renderRows(){
// console.log("here1");
// return(
// <ListItem datarows = { this.props.data }/>
// )
return this.props.data.map((list, id) => <ListItem onEdit={this.props.onEdit} mystate = {this.props.mystate} key_id={id} key={id} col = {list}/> )
}
Finally in your ListItem inside handleEditClick method call the function passed in the props:
handleEditClick(){
// const desc1 = ...
// const desc2 = ...
this.props.onEdit(desc1, desc2); // this call will cause desc1, desc2 values to show up in the parent's input fields.
}
I have a table where I would like it to be possible to click on a cell and for that to toggle an input that allows you to change the data in that cell. At the moment I am just rendering the input straight off the bat but ideally I don't want this. What I would really like is to show the current data in the cells and then when you click on the cell it becomes the input and allows you to edit the data.
Table Component:
import React from 'react';
import TableWithDataHeader from './TableWithDataHeader.jsx';
import Row from './Row.jsx';
import {createRowHistory} from '../../actions/DALIActions';
import TokenStore from '../../stores/TokenStore';
import TableDataStore from '../../stores/TableDataStore';
export default class Table extends React.Component {
state = {data: TableDataStore.getCells().historycells};
handleSubmitEvent = (e) => {
e.preventDefault();
console.log(this.state.data);
let data = this.state.data;
let dataEntriesArray = [];
for (let key in data) {
if (data.hasOwnProperty(key)) {
dataEntriesArray.push({contents: data[key]});
}
}
console.log(dataEntriesArray);
let access_token = TokenStore.getToken();
let row_id = TableDataStore.getRowId();
createRowHistory(access_token, row_id, dataEntriesArray);
};
handleChangeEvent = (value, cell) => {
let newState = this.state.data.slice(0);
console.log(newState);
newState[cell] = value;
console.log(newState);
this.setState({data: newState});
console.log(this.state.data);
};
render() {
let {data} = this.state;
return (
<div>
<div className="row">
<table className="table table-striped table-bordered">
<thead>
<TableWithDataHeader />
</thead>
<tbody>
<Row data={this.state.data} handleSubmitEvent={this.handleSubmitEvent} handleChangeEvent={this.handleChangeEvent} />
</tbody>
</table>
</div>
</div>
);
}
}
Row Component:
import React from 'react';
import TableDataStore from '../../stores/TableDataStore';
export default class Row extends React.Component {
render() {
let cells = this.props.data.map((el, i) => {
return (
<td key={i}>
<input type="text" className="form-control" id={el.id} defaultValue={el.contents} onChange={(e) => {
this.props.handleChangeEvent(e.target.value, i)
}} />
</td>
);
});
cells.push(
<td className="dtable-button" key={this.props.data.length}>
<button className="btn btn-primary" onClick={this.props.handleSubmitEvent}>Submit</button>
</td>
);
return (
<tr id={TableDataStore.getRowId()}>{cells}</tr>
);
}
}
Can I just simply toggle a state that will switch between a default show state (a <div>?) and the input that I already have? Or do I need to do something a little more involved?
Any help would be much appreciated!
Thanks for your time
One approach would be to create a Cell component that renders a div, but when clicked, renders an input instead.
import React from 'react';
class Cell extends React.Component {
constructor(props) {
super(props);
this.state = { editing: false };
}
render() {
const { value, onChange } = this.props;
return this.state.editing ?
<input ref='input' value={value} onChange={e => onChange(e.target.value)} onBlur={() => this.onBlur()} /> :
<div onClick={() => this.onFocus()}>{value}</div>
}
onFocus() {
this.setState({ editing: true }, () => {
this.refs.input.focus();
});
}
onBlur() {
this.setState({ editing: false });
}
}
Then you can create your table rows like this:
let cells = data.map((el, i) => (
<td key={i}>
<Cell
value={el.contents}
onChange={v => { this.props.handleChangeEvent(v, i) }}
/>
</td>
));
You can it like these.
let cells = this.props.data.map((el, i) => {
return (
<td key={i}>
{
(el.editable)?
<input type="text" className="form-control" id={el.id} defaultValue={el.contents} onChange={(e) => {
this.props.handleChangeEvent(e.target.value, i)
}} />:
<div onClick={onCellClick(i){}}>{el.contents}</div>
}
</td>
);
});
editable flag should be toggled using onCellClick(i){} & this.props.handleChangeEvent(e.target.value, i) functions. u should update the approprite state based on the index you have here
I have a table which starts off with data from a store which can then be edited by row. When the editing has been completed, a button is clicked which saves the new values and should present them in the table. However, this is not happening for me. I have been stuck on this for a while now and have tried various things but none of them have worked. I think it may be a problem with the state but I can't figure out how to change it to make it work!
The code I have currently is:
Table:
import React from 'react';
import TableWithDataHeader from './TableWithDataHeader.jsx';
import TableWithDataBody from './TableWithDataBody.jsx';
import TableWithDataRowForm from './TableWithDataRowForm.jsx';
import {updateRowHistory} from '../../actions/DALIActions';
import AppStore from '../../stores/AppStore';
export default class TableWithData extends React.Component {
state = {rows: [], isEditing: false, input: null};
componentDidMount() {
let rows = this.state.rows;
rows.push({id: AppStore.getRowId(), cells: AppStore.getCells().historycells});
this.setState({rows});
console.log(rows);
}
handleEdit = (row) => {
this.setState({isEditing: true});
};
handleInputChange = (newCellValuesArray) => {
let input = this.state.input;
input = newCellValuesArray;
this.setState({input});
};
editStop = (row) => {
this.setState({isEditing: false});
};
handleSubmit = (access_token, row_id) => {
let newCellValuesArray = this.state.input;
updateRowHistory(access_token, row_id, newCellValuesArray);
this.setState({isEditing: false});
};
render() {
let {rows, isEditing, input} = this.state;
return (
<div>
<div className="row">
<table className="table table-striped">
<thead>
<TableWithDataHeader />
</thead>
<tbody>
{rows.map(row => this.state.isEditing ?
<TableWithDataRowForm
key={row.id}
cells={row.cells}
editStop={this.editStop.bind(null, row.id)}
handleSubmit={this.handleSubmit}
handleInputChange={this.handleInputChange}
/>
:
<TableWithDataBody
key={row.id}
cells={row.cells}
handleEdit={this.handleEdit.bind(null, row.id)}
/>
)}
</tbody>
</table>
</div>
</div>
);
}
}
Data table starts with:
import React from 'react';
export default class TableWithDataBody extends React.Component {
state = {cells: this.props.cells};
handleEdit = () => {
this.props.handleEdit();
};
render() {
let {cells} = this.state;
return (
<tr>
{cells.map(cell => {
return <td key={cell.id} className="text-center">{cell.contents}</td>
})}
<td>
<button className="btn btn-primary" onClick={this.handleEdit}><i className="fa fa-pencil"></i>Edit</button>
</td>
</tr>
);
}
}
In-row edit form:
import React from 'react';
import AppStore from '../../stores/AppStore';
export default class TableWithDataRowForm extends React.Component {
state = {cells: this.props.cells, newCellValues: []};
onChange = (e) => {
let newCellValues = this.state.newCellValues;
newCellValues[e.target.id] = e.target.value;
this.setState({newCellValues});
console.log(newCellValues);
let newCellValuesArray = [];
for (let key in newCellValues) {
if (newCellValues.hasOwnProperty(key)) {
newCellValuesArray.push({contents: newCellValues[key]});
}
}
console.log(newCellValuesArray);
this.props.handleInputChange(newCellValuesArray);
};
editStop = () => {
this.props.editStop();
};
handleSubmit = (e) => {
e.preventDefault();
let access_token = AppStore.getToken();
let row_id = AppStore.getRowId();
this.props.handleSubmit(access_token, row_id);
};
render() {
let {cells, newCellValues} = this.state;
return (
<tr>
{cells.map(cell => {
return <td key={cell.id} className="text-center"><input type="text" className="form-control" id={cell.id} defaultValue={cell.contents} onChange={this.onChange} /></td>
})}
<td>
<button className="btn btn-default"><i className="fa fa-ban" onClick={this.editStop}></i>Cancel</button>
<button className="btn btn-success"><i className="fa fa-cloud" onClick={this.handleSubmit}></i>Save</button>
</td>
</tr>
);
}
}
Help with examples would be much appreciated, sorry if the code is a bit of a mess right now!
Thanks for your time
I might be missing something, but I don't see where you're editing the rows state? The change handler just changes the input, and you aren't passing the input down to the data table.
The only time I see rows being set is in componentDidMount, which explains why it's being populated, but not changed.
I have a table with dynamically generated column headers and rows, a user can enter in data to the rows and when they click save the data should be saved into that row but at the moment it isn't saving the values to the table but it is saving them on my database. Ideally I would like it so that when the save button is clicked the data will be saved to the row and is then viewable in that row (if that makes any sense).
Here is the code I am working with (I know it is a mess at the moment!):
Code for data input form:
import React from 'react';
import AppStore from '../../stores/AppStore';
export default class RowForm extends React.Component {
state = {dataEntries: []};
onChange = (event, element) => {
let dataEntries = this.state.dataEntries;
dataEntries[element] = event.target.value;
this.setState({dataEntries});
};
editStop = () => {
this.props.editStop();
};
handleSubmit = (e) => {
e.preventDefault();
let access_token = AppStore.getToken();
let id = AppStore.getTable().id;
let dataEntries = this.state.dataEntries;
let dataEntriesArray = [];
for (let key in dataEntries) {
if (dataEntries.hasOwnProperty(key)) {
dataEntriesArray.push({contents: dataEntries[key]});
}
}
this.props.handleSubmit(access_token, id, dataEntriesArray);
};
componentDidMount() {
let nameArray = AppStore.getTable().columns.map((obj) => {
return obj.name;
});
let dataEntries = nameArray.reduce((obj, name) => {
obj[name] = null;
return obj;
}, {});
this.setState({dataEntries});
}
render() {
let {dataEntries} = this.state;
return (
<tr>
{Object.keys(dataEntries).map((element) => {
return (
<td key={element}><input type="text" className="form-control" id={element} placeholder="enter data" value={dataEntries[element]} onChange={event => this.onChange(event, element)} /></td>
);
})}
<td>
<button className="btn btn-default" onClick={this.editStop}><i className="fa fa-ban"></i>Cancel</button>
<button className="btn btn-success" onClick={this.handleSubmit}><i className="fa fa-check"></i>Save</button>
</td>
</tr>
);
}
After the data has been entered and submitted (it is an array of objects like dataEntriesArray = [{contents: "value"}, {contents: "value"}, {contents: "value"}, {contents: "value"}].
And here is how I am rendering the table (this is where the problem is I think):
import React from 'react';
import TableHeader from './TableHeader.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: [], isNew: false, isEditing: false};
handleAddRowClickEvent = () => {
let rows = this.state.rows;
rows.push({isNew: true});
this.setState({rows: rows, isEditing: false});
};
handleEdit = (row) => {
this.setState({isEditing: true});
};
editStop = () => {
this.setState({isEditing: false});
};
handleSubmit = (access_token, id, dataEntriesArray) => {
createRow(access_token, id, dataEntriesArray);
};
render() {
let {rows, isNew, isEditing} = this.state;
let headerArray = AppStore.getTable().columns;
return (
<div>
<div className="row" id="table-row">
<table className="table table-striped">
<thead>
<TableHeader />
</thead>
<tbody>
{rows.map((row, index) => this.state.isEditing ?
<RowForm formKey={index} key={index} editStop={this.editStop} handleSubmit={this.handleSubmit} /> :
<tr key={index}>
{headerArray.map((element, index) => {
return (
<td key={index} id={element.id}></td>
);
})}
<td>
<button className="btn btn-primary" onClick={this.handleEdit.bind(this, row)}><i className="fa fa-pencil"></i>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={this.handleAddRowClickEvent}>Add Row</button>
</div>
</div>
</div>
);
}
}
I am using flux at the moment, and would ideally like to keep using it for now (I know about redux but I would ideally like to get it working in flux before I start refactoring my code). I suspect it is a problem with the way I am rendering my table.
Any help would be much appreciated, especially examples!
Thanks for you time!
It looks like you probably want to extract your table data into your store, your UI child elements trigger change events, your store then updates its data and trigger change events which your parent component can listen for and update.
Something like the following simplified example, which mutates array elements:
class Store extends EventEmitter {
constructor() {
super()
this.data = [ 'a', 'b', 'c' ]
}
onChange() {
this.emit( 'update', this.data )
}
mutate( index, value ) {
this.data[ index ] = value
this.onChange()
}
}
var store = new Store()
class ChildComponent extends React.Component {
constructor( props ) {
super( props )
}
// You probably want to use a dispatcher rather than directly accessing the store
onClick = event => {
store.mutate( this.props.index, this.props.value + 'Z' )
}
render() {
return <button onClick={ this.onClick }>{ this.props.value }</button>
}
}
class ParentComponent extends React.Component {
constructor( props ) {
super( props )
// You probably want to be smarter about initially populating state
this.state = {
data: store.data
}
}
componentWillMount() {
store.on( 'update', data => this.setState({ data: data }) )
}
render() {
let cells = this.state.data.map( ( value, index ) => <ChildComponent index={ index } value={ value } /> )
return (
<div>
{ cells }
</div>
)
}
}
For brevity child components here directly tell the store to change values, you'd probably want to dispatch messages/actions and have the store decide how to respond, the key is simply that the store data is passed to the parent component, which updates its state and triggers a re-render.
The flow here is that essentially the UI is dumb, it simply renders the data that it gathers from the store and dispatches a message to tell the store to update/mutate when a user action is detected (in this case a button press but it sound like you'll need an input of some sort), when the data in the store changes it emits (or could use a dispatcher also) a change event which forces the UI to re-render the new state. As child components are re-rendered at this stage they are populated with the new data state, ensuring that your UI remains consistent.