React component structure help needed - reactjs

I'm learning React, and I don't think I'm grasping everything correctly. I'm just trying to make a data table, but I just don't understand how I should set it up.
I've setup two components. One for the table and one for the rows. From my understanding, I should have a component for a container and another component for presentation. The presentational component should be stateless. Am I doing this right?
class ScheduledTable extends React.Component {
constructor(props) {
super(props);
this.state = {
sales: [],
theAction: "Pause",
liveActive: true,
upcomingActive: false,
completedActive: false
};
this.handleClick = this.handleClick.bind(this);
}
componentWillMount() {
this.setState({
sales: this.props.active_sales,
theAction: "Pause"
});
}
handleClick(e) {
if (e.target.id == "live") {
console.log("clicked Live");
this.setState({
sales: this.props.active_sales,
theAction: "Pause",
liveActive: true,
upcomingActive: false,
completedActive: false
});
} else if (e.target.id == "upcoming") {
console.log("clicked upcoming");
this.setState({
sales: this.props.scheduled_sales,
theAction: "Start Now",
liveActive: false,
upcomingActive: true,
completedActive: false
});
} else {
console.log("clicked completed");
this.setState({
sales: this.props.completed_sales,
theAction: "Duplicate",
liveActive: false,
upcomingActive: false,
completedActive: true
});
}
}
render() {
const latestAction = this.state.theAction;
const header = (
<tr>
<th>Name</th>
<th>Discount</th>
<th>Start Time</th>
<th>End Time</th>
<th>Cents Modification</th>
<th>View/Edit</th>
<th>Action</th>
<th>Cancel</th>
</tr>
);
const rows = [];
this.state.sales.map(function (sale) {
if (sale[4]) {
rows.push(<ScheduledTableRow key={sale[6]} name={sale[0]} discount={sale[1]} startTime={sale[2]} endTime={sale[3]} cMod={5} dType={sale[7]} action={latestAction} />);
} else {
rows.push(<ScheduledTableRow key={sale[6]} name={sale[0]} discount={sale[1]} startTime={sale[2]} endTime={sale[3]} cMod="N/A" dType={sale[7]} action={latestAction} />);
}
}, this
);
return (
<table className="dataTable" id="scheduledSalesTable">
<tbody>
<tr>
<th colSpan="12" className="filterRow">
<span className={this.state.liveActive ? 'filterCell activeUnderline': 'filterCell'} id="live" onClick={this.handleClick}>Live</span>
<span className={this.state.upcomingActive ? 'filterCell activeUnderline' : 'filterCell'} id="upcoming" onClick={this.handleClick}>Upcoming</span>
<span className={this.state.completedActive ? 'filterCell activeUnderline' : 'filterCell'} id="completed" onClick={this.handleClick}>Completed</span>
</th>
</tr>
{header}
{rows}
</tbody>
</table>);
}
}
function ScheduledTableRow(props) {
let discountType;
if (props.dType == "percent") {
discountType = props.discount + "%"
} else {
discountType = "$" + props.discount
}
return (
<tr>
<td>{props.name}</td>
<td>{discountType}</td>
<td>{props.startTime}</td>
<td>{props.endTime}</td>
<td>{props.cMod}</td>
<td>View</td>
<td><button type="button" className="btn btn-success goLiveButton">{props.action}</button></td>
<td className="text-center"><i className="far fa-trash-alt trashBin"></i></td>
</tr>
)
}
My ScheduledTable component seems bulky. Should I be doing it differently? Does it really matter?

In react, there is no sure shot way of doing things. The general abstraction is that containers must handle the logic and should hold state where as presentational components should be state less and be resposible for rendering things on browser i.e DOM elements and styles.
For your case, I would have made the table also a "presentational component" and the page or the component in which table is used as a "container".
Here is a good article by Dan Abramov about the same.
Do keep in mind that this not the only way to do things but this is a good way.

Related

How to render total number of objects in array react

Just wondering how can I go about, rendering the total length of the this.state.apiData array on the output screen. so I can go on and use conditional formatting with the outputted results.
that the user can see the total number of stock objects returned. T
import React, { Component } from "react";
class App extends Component {
constructor(props) {
super(props);
// create three state variables.
// apiData is an array to hold our JSON data
// isFetched indicates if the API call has finished
// errorMsg is either null (none) or there is some error
this.state = {
apiData: [],
isFetched: false,
errorMsg: null
};
}
// componentDidMount() is invoked immediately after a
// component is mounted (inserted into the tree)
async componentDidMount() {
try {
const API_URL =
"#";
// Fetch or access the service at the API_URL address
const response = await fetch(API_URL);
// wait for the response. When it arrives, store the JSON version
// of the response in this variable.
const jsonResult = await response.json();
// update the state variables correctly.
this.setState({ apiData: jsonResult.stockData });
this.setState({ isFetched: true });
} catch (error) {
// In the case of an error ...
this.setState({ isFetched: false });
// This will be used to display error message.
this.setState({ errorMsg: error });
} // end of try catch
} // end of componentDidMount()
// Remember our three state variables.
// PAY ATTENTION to the JSON returned. We need to be able to
// access specific properties from the JSON returned.
// Notice that this time we have three possible returns for our
// render. This is conditional rendering based on some conditions
render() {
if (this.state.errorMsg) {
return (
<div className="error">
<h1>We're very sorry: An error has occured in the API call</h1>
<p>The error message is: {this.state.errorMsg.toString()}</p>
</div>
); // end of return.
} else if (this.state.isFetched === false) {
return (
<div className="fetching">
<h1>We are loading your API request........</h1>
<p>Your data will be here very soon....</p>
</div>
); // end of return
} else {
// we have no errors and we have data
return (
<div className="App">
<div className="StocksTable">
<h1>CS385 - Stocks API Display</h1>
<table border="1">
<thead>
<tr>
<th>stock ID</th>
<th>Industry</th>
<th>Sector</th>
<th>Symbol</th>
<th>Name</th>
<th>Buy</th>
<th>Sell</th>
<th>Timestamp</th>
</tr>
</thead>
<tbody>
{this.state.apiData.map((s) => (
<tr>
<td>{s.StockID}</td>
<td>{s.stock.industry}</td>
<td>{s.stock.sector}</td>
<td>{s.stock.symbol}</td>
<td>{s.stock.name}</td>
<td>{s.rates.buy}</td>
<td>{s.rates.sell}</td>
<td>{s.rates.timestamp}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
); // end of return
} // end of the else statement.
} // end of render()
} // end of App class
export default App;
Here is an example of one of my array objects:
{"stockData":
[
{
"stockID":1,
"stock":
{
"industry":"Investment Bankers/Brokers/Service",
"sector":"Finance",
"symbol":"JMP",
"name":"JMP Group LLC"
},
"rates":
{
"buy":12.6,
"sell":393.11,
"timestamp":"2024-06-05 19:12:01"
}
},
{
"stockID":2,
"stock":
{
"industry":"Investment Bankers/Brokers/Service",
"sector":"Finance",
"symbol":"USOI",
"name":"Credit Suisse AG"
},
"rates":
{
"buy":363.49,
"sell":14.15,
"timestamp":"2024-08-30 13:37:23"
}
},
Thanks!
It is a simple as:
<div>{this.state.apiData.length}</div>
Maybe the size property would help with that ?
So probably something like :
let objAmount = this.state.apiData.size
Or even just
this.state.apiData.size
, if you don't want to use a variable.

Can't fill my state in componentDidMound function

inside of my componentDidMount method I'm trying to make array of objects by using fetch. In my head it looks like this - on state I keep variable "loading" (true by default), and when my method is done with fetching it set it to false. On render method I've put if statement. But in real life my method filled array doesn't get executed (first console.log gets executed, second is not), . I'm losing my mind with this.
import { Company } from "../company/company.component";
export class CompanyList extends Component {
constructor(props) {
super(props);
this.state = {
tempResult: 10,
newArray: [],
loading: true,
};
}
componentDidMount = () => {
console.log(this.state.loading,"1");
const filledArray = this.props.companies.map((item) => {
fetch(`xxxx/incomes/${item.id}`)
.then((response) => response.json())
.then((data) => {
let transactionsToFloat = data.incomes.map((item) =>
parseFloat(item.value)
);
let result = transactionsToFloat.reduce((acc, num) => {
return acc + num;
}, 0);
result = Math.round(result * 100) / 100;
this.setState({ tempResult: result, loading: false });
console.log(item.id, item.name, item.city, result);
return {
id: item.id,
name: item.name,
city: item.city,
totalIncome: result,
};
});
this.setState({ loading: false });
return true;
});
this.setState({ newArray: filledArray });
};
render() {
if (this.state.loading) {
return <h1>Loading...</h1>;
} else if (!this.state.loading) {
return (
<div>
{/* <button onClick={this.handleClick}>Button</button> */}
<table>
<thead>
<tr>
<th> Id </th>
<th> Name </th>
<th> City </th>
<th> Total income </th>
</tr>
</thead>
{this.state.newArray.map((item) => (
<Company key={item.id} company={item} />
))}
</table>
</div>
);
}
}
}
Cheers
fetch is async, when you do this.setState({ loading: false }) after fetch, this line of code will be executed right away, before promise is even resolved. you are also not returning the data, but true values instead.
given that you are executing an array of promises, you may consider return fetch promises and wrap your array of promises with a Promise.all:
Promise.all(this.props.companies.map((item) => { return fetch().then().then() })
.then(results => this.setState({ newArray: results, loading: false }))
.catch(error => ... handle error here)
there is a caveat that Promise.all will reject if one of the promises fails. if you dont want that behavior you could use Promise.allSettled instead. allSettled will never reject and it returns instead an array of objects, with status and value keys.

React Dynamic Table not showing in Knowckout project

i have been able to successfully integrate react components in my durandal/knockout application. I have managed to show data in a graph but i cant seem to show the data in my react table. I get the following JSON dynamic data :
{
"Level": 1,
"Label": "Level 1",
"Qualification": "≥ 100 points",
"BBBEERecognitionLevel": "135%",
"LevelMet": false,
"Color": "whitesmoke"
},
Now the data shows in my console so i know i am getting the Json data, here is my Knockout code:
index.html
<bee-data-Table data-bind="attr: { data: ReactBeeLevelTable }" />
index.js
self.ReactBeeLevelTable = ko.observable();
var length = levels.length;
for (var i = 0; i < length; i++) {
var levelItem = levels[i];
levelItem.LevelMet = levelItem.Level === recognisedLevel;
levelItem.Color = levelItem.LevelMet ? reveresedColours[i].color : defaultTableRowColor;
var levelCompare = getCompareValueFromLevel(levelItem.Level);
var recognisedLevelCompare = getCompareValueFromLevel(recognisedLevel);
if (levelCompare <= recognisedLevelCompare) {
var chartIndex = ConvertLevelToIndex(levelItem.Level);
dataset.backgroundColor[chartIndex] = defaultChartBackgroundColor;
}
}
self.ReactBeeLevelTable(JSON.stringify(levels));
And this is my react datatable.js
import React, { Component } from 'react';
import { Table } from '#react-pakistan/react-commons-collection/table';
const RenderRow = (props) =>{
return props.keys.map((key, index)=>{
return <td key={props.data[key]}>{props.data[key]}</td>
})
}
export class Example extends Component {
constructor(props) {
super(props)
this.getHeader = this.getHeader.bind(this);
this.getRow= this.getRow.bind(this);
this.getKeys=this.getKeys.bind(this);
}
getKeys = function() {
return Object.keys(this.props.data[0]);
}
getHeader = function() {
var keys = this.getKeys();
return keys.map((key,index)=> {
return <th key={key}>{key.toUpperCase()}</th>
})
}
getRow = function () {
var items = this.props.data;
var keys = this.getKeys();
return items.map((row,index) => {
return <tr key={index}><RenderRow key={index} data={row} keys={keys}/></tr>
})
}
render () {
return (
<div>
<Table>
<thead>
<tr>
<th>B-BBEE Recognition Level</th>
</tr>
<tr>
<th>{this.getHeader()}</th>
</tr>
</thead>
<tbody>
<tr> {this.getRow()} </tr>
</tbody>
</Table>
</div>
);
}
}
and here is my Datatable.Story.js
import React from 'react';
import { storiesOf } from '#storybook/react';
import { Example } from './DataTable';
const TableElements = [{
"Level": 1,
"Label": "Level 1",
"Qualification": "≥ 100 points",
"BBBEERecognitionLevel": "135%",
"LevelMet": false,
"Color": "whitesmoke"
}]
storiesOf('Table Examples', module)
.add('Priority Element', () => (
<Example data={TableElements} title={"Points Available/Earned"}/>)
);
and here is my index,js
import { register } from 'web-react-components';
register(Example,'bee-data-Table',['data'],{}, { useShadowDOM: false });
So the Table shows with hard coded data in my react project but when i try to switch out the data with dynamic data in my durandal/knockout project, it doesnt seem to show
After some investigation i found that when you register a component, it is case sensitive, so if you look in my index.jsfile i have this line of code:
register(Example,'bee-data-Table',['data'],{}, { useShadowDOM: false });
Which is incorrect, the correct way to register a component is like this:
register(Example,'bee-data-table',['data'],{}, { useShadowDOM: false });

React.js: Unable to access object attribute within state object

I am working on a finance tracker. Eventually, the finance tracker will be able to access an account in the user's bank account, pull every transaction, and the user will be able to add transactions as future projections. The idea is to give the user the ability to run financial projections/scenarios using the most recent checking/saving account information in the user's bank account(s).
I am working on a "running total" column which takes the amount found in transactionData.amount and will add transactionData.amount to startBal if this is the "zero" index. Otherwise, it will use the numbers found in the previous index for transactionData.runningTotal and add to the value found in the current index for transactionData.amount.
In either case, the new calculation should be added to the current index for transactionData.runningTotal. I am essentially mimicking what an online transaction detail would, in the event that the bank does not provide this data already.
Here is the parent component.
import React, { Component } from "react";
import TransactionSearch from "./transactionSearch.js";
import PendingTransactions from "./pendingTransactions.js";
import Transactions from "./transactions.js";
class CheckingAccount extends Component {
state = {
startBal: 1000,
pendingTransData: [
{ id: 0, date: "1/1/2020", transaction: "gas", amount: -25.45 },
{ id: 1, date: "1/2/2020", transaction: "cell phone", amount: -127.35 },
{ id: 2, date: "1/3/2020", transaction: "car payment", amount: -303.97 }
],
transactionData: [
{
id: 0,
date: "1/1/2020",
transaction: "gas",
amount: -35.45,
runningTotal: null
},
{
id: 1,
date: "1/2/2020",
transaction: "cell phone",
amount: -227.35,
runningTotal: null
},
{
id: 2,
date: "1/3/2020",
transaction: "car payment",
amount: -403.97,
runningTotal: null
}
]
};
addRunningTotal() {
let { transactionData, startBal } = this.state;
console.log(transactionData);
transactionData.map((el, i) => {
console.log("in map function");
if (el[i] === 0) {
return (el[i].runningTotal = el[i].amount + startBal);
} else if (el[i] > 0) {
return (el[i].runningTotal = el[i - 1].amount + el[i].amount);
}
});
console.log("out of map function");
console.log("start Balance: ", startBal);
console.log("amount: ", transactionData[0].amount);
console.log("running total: ", transactionData[0].runningTotal);
this.setState({ transactionData: transactionData, startBal: startBal });
}
componentDidMount() {
this.addRunningTotal();
}
render() {
let pendTransData = (
<div>
<h1>PendingTransactions</h1>
<table>
<tr>
<th>Date</th>
<th>Transaction</th>
<th>Amount</th>
</tr>
</table>
{this.state.pendingTransData.map((pendingTransData, index) => {
return (
<PendingTransactions
key={pendingTransData.id}
date={pendingTransData.date}
transaction={pendingTransData.transaction}
amount={pendingTransData.amount}
/>
);
})}
</div>
);
let transData = (
<div>
<h1>Transaction Component</h1>
<table>
<tr>
<th>Date</th>
<th>Transaction</th>
<th>Amount</th>
<th>Running Total</th>
</tr>
</table>
{this.state.transactionData.map((transactionData, index) => {
return (
<Transactions
key={transactionData.id}
date={transactionData.date}
transaction={transactionData.transaction}
amount={transactionData.amount}
runningTotal={transactionData.runningTotal}
/>
);
})}
</div>
);
return (
<div className="App">
<h1> Checking Account</h1>
<TransactionSearch />
{pendTransData}
{transData}
</div>
);
}
}
export default CheckingAccount;
Here is the child component where the data should appear.
import React from "react";
function Transactions(props) {
return (
<tr>
<td>{props.date} </td>
<td>{props.transaction}</td>
<td>{props.amount}</td>
<td>{props.runningTotal}</td>
</tr>
);
}
export default Transactions;
First, runningTotal attribute does not render in the component. I expected to see a column with the new data in the runningTotal attribute.
In addRunningTotal, It looks like it's how you've used map. In map((el, i) => {}), el is a reference to the current iteration's value so where you've used el[i] (undefined), you wanted to use just el.
You also only need to use i (index) in your if statement.
This should do the trick (keeping reference to the previous value):
let prevAmount, running;
transactionData.map((el, i) => {
if (i === 0) {
running = el.runningTotal = el.amount + startBal;
prevAmount = el.amount;
return running;
} else if (i > 0) {
running = el.runningTotal = prevAmount + el.amount;
prevAmount = el.amount;
return running;
}
});

for my code iam not getting pagenation and searchbar in reactjs

//here is my code//
class TableList extends Component {
constructor(props) {
super(props);
//var totalPages = 100 / 10; // 10 page numbers
this.state = {
query: "",
countries: [],
searchString:[],
currentPageNumber: 1,
pageOfItems: [],
totalItems: 4,
itemsPerPage: 10
}
this.onChangePage = this.onChangePage.bind(this);
}
onChangePage(pageOfItems) {
this.setState({ pageOfItems: pageOfItems });
}
handleInputChange = (event) => {
this.setState({
query: event.target.value
},()=>{
this.filterArray();
})
}
handleSelect(number) {
console.log('handle select', number);
this.setState({currentPageNumber: number});
}
componentDidMount() {
const apiUrl = 'https://indian-cities-api-nocbegfhqg.now.sh/cities';
fetch(apiUrl)
.then(res => res.json())
.then(
(result) => {
this.setState({
countries: result,
searchString:result,
currentPageNumber:result.currentPageNumber,
totalItems: result.totalItems,
itemsPerPage: result.itemsPerPage
});
},
)
}
filterArray = () => {
let searchString = this.state.query;
let result = this.state.countries;
if(searchString.length > 0){
result = result.filter(searchString);
this.setState({
result
})
}
}
render() {
const { countries} = this.state;
let totalPages = Math.ceil(this.state.totalItems / this.state.numItemsPerPage);
return(
<div>
<div className="container">
</div>
<h2>countrie List</h2>
<form>
<input type="text" id="filter" placeholder="Search for..." onChange={this.handleInputChange}/>
</form>
<Table>
<Pagination
bsSize="medium"
items={totalPages}
activePage={this.state.currentPageNumber} onSelect={this.handleSelect.bind(this)}/>
<thead>
<tr>
<th>#ID</th>
<th>countrie Name</th>
<th>Code</th>
<th>States</th>
</tr>
</thead>
<tbody>
{countries.map(countrie => (
<tr key={countrie.City}>
<td>{countrie.sno}</td>
<td>{countrie.City}</td>
<td>{countrie.State}</td>
<td>{countrie.District}</td>
</tr>
))}
</tbody>
</Table>
</div>
)
}
}
export default TableList;
//the error coming is
Warning: Encountered two children with the same key, `Wadi`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version
IN SHORT:
Search - filtered data stored in this.state.result - not used in render
this.setState({
result
})
as { result } is a short of { result: result } and this is good ... overwriting this.state.countries would result in source data loss (needs refetching)
render gets/make use of this.state.countries - always full dataset, not filtered by search, not divided by page ranges
You need to copy some data into this.state.result after fetching (not copy a countries reference)
Pagination - 'results' (not proper as above) records not subselected by range based on currentPage
Inspect state changes (check if properly working) in browser using react dev tools.

Resources