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>
);
})}
Related
I would like to create an array outside useTable invocation, manipulate the array, and based on the array state make checkbox checked or unchecked. After each click on a button, length of the array is increased by adding one element to the array. When length become greater than 3, the input should be checked.
The problem is that the array state is different inside and outside checked attribute of input. Outside it works as expected: the array length increases. Inside, the array length is equal to initial length of 0.
I have attached code with some logging. I think that the relevant part probably ends with the end of useTable invocation (then is some code which I took from react-table docs with button and mock data, columns added). What changes should I introduce to the code to make it work as I expect?
import React, { useMemo, useState } from 'react';
import { useTable } from 'react-table'
function Table({ columns, data }) {
// neither stateArr nor simpleArr help reach what I would like to
const [stateArr, setStateArr] = useState([]);
let simpleArr = [...stateArr];
const handleOnButtonClick = () => {
console.log("Outside checked: simpleArr, stateArr");
console.log(simpleArr);
console.log(stateArr);
setStateArr([...stateArr, 1]);
// in this case unnecessary, since, as I understand, simpleArr is rendered and (re)assigned above
// simpleArr = [...stateArr];
};
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({
columns,
data,
},
(hooks) => {
hooks.visibleColumns.push((columns) => {
return [
{
id: 'checkedInputs',
Header: () => {
return (<div>
<input type="checkbox"
// working, not most elegant way to combine logging and computing boolean
checked={console.log("Inside checked: simpleArr, stateArr") || console.log(simpleArr)
|| console.log(stateArr) || simpleArr.length > 3 || stateArr.length > 3} />
</div>);
},
Cell: () => {
return (<div>R</div>);
},
},
...columns,
];
});
}
);
return (
<div>
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>{column.render('Header')}</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map((row, i) => {
prepareRow(row)
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
})}
</tr>
)
})}
</tbody>
</table>
<button type="button" onClick={handleOnButtonClick}>Click Me!</button>
</div>
)
}
function App() {
const columns = useMemo(
() => [
{
Header: 'Animal Type',
accessor: 'animalType',
},
{
Header: 'Number of legs',
accessor: 'numberOfLegs',
},
],
[],
);
const data = useMemo(
() => [
{
animalType: 'dog',
numberOfLegs: 4,
},
{
animalType: 'snake',
numberOfLegs: 0,
},
],
[],
);
return (
<Table columns={columns} data={data} />
)
}
export default App;
Stale Data
The hooks.visibleColumns.push function is called one time when the table is created. It creates a Header render component that takes some props and returns a JSX element. The function which renders the Header based on these props is called every time that the table updates. The function which creates this Header component is called once.
In your example, you create a Header component which prints out some data based on the values of simpleArr and stateArr at the time that it was created, not at the time that it was called.
Table State
If we want our Header component to render with current data then we should get that data from props. The Header is called with quite a lot of props but the one that we will use is state which is the state of the table. We will set the initialState of the table to an object { stateArr: [] }. This gets merged with the standard table state { hiddenColumns: [] }.
The table state is updated through a useReducer hook, so we update it by disptaching an action. We need a custom stateReducer to update the table state based on the contents of the action.
import React, { useMemo } from "react";
import { useTable } from "react-table";
function Table({ columns, data }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
dispatch,
state
} = useTable(
{
columns,
data,
initialState: {
stateArr: []
},
stateReducer: (newState, action, prevState) => {
console.log(action, newState, newState.stateArr);
switch (action.type) {
case "incrementChecks":
return {
...newState,
stateArr: [...newState.stateArr, action.payload]
};
default:
return newState;
}
}
},
(hooks) => {
hooks.visibleColumns.push((columns) => {
return [
{
id: "checkedInputs",
Header: (props) => {
console.log("header props", props); // so you can see all the data you get
console.log("stateArr", props.state.stateArr);
return (
<input
type="checkbox"
readOnly
checked={props.state.stateArr.length > 3}
/>
);
},
Cell: () => {
return <div>R</div>;
}
},
...columns
];
});
}
);
const handleOnButtonClick = () => {
// payload is the item which we are appending to the array
dispatch({ type: "incrementChecks", payload: 1 });
};
console.log("stateArr", state.stateArr);
return (
<div>
<table {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps()}>{column.render("Header")}</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<td {...cell.getCellProps()}>{cell.render("Cell")}</td>
);
})}
</tr>
);
})}
</tbody>
</table>
<button type="button" onClick={handleOnButtonClick}>
Append
</button>
</div>
);
}
function App() {
const columns = useMemo(
() => [
{
Header: "Animal Type",
accessor: "animalType"
},
{
Header: "Number of legs",
accessor: "numberOfLegs"
}
],
[]
);
const data = useMemo(
() => [
{
animalType: "dog",
numberOfLegs: 4
},
{
animalType: "snake",
numberOfLegs: 0
}
],
[]
);
return <Table columns={columns} data={data} />;
}
export default App;
CodeSandbox Link
I found a tutorial to implement DataTable in React App but I have no idea how can I custom my tr, td, colspan etc...
My DataTable.js is :
import React, { Component } from 'react';
const $ = require('jquery');
$.DataTable = require('datatables.net');
class App extends Component {
componentDidMount() {
this.$el = $(this.el);
this.$el.DataTable({
data: this.props.data,
columns: this.props.columns
})
}
render() {
return (
<div className="table-responsive">
<table className="table" ref={el => this.el = el}>
</table>
</div>
);
}
}
And after in my Test.js :
import React, { Component } from 'react';
import DataTable from './DataTable';
import axios from 'axios';
class Test extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
columns: [
{ title: "Id" },
{ title: "Category" },
{ title: "Title" },
{ title: "Command 1" },
{ title: "Command 2" },
{ title: "Command 3" }
]
}
}
componentDidMount() {
this._getFilteredItems();
}
_getFilteredItems = () => {
axios.post('http://localhost:8080/items/category', { category: "category1" })
.then((res) => {
var test = res.data.map((e) => Object.values(e)); // to transform objet in array
this.setState({ data: test });
})
.catch(error => { console.log(error) });
}
display = () => {
if(this.state.data.length > 0){
return (
<DataTable
data={this.state.data}
columns={this.state.columns}>
</DataTable>
);
}
}
render() {
return (
<div>
{this.display()}
</div>
);
}
}
export default Test;
My data received from my backend is like this :
[
["5e9c231facad1424801f5167", "category1", "title", "command1", "command2", "command3"],
["5e9c2337acad1424801f58ce", "category1", "title", "command1", "command2", "command3"],
["5eaa105b82d1130017d31dbe", "category1", "title", "command1", "command2", "command3"],
]
The thing is I would like to custom my tr, td, colspan etc... I mean, I would like for example put the title with a colspan="5" and my command1, command2 and command3 in the same td.
Do you have any idea how can I do that ? Thanks
I found, I simply initialized my table as I wanted in the render instead of using props like the tutorial mentionned it :
import React, { Component } from 'react';
import axios from 'axios';
const $ = require('jquery');
$.DataTable = require('datatables.net');
class Test extends Component {
constructor(props) {
super(props);
this.state = {
data: []
}
}
componentDidMount() {
this._getFilteredItems();
}
_getFilteredItems = () => {
axios.post('http://localhost:8080/items/category', { category: "category1" })
.then((res) => {
this.setState({data: res.data});
this.$el = $(this.el);
this.$el.DataTable({});
})
.catch(error => { console.log(error) });
}
render() {
return (
<div className="table-responsive">
<table className="table" ref={el => this.el = el}>
<thead>
<tr>
<th>Title</th>
<th>Commands</th>
</tr>
</thead>
<tbody>
{this.state.data.map((elmt, i) => (
<tr key={i}>
<td>
{elmt.title}
</td>
<td>
{elmt.command1} <br/>
{elmt.command2} <br/>
{elmt.command3} <br/>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
}
export default Test;
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>
I have an array items[] which has four fields name, origin, destination, seats.
I would like to sort the name alphabatically onClick of the table heading i.e Name
heres my snippet of code
JS file with array declaration variables and passing the values to form.js
Stuff.js
import React, { Component } from 'react';
import './App.css';
import Tab from './Table';
import Form from './Form';
class Stuff extends Component {
constructor() {
super();
this.state = {
name: '',
origin: '',
destination: '',
seats: '',
items: []
}
};
handleFormSubmit = (e) => {
e.preventDefault();
let items = [...this.state.items];
items.push({
name: this.state.name,
origin: this.state.origin,
destination: this.state.destination,
seats: this.state.seats
});
this.setState({
items,
name: '',
origin: '',
destination: '',
seats: ''
});
};
handleInputChange = (e) => {
let input = e.target;
let name = e.target.name;
let value = input.value;
this.setState({
[name]: value
})
};
render() {
return (
<div className="App">
<Form handleFormSubmit={ this.handleFormSubmit }
handleInputChange={ this.handleInputChange }
newName={ this.state.name }
newOrigin={ this.state.origin }
newDestination={ this.state.destination }
newSeats={ this.state.seats } />
<Tab items={ this.state.items }/>
</div>
);
}
}
export default Stuff;
Table.js
import React, { Component } from 'react';
import { Table } from 'reactstrap';
class Tab extends React.Component {
render() {
const items = this.props.items;
return (
<Table striped>
<thead>
<tr>
<th >Name</th>
<th>Origin</th>
<th>Destination</th>
<th>Seats</th>
</tr>
</thead>
<tbody>
{items.map(item => {
return (
<tr>
<td>{item.name}</td>
<td>{item.origin}</td>
<td>{item.destination}</td>
<td>{item.seats}</td>
</tr>
);
})}
</tbody>
</Table>
);
}
}
export default Tab;
Is there any way that when I click on Name Heading in my table it would sort according to the name?
Thank You
I don't go into details of ReactJS, you need to implement yourself. The idea is quite simple:
You create a event handler for header like (onClick)="sortByName()"
Define your function sortByName by passing your data into it, and sort it manually, you can use Array.sort() to achieve this.
Update the state will make React re-render the part have the update
I hope this answers your query:
const items = [
{
name: 'C',
quantity: 2
},
{
name: 'B',
quantity: 3
},
{
name: 'A',
quantity: 8
}
];
console.log(items);
items.sort((a,b) => {
const name1 = a.name.toLowerCase(), name2 = b.name.toLowerCase();
return name1 === name2 ? 0 : name1 < name2 ? -1 : 1;
});
console.log(items);
https://jsfiddle.net/abhikr713/r6L2npfL/1/
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>