I'm learning react and I'm trying to figure out how I would put this information into a table. I know there is
<table></table>
in html so I'm assuming I would use something like that within the javascript. I'm trying to create a table where the month of the birthday contains the name and the date of each person.
Any advice/help would be great!
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
birthdays: {
'January': [{
name: 'Mike',
date: '1/14/90'
}, {
name: 'Joe',
date: '1/7/92'
}],
March: [{
name: 'Mary',
date: '3/7/88'
}]
}
}
}
render() {
return (
<div>
{Object.keys(this.state.birthdays).reduce((birthdays, month) => {
return birthdays.concat(this.state.birthdays[month].map((bday) => {
return (
<p>{bday.name} - {bday.date}</p>
);
}));
}, [])}
</div>
);
}
ReactDOM.render(<App />,
document.getElementById('container'));
Excuse the formatting of the code.
You need to first map the keys and then iterate over the birthdays object. Also put single quotes on March like 'March'
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
birthdays: {
'January': [{
name: 'Mike',
date: '1/14/90'
}, {
name: 'Joe',
date: '1/7/92'
}],
'March': [{
name: 'Mary',
date: '3/7/88'
}]
}
}
}
renderTableRows = () => {
var rows = [];
Object.keys(this.state.birthdays).forEach((month, key) => { var birthdays = this.state.birthdays[month];
birthdays.forEach((obj) => {
rows.push (
<tr>
<td>{month}</td>
<td >{obj.name}</td>
<td>{obj.date}</td>
</tr>
)
})
})
var tby = null;
tby = <tbody>
{rows.map((obj, key) => {
return (
obj
)
})}
</tbody>
return tby;
}
render() {
return (
<div>
<table className="table table-striped">
<thead>
<tr>
<th>Month</th>
<th>Name</th>
<th>Date</th>
</tr>
</thead>
{this.renderTableRows()}
</table>
</div>
);
}
}
ReactDOM.render(<App />,
document.getElementById('container'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.js"></script>
<div id="container"></div>
You're using jsx which is an extension of javascript by including html tags, this means that you can use them within your javascript code.
You need to open the <table> tag before iterating over your birthdays so you can create the <tr> (rows) inside it.
return <table>
{
birthdays.map((bday) => (
<tr>
<td>{bday.name}</td>
<td>{bday.date}</td>
</tr>
);
}
</table>
I suggest you read more about table elements in html.
One way of presenting your data may be as follows: http://codepen.io/PiotrBerebecki/pen/jrVkdO
Here is the code for the React component class:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
birthdays: {
'January': [{
name: 'Mike',
date: '1/14/90'
}, {
name: 'Joe',
date: '1/7/92'
}],
March: [{
name: 'Mary',
date: '3/7/88'
}]
}
}
}
render() {
console.log();
return (
<table>
<thead>
<th>
<td>Month</td>
<td>Name</td>
<td>Birthday</td>
</th>
</thead>
<tbody>
{Object.keys(this.state.birthdays).map((month, key) => {
return (
<tr key={key}>
{this.state.birthdays[month].map((person, index) => {
return (
<tr key={String(key) + String(index)}>
<td>{month}</td>
<td>{person.name}</td>
<td>{person.date}</td>
</tr>
);
})}
</tr>
)
})}
</tbody>
</table>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('container')
);
Related
I am trying to create a currency converter and I want to show a list with the currency and the value. I am getting some data using axios from an API and my data looks like this:
Object {type: "li", key: "0", ref: null, props: Object, _owner: null…}
type: "li"
key: "0"
ref: null
props: Object
children: Array[33]
0: Object
name: "AUD"
value: 1.5167896679
1: Object
2: Object
...
When I try to map the data it is not showing anything:
import React from "react";
import axios from "axios";
class Table extends React.Component {
constructor(props) {
super(props);
this.state = {
result: null,
fromCurrency: "USD",
currencyList: []
};
}
componentDidMount() {
this.getCoinTypes();
}
getCoinTypes() {
let currency = [];
axios
.get(
`https://alt-exchange-rate.herokuapp.com/latest?base=${
this.state.fromCurrency
}`
)
.then(response => {
currency.push(
Object.keys(response.data.rates).map(k => ({
name: k,
value: response.data.rates[k]
}))
);
this.setState({
currencyList: currency
});
this.state.currencyList.map((val, index) => {
return console.log(<li key={index}>{val}</li>);
});
})
.catch(function(error) {
console.log(error);
});
}
render() {
return (
<table className="table table-sm bg-light mt-4">
<thead>
<tr>
<th scope="col" />
<th scope="col" className="text-right pr-4 py-2">
5.00
</th>
</tr>
</thead>
<tbody>
{this.state.currencyList.map((val, index) => {
<tr key={index}>
<th>{index}</th>
<th>{val}</th>
</tr>;
})}
</tbody>
</table>
);
}
}
export default Table;
What I am want to achieve is e.g:
USD 1.00
AUD 1.95
and so on...
The link to sandbox
Is this what you needed? https://codesandbox.io/s/react-props-practice-tsvbu
If yes, what I did was to save the currency "label" (USD, AUD, etc) and it's "value" (1.00, 1.69, etc)
Here is a working code sandbox
https://codesandbox.io/embed/react-props-practice-94t49?fontsize=14&hidenavigation=1&theme=dark
Changed this on line 60 of Table.jsx
{this.state.currencyList.map((val, index) => {
return (
<tr key={index}>
<th>{index}</th>
<th>{val.value}</th>
</tr>
);
})}
My scenario is that I have a table that is generated based on data. In one of the columns I would like to pass in a 'remove' button/component, which is meant to remove the row that it is in.
My issue is that the 'remove' button component needs to be given the row so it can determine which data to remove.
If you look in Table.js, you can see where I render the prop as a component '{col.component}' - But how can I also pass values to the components action?Examples below.
App.js
import React, { Component } from 'react';
import Table from './Table';
import Table from './RemoveButton';
class App extends Component {
//Data is the array of objects to be placed into the table
let data = [
{
name: 'Sabrina',
age: '6',
sex: 'Female',
breed: 'Staffordshire'
},
{
name: 'Max',
age: '2',
sex: 'Male',
breed: 'Boxer'
}
]
removeRow = name => {
//Remove object from data that contains name
}
render() {
//Columns defines table headings and properties to be placed into the body
let columns = [
{
heading: 'Name',
property: 'name'
},
{
heading: 'Age',
property: 'age'
},
{
heading: 'Sex',
property: 'sex'
},
{
heading: 'Breed',
property: 'breed'
},
{
heading: '',
component: <RemoveButton action=removeRow()/>
}
]
return (
<>
<Table
columns={columns}
data={data}
propertyAsKey='name' //The data property to be used as a unique key
/>
</>
);
}
}
export default App;
RemoveButton.js
import React from 'react';
const RemoveButton = action => {
return(
<button onClick={action}>Remove Row</button>
)
}
export default RemoveButton;
Table.js
const Table = ({ columns, data, propertyAsKey }) =>
<table className='table'>
<thead>
<tr>{columns.map(col => <th key={`header-${col.heading}`}>{col.heading}</th>)}</tr>
</thead>
<tbody>
{data.map(item =>
<tr key={`${item[propertyAsKey]}-row`}>
{columns.map(col => {
if(col.component){
return(<td> key={`remove-${col.property}`}>{col.component}</td>)
} else {
return(<td key={`${item[propertyAsKey]}-${col.property}`}>{item[col.property]}</td>)
}
})}
</tr>
)}
</tbody>
</table>
Instead of passing down a component in the column, you could pass down the removeRow function to the Table component as a regular prop, and have another value on the remove column to indicate when you should render the remove button for that column, and pass the item name when you invoke it.
class App extends React.Component {
state = {
data: [
{
name: "Sabrina",
age: "6",
sex: "Female",
breed: "Staffordshire"
},
{
name: "Max",
age: "2",
sex: "Male",
breed: "Boxer"
}
]
};
removeRow = name => {
this.setState(({ data }) => ({
data: data.filter(el => el.name !== name)
}));
};
render() {
let columns = [
{
heading: "Name",
property: "name"
},
{
heading: "Age",
property: "age"
},
{
heading: "Sex",
property: "sex"
},
{
heading: "Breed",
property: "breed"
},
{
heading: "",
removeCol: true
}
];
return (
<Table
columns={columns}
data={this.state.data}
removeRow={this.removeRow}
propertyAsKey="name"
/>
);
}
}
const Table = ({ columns, data, removeRow, propertyAsKey }) => (
<table className="table">
<thead>
<tr>
{columns.map(col => (
<th key={`header-${col.heading}`}>{col.heading}</th>
))}
</tr>
</thead>
<tbody>
{data.map(item => (
<tr key={`${item[propertyAsKey]}-row`}>
{columns.map(col => {
if (col.removeCol) {
return (
<td key={`remove-${col.property}`}>
<button onClick={() => removeRow(item.name)}>
Remove Row
</button>
</td>
);
} else {
return (
<td key={`${item[propertyAsKey]}-${col.property}`}>
{item[col.property]}
</td>
);
}
})}
</tr>
))}
</tbody>
</table>
);
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Im creating a table from JSON data, and main problem is I can't use object keys to map it.
My json something like this:
[{
key: 'val',
key2: 'val',
key3: 'val'
},
{
key: 'val',
key2: 'val',
key3: 'val'
}]
Here is how i implemented header with columns:
class Table extends Component {
render() {
const header = this.props.data.slice(0, 1);
return (<table>
<thead>
<TableHead children={header}/>
</thead>
<tbody>
<TableBody children={this.props.data}/>
</tbody>
</table>)
}
}
export default Table;
class TableHead extends Component {
render() {
return (
<tr>
{this.props.children.map((header) => {
return Object.keys(header).map((el) => {
return <th>{el}</th>
})
})}
</tr>
)
}
}
export default TableHead;
But I can't understand how to map my table body iterating over objects...
I sliced my JSON for header, but I can't do this with data, and my table looks like
class TableBody extends Component {
render() {
const row = this.props.children.map((row) => {
return Object.values(row).map((el,i) => {
if (i%Object.values(row).length===0) {
return <tr><td>{el}</td></tr>
}else{
return <td>{el}</td>
}
})
});
return (
<tbody>
{row}
</tbody>
)
}
}
export default TableBody;
I would extract the keys and re-use when mapping over the rows for the TableBody.
Something like
class Table extends Component {
render() {
const { data } = this.props;
const columns = Object.keys(data[0]);
return (
<table>
<thead>
<TableHead columns={columns} />
</thead>
<tbody>
<TableBody columns={columns} data={data} />
</tbody>
</table>
);
}
}
class TableHead extends Component {
render() {
const { columns } = this.props;
return (
<tr>
{columns.map(header => {
return <th>{header}</th>;
})}
</tr>
);
}
}
class TableBody extends Component {
render() {
const { columns, data } = this.props;
return data.map(row => (
<tr>
{columns.map(cell => (
<td>{row[cell]}</td>
))}
</tr>
));
}
}
export class UsersTable extends React.Component {
constructor() {
super();
this.state = {
info: null
};
}
componentWillMount() {
fetch("http://localhost:8081/milltime/getUsers")
.then(res => res.json())
.then(info => {
this.setInfo(info);
});
}
setInfo(info) {
const state = this.state;
state['info'] = info;
this.setState(state);
}
render() {
const info = this.state.info;
if (!this.state.info) {
return null;
}
let listItems = [];
for (var i = 0; i < info['mta:getUsersResponse']['mta:users'].length; i++) {
listItems.push(
<tr>
<td>{info['mta:getUsersResponse']['mta:users'][i]['mta:UserId']}</td>
<td>{info['mta:getUsersResponse']['mta:users'][i]['mta:FullName']}</td>
<td>{info['mta:getUsersResponse']['mta:users'][i]['mta:CostHour']}</td>
</tr>);
}
return(
<div className="usersTable">
<Table striped bordered condensed responsive hover>
<thead>
<tr>
<th>Id</th>
<th>Full Name</th>
<th>Hour cost</th>
</tr>
</thead>
<tbody>
{listItems}
</tbody>
</Table>
</div>
);
}
}
This is the code I have for a table that get users and displays 3 columns of data. What I am having problems doing is being able to select a table and by selecting that table get the data in that cell and use it to search with the help of the id of the user in the selected cell. Has anyone got a neat solution? I'm using React bootstrap.
Bind your onClick handler when your creating the row.
See comments in code.
https://reactjs.org/docs/handling-events.html
export class UsersTable extends React.Component {
constructor() {
super();
this.state = {
info: null
};
}
componentWillMount() {
fetch("http://localhost:8081/milltime/getUsers")
.then(res => res.json())
.then(info => {
this.setInfo(info);
});
}
setInfo(info) {
const state = this.state;
state['info'] = info;
this.setState(state);
}
onSelectedRow(user, clickEvent){
//your user object and the click event
//clickEvent.currentTarget = the cell clicked
}
render() {
const info = this.state.info;
if (!this.state.info) {
return null;
}
let listItems = [];
for (var i = 0; i < info['mta:getUsersResponse']['mta:users'].length; i++) {
const user = info['mta:getUsersResponse']['mta:users'][i]; //dryer
//Bind you onclick handler to the context and you user object (or id if thats what you want)
listItems.push(
<tr onClick={this.onSelectedRow.bind(this, user)}>
<td>{user['mta:UserId']}</td>
<td>{user['mta:FullName']}</td>
<td>{user['mta:CostHour']}</td>
</tr>);
}
return(
<div className="usersTable">
<Table striped bordered condensed responsive hover>
<thead>
<tr>
<th>Id</th>
<th>Full Name</th>
<th>Hour cost</th>
</tr>
</thead>
<tbody>
{listItems}
</tbody>
</Table>
</div>
);
}
}
Api requests should be handled in componentDidMount lifecycle event as described in React docs.
Also, your are mutating your state on setInfo and this is not a good practice either. You can directly update your state like:
setInfo(info) {
this.setState({
info: info,
})
}
or simply using object shorthand
setInfo(info) {
this.setState({
info,
})
}
Should your api change in the future, you are gonna have problems replacing all the mta:** in your code. Why don't you map them upon state?
this.setState({
info: {
users: info['mta:getUsersResponse']['mta:users'].map(user => ({
id: user['mta:UserId'],
fullName: user['mta:FullName'],
costHour: user['mta:CostHour'],
}))
}
})
Click handling becomes easier from now, just create a UserRow component, send user as prop and propagate changes on onClick.
const UserRow = ({ user, onClick }) =>
<tr onClick={onClick}>
<td>{user.id}</td>
<td>{user.fullName}</td>
<td>{user.costHour}</td>
</tr>
Now you can map though your state and propagate props to it:
const UserRow = ({ user, onClick }) =>
<tr onClick={onClick}>
<td>{user.id}</td>
<td>{user.fullName}</td>
<td>{user.costHour}</td>
</tr>
class App extends React.Component {
constructor() {
super()
this.state = {
info: {
users: [
{ id: 0, fullName: 'User 1', costHour: 100 },
{ id: 1, fullName: 'User 2', costHour: 50 },
{ id: 2, fullName: 'User 3', costHour: 150 }
]
}
}
this.handleUserClick = this.handleUserClick.bind(this)
}
handleUserClick(user) {
console.log(`user ${user.id} has been clicked`)
}
render() {
return (
<div className="usersTable">
<table striped bordered condensed responsive hover>
<thead>
<tr>
<th>Id</th>
<th>Full Name</th>
<th>Hour cost</th>
</tr>
</thead>
<tbody>
{this.state.info.users.map(user =>
<UserRow
key={user.id}
user={user}
onClick={this.handleUserClick.bind(this, user)}
/>
)}
</tbody>
</table>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
I am building a form to edit a key/value pair object, see picture:
This is the object that I get from the backend that I stored in the container component.
items: [{
name: "Product Name",
sku: "00001",
attributes: {
width: "value",
height: "value",
length: "value",
color: "value"
}
},
{
name: "Product Name 2",
sku: "00002",
attributes: {
description: "Hello World",
weight: "250",
quantity: "500",
hello: "World"
}
}]
I then pass the data into a child component via props. This is the code for the form:
class EditForm extends Component {
render() {
let editForm = this.props.items && this.props.items.map((item, idx) => {
return(
<tr key={item.sku}>
<td className="edit-table">
<input
value={item.name}
name={item.name}
onChange={(e)=> this.props.onChange(e, idx)}
/>
</td>
<td className="edit-table">
<ul className="item-attributes">
{Object.keys(item.attributes).map((key) => {
return (<li key={key}>
<label>{key}</label>
<input
value={item.attributes[key]}
onChange={(e) => this.props.onChange(e, idx) }
/>
</li>)
})}
</ul>
</td>
</tr>
)
})
return(
<table className="editcontainer-table">
<thead>
<tr>
<th>SKU</th>
<th>Attributes</th>
</tr>
</thead>
<tbody>
{editForm}
</tbody>
</table>
);
}
}
Now, this is where I'm stuck, I'm trying to figure out how the onChange function would work for me to edit the objects in the state and send back to the server for updating.
Give the item name input a name="name" attribute
Give the attribute inputs names per the attribute: name={key}
You can now identify what's being edited in onChange - make the appropriate state change based on the index and input name being changed.
e.g. (using import update from 'immutability-helper') onChange might look like this:
onChange = (e, index) => {
let {name, value} = e.target
let updateSpec
if (name === 'name') {
updateSpec = {
[index]: {
name: {$set: value}
}
}
}
else {
updateSpec = {
[index]: {
attributes: {
[name]: {$set: value}
}
}
}
}
this.setState({items: update(this.state.items, updateSpec)})
}
Here's an example app which shows this solution in action:
Live version
Full example source
I would change the onChange function in your Container to be the following:
onChange = (e, sku, field) => {
const updatedItems = this.state.items.map(e => {
if (e.sku !== sku) return e;
if (field === 'name') {
e.name = e.target.value;
} else {
// has to be attributes
e.attributes[field] = e.target.value;
}
return e;
});
this.setState({
items: updatedItems,
})
}
Added sku parameter to identify the item in items array and the field you want to change. Since sku is not going to change, we can use this as the item identifier. This way, you can change the product name and its respective attributes. For full working code, see the code example below.
class Container extends React.Component {
constructor() {
super();
this.state = {
items: [{
name: "Product Name",
sku: "00001",
attributes: {
width: "value",
height: "value",
length: "value",
color: "value"
}
},
{
name: "Product Name 2",
sku: "00002",
attributes: {
description: "Hello World",
weight: "250",
quantity: "500",
hello: "World"
}
}],
}
}
onChange = (e, sku, field) => {
console.log(e.target.value);
const updatedItems = this.state.items.map(item => {
if (item.sku !== sku) return item;
if (field === 'name') {
item.name = e.target.value;
} else {
// has to be attributes
item.attributes[field] = e.target.value;
}
return item;
});
this.setState({
items: updatedItems,
})
}
render() {
// log new state here
console.log(this.state.items);
return <EditForm onChange={this.onChange} items={this.state.items} />
}
}
class EditForm extends React.Component {
render() {
let editForm = this.props.items && this.props.items.map((item, idx) => {
return(
<tr key={item.sku}>
<td className="edit-table">
<input
value={item.name}
name={item.name}
onChange={(e)=> this.props.onChange(e, item.sku, 'name')}
/>
</td>
<td className="edit-table">
<ul className="item-attributes">
{Object.keys(item.attributes).map((key) => {
return (<li key={key}>
<label>{key}</label>
<input
value={item.attributes[key]}
onChange={(e) => this.props.onChange(e, item.sku, key) }
/>
</li>)
})}
</ul>
</td>
</tr>
)
})
return(
<table className="editcontainer-table">
<thead>
<tr>
<th>SKU</th>
<th>Attributes</th>
</tr>
</thead>
<tbody>
{editForm}
</tbody>
</table>
);
}
}
ReactDOM.render(<Container />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>