React - require some aid with saving data to a table - reactjs

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.

Related

React: How to change status of toggle switch inside map?

I am trying to map a collection of keys to a list of toggle <Switch> components. The state of each toggle switch is coming from redux. When I click on the <Switch>, I expect the toggle state of the component to alternate.
I tried to call setState in the onChange handler on the <Switch>, but its not working as expected. How can I write a function that will toggle the switch?
This is what I have so far:
{Object.keys(all_product).map((item, index) => {
console.log(all_product[item]);
let product = all_product[item];
this.state.activevalue = product.is_active;
let val;
product.is_active == 1 ? (val = true) : (val = false);
this.state = { checked: val };
return (
<tr>
<th scope="row">{product.id}</th>
<td>
<img
src={`/img/${product.code}.jpg`}
alt={product.name}
class="img-fluid dataTableImg"
/>
</td>
<td>{product.name}</td>
<td>{product.brand}</td>
<td>{product.quantity}</td>
<td>{product.unit_price}</td>
<td>
<Switch
onChange={() => {
this.setState({ checked: !val });
}}
checked={this.state.checked}
value={"A"}
/>
</td>
</tr>
);
})}
If I understand your question correctly, you're getting the all_product map from a redux store. The all_products map contains an is_active field on values to determine the initial state of each <Switch> component and from there, the <Switch> is then controlled by state internal to the enclosing component.
Assuming my understanding is correct, one solution might be to read and update the checked prop of each <Switch> component via key/values tracked in the enclosing component:
/* Read checked state */
let isChecked = this.state[ item ]
/* Update (toggle) checked state */
this.setState({ [ item ] : !isChecked })
Integrating that with your code could look like this:
{Object.keys(all_product).map((item, index) => {
/* Get product */
const product = all_product[item];
/* First try to get "checked value" from state */
let isChecked = this.state[ item ]
/* If isChecked fetched from state not present or
then default to the is_active value supplied by
your redux store */
if(typeof isChecked === 'undefined') {
isChecked = product.is_active;
}
return (
<tr>
<th scope="row">{product.id}</th>
<td>
<img
src={`/img/${product.code}.jpg`}
alt={product.name}
class="img-fluid dataTableImg"
/>
</td>
<td>{product.name}</td>
<td>{product.brand}</td>
<td>{product.quantity}</td>
<td>{product.unit_price}</td>
<td>
{ /* Use locally defined isChecked to set checked
prop, and update setState to set negated isChecked
for item by key */}
<Switch
onChange={() => {
this.setState({ [ item ]: !isChecked });
}}
checked={ isChecked }
value={"A"}
/>
</td>
</tr>
);
})}
In your handleChange of Switch, pass the index/id in your callback as well. see this
import React from "react";
import Switch from "react-switch";
import update from "react-update";
class App extends React.Component {
constructor(props) {
super(props);
//Assuming you've state in your component, that might as well be your redux state
this.state = {
all_products: [{ isActive: false, id: 1 }, { isActive: true, id: 2 }]
};
}
handleChange = (activeStatus, itemId) => {
const productIndex = this.state.all_products.findIndex(function(
item,
index
) {
return item.id === itemId;
});
let products = [...this.state.all_products];
let product = { ...products[productIndex] };
product.isActive = activeStatus;
products[productIndex] = product;
//fire a redux action if all_products is stored in redux state
this.setState({ all_products: products });
};
render() {
const that = this;
console.log(this.state.all_products);
return (
<div>
{this.state.all_products.map(function(item, index) {
return (
<Switch
checked={item.isActive}
onChange={isActive => that.handleChange(isActive, item.id)}
/>
);
})}
</div>
);
}
}
export default App;

Is there something wrong with my react parent class?

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.

State not updating in Component

Hey I am trying to create a simple to-do list and I have added the components necessary. However, the state is not being updated in the Title {this.state.data.length} and the TodoList {this.state.data}. A Codepen and the relevant code is below.
https://codepen.io/skasliwal12/pen/BREYXK
const TodoForm = ({addTodo}) => {
let input;
return (
<div>
<input ref={node => {input = node;}} />
<button onClick={(e) => {
e.preventDefault();
addTodo(input.value);
input.value='';
}}> +
</button>
</div>
);
};
const TodoList = ({todos}) => {
let todoNodes = todos.map(todo => {
return <li>{todo}</li>
});
return <div> {todoNodes} </div>;
}
const Title = ({todoCount}) => {
return (
<div>
<div>
<h1>To-do App {todoCount} items</h1>
</div>
</div>
);
}
class TestApp extends React.Component {
constructor(props) {
super(props);
this.state = { data : [] }
}
addTodo(val) {
let todo = {text: val}
this.state.data.push(todo);
this.setState = ({data: this.state.data});
console.log('state updated?')
}
render(){
return (
<div>
<Title todoCount={this.state.data.length}/>
<TodoForm addTodo={this.addTodo.bind(this)}/>
<TodoList todos={this.state.data}/>
</div>
);
}
}
ReactDOM.render(<TestApp />, document.getElementById('root'));
Quite simply it is important that you DO NOT MUTATE the state like you are doing here
this.state.data.push(todo);
It is hard to debug and adds side effects that are hard to keep track of. Following your approach you should copy the state to a var, update that var and then pass it as the new field in your state. Which could work but it's also something I do not recommend. A general good approach is to to compute the new state based on the old one
// this.state.data.push(todo); You can remove this line
this.setState(prevState => ({ data: prevState.data.concat(todo) }))
This will fix your issue and avoid mutating the state, which is something you should never do, only update the state using the setState method.
I also updated your TodoList which was not displaying properly, you have to access the text field of the todo in order to show something.
const TodoList = ({todos}) => {
let todoNodes = todos.map(todo => {
return <li>{todo.text}</li>
});
return <div> {todoNodes} </div>;
}
https://codepen.io/anon/pen/MmRVmX?editors=1010

React - toggling input in a cell of a table

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

React - How can I get a re-render to happen in my table?

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.

Resources