I have two components, RaisedButton and TableList. Tablelist return select rows and the same is updated in the state (currentSelectedRows). RaisedButton simply consoles the currentSelectedRows. Now the problem statement:
onClick of RaisedButton it consoles the state properly (using approveSelected) till the time updateSelectedRows does not update the state. Once the state is updated inside updateSelectedRows method, onClick of RaisedButton component first calls updateSelectedRows then approveSelected. Below is the code.
export default class MyList extends React.Component {
constructor(props) {
super(props);
this.state = {
pendingList:[],
currentSelectedRows:[]
}
}updateSelectedRows(selectedRows){
console.log("updateCalled");
this.setState({
currentSelectedRows:selectedRows
});
};
approveSelected() {
console.log("approve selected");
console.log(this.state.currentSelectedRows);
};
render() {
return (
<div className="container">
<div className="row_align_right">
<RaisedButton label="APPROVE" backgroundColor={MUIColors.lightGreen500} labelColor={MUIColors.white} onClick={this.approveSelected.bind(this)} />
</div>
<div className="content">
<div className="">
<TableList
selectedRows={this.state.currentSelectedRows}
updateSelectedRows={this.updateSelectedRows.bind(this)}
/>
</div>
</div>
</div>
)
}
}
Any advice would be of great help.
Thanks
You didn't provide the code for TableList so it's hard to know what could be the problem but it seems to work when you just pass a row id upwards to the parent:
const usersList = [
{ name: 'John', age: 33 },
{ name: 'Jane', age: 32 },
{ name: 'David', age: 28 },
{ name: 'Eve', age: 29 },
];
class Row extends React.Component {
onClick = e => {
const { onClick, rowId } = this.props;
onClick(rowId)
}
render() {
const { user } = this.props;
return (
<tr onClick={this.onClick}>
<td>{user.name}</td>
<td>{user.age}</td>
</tr>
);
}
}
class TableList extends React.Component {
onClick = rowId => {
this.props.updateSelectedRows(rowId);
}
render() {
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody>
{
usersList.map((user, i) => <Row key={i} rowId={i} user={user} onClick={this.onClick}/>)
}
</tbody>
</table>
);
}
}
class MyList extends React.Component {
constructor(props) {
super(props);
this.state = {
pendingList: [],
currentSelectedRows: []
}
} updateSelectedRows(selectedRows) {
console.log("updateCalled");
this.setState({
currentSelectedRows: selectedRows
});
};
approveSelected() {
console.log("approve selected");
console.log(this.state.currentSelectedRows);
};
render() {
return (
<div className="container">
<div className="row_align_right">
<button onClick={this.approveSelected.bind(this)}>Click</button>
</div>
<div className="content">
<div className="">
<TableList
selectedRows={this.state.currentSelectedRows}
updateSelectedRows={this.updateSelectedRows.bind(this)}
/>
</div>
</div>
</div>
)
}
}
ReactDOM.render(<MyList />, 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>
Related
How to return element in react class functions on a click. is it even possible?
class Item extends Component {
constructor(props) {
super(props);
this.itemInfo = this.itemInfo.bind(this);
}
itemInfo = () =>{
return <div> some info</div>
}
render(){
return(
<div>
<div onClick={this.itemInfo}> Click Here <div>
</div>
)
}
}
class Item extends React.Component {
state = {
showDiv: false
};
render() {
return (
<div>
<div
style={{ cursor: "pointer" }}
onClick={() =>
this.setState(prevState => ({
showDiv: !prevState.showDiv
}))
}
>
Click Me
</div>
{/*Show the INFO DIV ONLY IF THE REQUIRED STATE IS TRUE*/}
{this.state.showDiv && <InfoDiv />}
</div>
);
}
}
//This is the div which we want on click
var InfoDiv = () => (
<div style={{ border: "2px solid blue",borderRadius:10, padding: 20 }}>
<p> Long Text DIVLong Text DIVLong Text DIVLong Text DIVLong Text DIV </p>
</div>
);
ReactDOM.render(<Item />, 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>
You should do that in the state.
itemInfo = () =>{
this.setState({ component:<div> some info</div> });
}
and render the component like this
return(
<div>
<div onClick={this.itemInfo}> Click Here <div>
{this.state.component}
</div>
)
You can try something like this, using the state and conditional rendering:
class Item extends Component {
constructor(props) {
super(props)
this.state = {
showMore: false,
}
}
toggleShowMore = () => {
this.setState({ showMore: !this.state.showMore })
}
render() {
return (
<div>
<div onClick={this.toggleShowMore}>
{this.state.showMore ? 'Show less' : 'Show more'}
</div>
{this.state.showMore ? <div>some info</div> : null}
</div>
)
}
}
Here's how I would do it:
function ItemInfo() {
return(
<div>Some Info</div>
);
}
class Item extends Component {
constructor(props) {
super(props);
this.handleClick= this.handleClick.bind(this);
this.state = {
showInfo: false
}
}
handleClick() {
this.setState((prevState) => {showInfo: !prevState.showInfo});
}
render(){
return(
<div>
<div onClick={this.handleClick}> Click Here <div>
{ this.state.showInfo ?
<ItemInfo/>
: null }
</div>
)
}
}
I am writing a simple react page that renders 2 different html tables based off of which button is clicked on the screen. The issue I am having is that the table that is rendered for each button click is associated with the previous button click. (E.G. if I click button 1 one time then click button 2 the table associated with button 1 will be displayed.)
I am new to react so in order to get the tables to update I refactored my code to hold as much of the state as possible in the App.js class, I created the toggleState callback to associate the button clicks with state change of the parent, and I then pass that to DataProvider via the endpoint property. I realize this is probably where the state / UI disconnect is occurring, but I'm uncertain of the cause since I'm adhering to react principles to the best of my capability.
my class structure is as follows:
App
/ \
/ \
/ \
DataProvider ButtonToggle
|
Table
If it is relevant the table class is building the table based off of an API call, I will add the code for this, but it is not causing me problems so I do not believe it to be the source of the issue.
App.js
import React, { Component } from "react";
import PropTypes from "prop-types";
import DataProvider from "./DataProvider";
import Table from "./Table";
import ButtonToggle from "./ButtonToggle";
class App extends Component {
constructor(props){
super(props);
this.state = {
input : 'employees',
endpoint : "api/employees/"
};
console.log("constructor app: " + this.state.input + "\n" + this.state.endpoint);
}
toggleState(input) {
if(input == "employees") {
this.setState({input : input, endpoint: "api/employees/"});
}
else {
this.setState({input : input, endpoint: "api/categories/"});
}
console.log("toggleState " + this.state.input + "\n" + this.state.endpoint);
}
render() {
return (
<div className="col-lg-12 grid-margin">
<div className="card">
<div className="card-title">
<div className="row align-items-center justify-content-center">
<div className="col-3"></div>
<div className="col-6">
<h1> Striped Table</h1>
</div>
<div className="col-3"></div>
</div>
<ButtonToggle toggleInput={ (input) => this.toggleState(input)}/>
</div>
<div className="card">
<div className="card-title"></div>
<div className="card-body">
<DataProvider endpoint={this.state.endpoint}
render={data => <Table data={data} />} />
</div>
</div>
</div>
</div>
);
}
}
export default App;
DataProvider.js
class DataProvider extends Component {
static propTypes = {
endpoint: PropTypes.string.isRequired,
render: PropTypes.func.isRequired
};
constructor(props) {
super(props);
this.state = {
data: [],
loaded: false,
placeholder: "Loading..."
};
}
componentWillReceiveProps(props) {
console.log("dataprov: " + this.props.endpoint);
this.componentDidMount();
}
componentDidMount() {
fetch(this.props.endpoint)
.then(response => {
if (response.status !== 200) {
return this.setState({ placeholder: "Something went wrong" });
}
return response.json();
})
.then(data => this.setState({ data: data, loaded: true }));
}
render() {
const { data, loaded, placeholder } = this.state;
return loaded ? this.props.render(data) : <p>{placeholder}</p>;
}
}
export default DataProvider;
ButtonToggle.js
class ButtonToggle extends Component {
constructor (props) {
super(props);
}
render() {
return (
<div className="row align-items-center justify-content-center">
<div className="col-3 center-in-div">
<button type="button" className="btn btn-info btn-fw" onClick={this.props.toggleInput.bind(this, 'categories')}> Categories </button>
</div>
<div className="col-3 center-in-div">
<button type="button" className="btn btn-info btn-fw" onClick={this.props.toggleInput.bind(this, 'employees')}>
Employees
</button>
</div>
<div className="col-6"></div>
</div>
);
}
}
export default ButtonToggle;
Table.js : I don't think this is a problem, but I may stand corrected.
import React from "react";
import PropTypes from "prop-types";
import key from "weak-key";
const Table = ({ data }) =>
!data.length ? (
<p>Nothing to show. Records: {data.length} </p>
) : (
<div className="table-responsive">
<h2 className="subtitle">
Showing <strong>{data.length} items</strong>
</h2>
<table className="table table-hover">
<thead>
<tr>
{Object.entries(data[0]).map(el => <th key={key(el)}>{el[0]}</th>)}
</tr>
</thead>
<tbody>
{data.map(el => (
<tr key={el.id}>
{Object.entries(el).map(el => <td key={key(el)}>{el[1]}</td>)}
</tr>
))}
</tbody>
</table>
</div>
);
Table.propTypes = {
data: PropTypes.array.isRequired
};
export default Table;
Below is the minimum working code I could come up with. Your Button and Table components can be dumb components which will get data from parent component and will present it.
Your Parent or container component will have logic to set the properties for Button and Table component.
As Table and Button components are dumb you can go with functional components.
I have added the code for calling api (I have tried to mimic the api call) and getting data in same parent component, you can separate it out.
You can work on style and validations as per your needs.
Let me know if you need any further help.
class ParentComponent extends Component {
constructor() {
super();
this.state = {
name: "Category"
}
this.onBtnClick = this.onBtnClick.bind(this);
}
componentDidMount() {
this.getData(this.state.name)
}
getData(name) {
if (name === "Category") {
this.apiCall("/Category").then((data) => {
this.setState({ data: data })
})
} else {
this.apiCall("/Employee").then((data) => {
this.setState({ data: data })
})
}
}
apiCall(url) {
return new Promise((res, rej) => {
setTimeout(() => {
if (url === "/Employee") {
res([{ "Emp Name": "AAA", "Emp Age": "20" }, { "Emp Name": "BBB", "Emp Age": "40" }])
} else {
res([{ "Cat Id": "XXX", "Cat Name": "YYY" }, { "Cat Id": "MMM", "Cat Name": "NNN" }])
}
}, 1000)
});
}
onBtnClick(name) {
let newName = "Category"
if (name === newName) {
newName = "Employee"
}
this.setState({ name: newName, data: [] }, () => {
this.getData(newName);
})
}
render() {
return (<>
<ButtonComponent name={this.state.name} onBtnClick={this.onBtnClick}></ButtonComponent>
<TableComponent data={this.state.data} />
</>)
}
}
const ButtonComponent = ({ name, onBtnClick }) => {
return <Button onClick={() => { onBtnClick(name) }}>{name}</Button>
}
const TableComponent = ({ data }) => {
function getTable(data) {
return < table >
<thead>
<tr>
{getHeading(data)}
</tr>
</thead>
<tbody>
{getRows(data)}
</tbody>
</table >
}
function getHeading(data) {
return Object.entries(data[0]).map((key) => {
return <th key={key}>{key[0]}</th>
});
}
function getRows(data) {
return data.map((row, index) => {
return <tr key={"tr" + index}>
{Object.entries(data[0]).map((key, index) => {
console.log(row[key[0]]);
return <td key={"td" + index}>{row[key[0]]}</td>
})}
</tr>
})
}
return (
data && data.length > 0 ?
getTable(data)
: <div>Loading....</div>
)
}
I am trying to map over an array and get a chart to appear alongside with each element, but it doesn't seem to work. This same code appeared once correctly, but no other time and I am not sure what I am missing.
I tried to change the id name to where it tags the chart and I did that by adding an index variable, but still not working
import React from 'react'
import c3 from '/c3.min.js'
class SearchedFood extends React.Component {
constructor(props) {
super(props)
this.state = {
}
this.graph = this.graph.bind(this)
}
graph(index) {
c3.generate({
bindto: '#class' + index,
data: {
columns: [
[1, 2, 3], [2, 3,4]
],
type: 'bar'
},
bar: {
width: {
ratio: 0.3
}
}
})}
render () {
return (
<div>
{this.props.foodResults.map((food, i) => {
return (
<div key={i}>
<label>{food.recipe.label}</label>
<img className="card-img-top" src={food.recipe.image} height="250" width="auto"></img>
<a href={food.recipe.url}>{food.recipe.source}</a>
<p>{food.recipe.dietLabels[0]}</p>
<div>
{food.recipe.ingredientLines.map((ingredient, i) => {
return (
<p key={i}>{ingredient}</p>
)
})}
</div>
<p>Calories {Math.floor(food.recipe.calories/food.recipe.yield)}</p>
<div id={`class${i}`}>{this.graph(i)}</div>
</div>
)
})}
</div>
)
}
}
export default SearchedFood
bindto: '#class' + index,/{this.graph...} isn't gonna work. React doesn't render directly/immediately to the DOM.
Looks like you can use elements with bindTo - your best bet is to use a ref
class SearchedFoodRow extends React.Component {
componentDidMount() {
c3.generate({
bindTo: this.element,
...
})
}
render() {
const { food } = this.props
return (
<div>
<label>{food.recipe.label}</label>
<img className="card-img-top" src={food.recipe.image} height="250" width="auto"></img>
<a href={food.recipe.url}>{food.recipe.source}</a>
<p>{food.recipe.dietLabels[0]}</p>
<div>
{food.recipe.ingredientLines.map((ingredient, i) => {
return (
<p key={i}>{ingredient}</p>
)
})}
</div>
<p>Calories {Math.floor(food.recipe.calories/food.recipe.yield)}</p>
<div ref={ element => this.element = element } />
</div>
)
}
}
and then
class SearchFood extends React.Component {
render() {
return (
<div>
{ this.props.foodResults.map((food, i) => <SearchedFoodRow key={i} food={food} />)}
</div>
)
}
}
I am in the process of learning React and having some trouble with my state. I am trying to get a function to log this.state.records.amount to my console when the component is rendered but it shows up as undefined. If someone can figure this out it would be VERY much appreciated.
Records component:
class Records extends React.Component {
constructor(props) {
super(props);
this.state = {
records: []
}
this.handleNewRecord = this.handleNewRecord.bind(this);
this.handleDeleteRecord = this.handleDeleteRecord.bind(this);
this.surplus = this.surplus.bind(this);
this.debt = this.debt.bind(this);
this.total = this.total.bind(this);
}
componentDidMount() {
this.setState({
records: this.props.records
})
}
handleNewRecord(record) {
let records = this.state.records.slice();
records.push(record)
this.setState({
records: records
})
}
handleDeleteRecord(record) {
let records = this.state.records.slice();
let index = records.indexOf(record)
records.splice(index, 1)
this.setState({
records: records
})
}
surplus() {
console.log(this.state.records[0].amount)
}
debt() {
console.log("debt")
}
total() {
console.log("total")
}
render() {
const records = this.state.records.map((record) =>
<Record record={record} key={record.id} handleDeleteRecord={this.handleDeleteRecord}/>
)
return (
<div className="container">
<h1>Records</h1>
<div className="row">
<AmountBox panelClass="panel panel-primary" panelHeader="Surplus" calculatedAmount={this.surplus()} />
<AmountBox panelClass="panel panel-warning" panelHeader="Debt" calculatedAmount={this.debt()} />
<AmountBox panelClass="panel panel-success" panelHeader="Total" calculatedAmount={this.total()} />
</div>
<RecordForm handleNewRecord={this.handleNewRecord}/>
<table className="table">
<thead>
<tr>
<th>Date</th>
<th>Title</th>
<th>Amount</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{records}
</tbody>
</table>
</div>
)
}
}
Amount Box component:
class AmountBox extends React.Component {
constructor(props) {
super(props);
}
render () {
return (
<div className="col-md-4">
<div className={this.props.panelClass}>
<div className="panel-heading">
<h3 className="panel-title">{this.props.panelHeader}</h3>
</div>
<div className="panel-body">
<p>
{this.props.calculatedAmount}
</p>
</div>
</div>
</div>
)
}
}
this.state.records[0].amount is undefined because on first render you are setting records to [] (in the constructor).
setState will trigger a second render, but in the first render the changes to state by setState will not apply.
So, you need some defensive code that makes sure that this.state.records have items.
surplus() {
this.state.records.length ? this.state.records[0].amount : 0;
}
for the following example I have a component GetCurrentVisitor which renders Visitors.
However it will only render the <h1> tag and the table is empty. I suspect I need to use ReactDOM to render Vistors component as well. But how to do it?
var VISITORS = [
{
first_name: 'Harry',
last_name: 'Potter'
},
{
first_name: 'Hermione',
last_name: 'Granger'
}
]
class GetCurrentVisitors extends React.Component {
constructor(props) {
super(props);
this.state = {
visitors: VISITORS
}
}
render () {
return (
<div>
<h1>Current visitors</h1>
<Visitors visitors={this.state.visitors} />
</div>
);
}
}
class Visitors extends React.Component {
constructor(props) {
super(props);
}
render () {
return (
<table>
{this.props.visitors.forEach(
function(visitor) {
<tr>
<td>
{console.log('from: ', visitor.first_name)}
{visitor.first_name}
</td>
</tr>
})}
</table>
);
}
}
ReactDOM.render(
<GetCurrentVisitors />, document.getElementById('getcurrentvisitors'))
In this case you should use .map instead of .forEach
{this.props.visitors.map(function(visitor, index) {
return <tr key={index}>
<td>{ visitor.first_name } </td>
</tr>
})}
Example
You can also are able to use .forEach but in another way fiddle
render () {
let itemList = [];
this.props.visitors.forEach(function(visitor,index) {
itemList.push(<tr key={index}><td>
{visitor.first_name}
</td></tr>
)
})
return (
<table>
{itemList}
</table>
);
}
As for me Array.prototype.map more easy to use with React. It just another example.
Thanks