_deleteQuote is not a function - reactjs

Cannot execute function declared in the Class component which has been passed to a function component.
There is a openBookDetails declared in the Class and that executes without a problem. I used the same logic to implement the deleteQuote function but I get a TypeError: _deleteQuote is not a function
I am trying to execute a function within a TableRow when a button is clicked however I get a typeerror.
import Modal from '../Modal/Modal'
import add from '../../images/add.png'
import addSelected from '../../images/addSelected.png'
import './Dashboard.css'
const TableRow = ({ row, openBookDetails, deleteQuote }) => (
<tr >
<th scope="row" onClick={openBookDetails}>{row.author}</th>
<td onClick={openBookDetails}>{row.quote}</td>
<td><i className="fa fa-close" onClick={deleteQuote}></i></td>
</tr>
)
const Table = ({ data, openBookDetails, deleteQuote }) => (
<table className="table table-hover">
<thead>
<tr className="table-active">
<th scope="col">Author</th>
<th scope="col">Quote</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{data.map((row, index) =>
<TableRow key={index} row={row} openBookDetails={() => openBookDetails(row, index)} deleteQuote={() => deleteQuote()}/>
)}
</tbody>
</table>
)
class Dashboard extends Component {
constructor() {
super()
this.state = {
quotes: [
{
"quote": "Our industry does not respect tradition - it only respects innovation.",
"author": "Satya Nadella",
"position": "CEO of Microsoft"
},
{
"quote": "Engineering is the closest thing to magic that exists in the world.",
"author": "Elon Musk",
"position": "Tesla and SpaceX CEO"
},
{
"quote": "For me, it matters that we drive technology as an equalizing force, as an enabler for everyone around the world.",
"author": "Sundar Pichai",
"position": "CEO of Google"
}
],
addSource: add,
isModalOpen: false,
index: '',
author: '',
quote: ''
}
}
onAddMouseOver = () => {
this.setState({ addSource: addSelected })
}
onAddMouseOut = () => {
this.setState({ addSource: add })
}
toggleModalOpenOrClose = () => {
this.setState({ isModalOpen: !this.state.isModalOpen })
this.setState({index: ''})
this.setState({author: ''})
this.setState({quote: ''})
}
openBookDetails = (row, index) => {
console.log('Row Clicked');
// console.log(index);
// console.log(row)
// console.log(row.author)
// console.log(row.quote)
// console.log(this.state.quotes[row])
// console.log(index)
this.setState({ isModalOpen: true});
this.setState({ index: index });
this.setState({ author: row.author });
this.setState({ quote: row.quote });
}
deleteQuote = () => {
this.setState({isModalOpen: false})
console.log('Row deleted')
}
addNewQuote = () => {
var quote = {
"quote": "There is no corruption in the system. The system is correuption",
"author": "Unknown",
"position": "Unknown"
}
console.log(this.state)
var quotes = this.state.quotes.concat(quote);
this.setState({ quotes: quotes });
}
render() {
return (
<div class='pt-3'>
<Table
data={this.state.quotes}
openBookDetails={this.openBookDetails} />
<div className='text-center align-items-center justify-content-centerpt-5'>
<a href='#add' onClick={this.toggleModalOpenOrClose}>
<img src={this.state.addSource} className='addButton mx-1' alt="add" onMouseOver={this.onAddMouseOver} onMouseOut={this.onAddMouseOut} />
</a>
</div>
<Modal
isModalOpen={this.state.isModalOpen}
toggleModalOpenOrClose={this.toggleModalOpenOrClose}
state={this.state}
addNewQuote={this.addNewQuote} />
</div>
)
}
}
export default Dashboard
When I click the ```<i className="fa fa-close" onClick={deleteQuote}></i>``` inside of the ```TableRow``` component I expect it to execute the ```deleteQuote``` function

You need to pass deleteQuote function as a prop to Table component when you calling it in Dashboard component in order to access it in Table component
So you need to change below line in Dashboard component
Change
<Table
data={this.state.quotes}
openBookDetails={this.openBookDetails} />
To
<Table
data={this.state.quotes}
openBookDetails={this.openBookDetails}
deleteQuote={this.deleteQuote} />

Related

[UPDATE][React]: Sorting component not rendering data

I have a table view in which I am fetching data from an API and then populating that data inside a table.
For some reason, the data inside tbody is not rendering and there are no errors either. I think it could be because I am not destructuring it correctly. But, unable to rectify it.
PS: the console logs inside tbody does work and they do display the information in the console.
UPDATE: doing the same thing using a simple table from reactstrap and that one works. But the first one with the sorting table is not working. Not sure where I am going wrong. I WOULD LIKE TO GO WITH THE SORTING TABLE PREFERABLY.
Code for Table View
import React from "react";
import axios from "axios";
// reactstrap components
import {
Card,
CardHeader,
CardBody,
CardTitle,
Row,
Col,
Table,
} from "reactstrap";
// core components
import SortingTable from "components/SortingTable/SortingTable.js";
class RegularTables extends React.Component {
constructor(props) {
super(props);
this.state = {
siteData: [],
isLoading: false,
};
}
signal = axios.CancelToken.source();
componentDidMount() {
this.handleGetEdgeSeverInfo();
}
componentUnMount() {
this.signal.cancel("Api is being canceled");
}
handleGetEdgeSeverInfo = async () => {
this.setState({ isLoading: true });
await axios
.get("http://www.mocky.io/v2/5ec3786f300000800039c0a5")
.then((response) => {
// handle success
this.setState({ siteData: response.data });
})
.catch((error) => {
// handle error
if (axios.isCancel(error)) {
console.log("Unable to fetch", error.message);
} else {
this.setState({ isLoading: false });
}
});
};
render() {
const { siteData } = this.state;
return (
<>
<div className="content">
<Row>
<Col className="mb-5" md="12">
<Card>
<CardHeader>
<CardTitle tag="h4">
table (sorting table)
</CardTitle>
<hr />
</CardHeader>
<CardBody>
<SortingTable
thead={[
{ text: "Ship" },
{ text: "technology" },
{ text: "https" },
{ text: "type" },
{ text: "Status" },
]}
tbody={siteData.map((data) => {
console.log("name:", data.site.name);
console.log("type:", data.https);
console.log("IMO:", data.site.attributes.type);
console.log("model:", data.technology);
console.log("status:", data.status);
return (
<div>
<tr key={data.site}>
<td>{data.site.name}</td>
<td>{data.kind}</td>
<td>{data.site.attributes.IMO}</td>
<td>{data.model}</td>
<td>{data.status}</td>
</tr>
</div>
);
})}
/>
</CardBody>
</Card>
</Col>
<Col className="mb-5" md="12">
<Card>
<CardHeader>
<CardTitle tag="h4">
table (simple table from reactstrap)
</CardTitle>
<hr />
</CardHeader>
<CardBody>
<Table>
<thead>
<tr>
<th>Ship</th>
<th>Type</th>
<th>IMO</th>
<th>Model</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{siteData.map((site) => {
console.log("name:", data.site.name);
console.log("type:", data.https);
console.log("IMO:", data.site.attributes.type);
console.log("model:", data.technology);
console.log("status:", data.status);
return (
<div>
<tr key={data.site}>
<td>{data.site.name}</td>
<td>{data.kind}</td>
<td>{data.site.attributes.IMO}</td>
<td>{data.model}</td>
<td>{data.status}</td>
</tr>
</div>
);
})}
</tbody>
</Table>
</CardBody>
</Card>
</Col>
</Row>
</div>
</>
);
}
}
export default RegularTables;
Code for sorting table component
class SortingTable extends React.Component {
constructor(props) {
super(props);
this.state = {
bodyData: props.tbody,
column: {
name: -1,
order: "",
},
};
}
sortTable = (key) => {
let { bodyData, column } = this.state;
let order = "";
if (
(column.name === key && column.order === "desc") ||
column.name !== key
) {
order = "asc";
bodyData.sort((a, b) =>
a.data[key].text > b.data[key].text
? 1
: a.data[key].text < b.data[key].text
? -1
: 0
);
} else if (column.name === key && column.order === "asc") {
order = "desc";
bodyData.sort((a, b) =>
a.data[key].text > b.data[key].text
? -1
: a.data[key].text < b.data[key].text
? 1
: 0
);
}
this.setState({
bodyData: bodyData,
column: {
name: key,
order: order,
},
});
};
render() {
const { bodyData, column } = this.state;
return (
<Table className="tablesorter" responsive>
<thead className="text-primary">
<tr>
{this.props.thead.map((prop, key) => {
return (
<th
className={classnames(
"header",
{
headerSortDown:
key === column.name && column.order === "asc",
},
{
headerSortUp:
key === column.name && column.order === "desc",
},
{
[prop.className]: prop.className !== undefined,
}
)}
key={key}
onClick={() => this.sortTable(key)}
>
{prop.text}
</th>
);
})}
</tr>
</thead>
<tbody>
{bodyData.map((prop, key) => {
return (
<tr
className={classnames({
[prop.className]: prop.className !== undefined,
})}
key={key}
>
{prop.data.map((data, k) => {
return (
<td
className={classnames({
[data.className]: data.className !== undefined,
})}
key={k}
>
{data.text}
</td>
);
})}
</tr>
);
})}
</tbody>
</Table>
);
}
}
SortingTable.propTypes = {
thead: PropTypes.arrayOf(
PropTypes.shape({
className: PropTypes.string,
text: PropTypes.string.isRequired,
})
).isRequired,
tbody: PropTypes.arrayOf(
PropTypes.shape({
className: PropTypes.string,
data: PropTypes.arrayOf(
PropTypes.shape({
className: PropTypes.string,
text: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
.isRequired,
})
),
})
).isRequired,
};
I think you need to return a single <tr> in siteData.map. Try this in CardBody
<CardBody>
<SortingTable
thead={[
{ text: "site" },
{ text: "https" },
{ text: "technology" },
{ text: "type" }
]}
tbody={siteData.map(data => {
console.log("name:", data.site.name);
console.log("type:", data.https);
console.log("IMO:", data.site.attributes.type);
console.log("model:", data.technology);
console.log("status:", data.status);
return (
<tr key={data.site}>
<td>{data.site.name}</td>
<td>{data.https}</td>
<td>{data.site.attributes.type}</td>
<td>{data.technology}</td>
<td>{data.status}</td>
</tr>
);
})}
/>
</CardBody>;
In SortingTable.render()
render() {
const { bodyData, column } = this.state;
return (
<Table className="tablesorter" responsive>
<thead className="text-primary">
<tr>
{this.props.thead.map((prop, key) => {
return (
<th
// className={classnames(
// "header",
// {
// headerSortDown:
// key === column.name && column.order === "asc"
// },
// {
// headerSortUp:
// key === column.name && column.order === "desc"
// },
// {
// [prop.className]: prop.className !== undefined
// }
// )}
key={key}
onClick={() => this.sortTable(key)}
>
{prop.text}
</th>
);
})}
</tr>
</thead>
<tbody>
{bodyData.map((prop, key) => {
return (
<tr
// className={classnames({
// [prop.className]: prop.className !== undefined
// })}
key={key}
>
{prop.props.children.map((data, k) => {
return (
<td
// className={classnames({
// [data.className]: data.className !== undefined
// })}
// key={k}
>
{data.props.children}
</td>
);
})}
</tr>
);
})}
</tbody>
</Table>
);
}
I Commented out some code to easily reproduce the issue

How to change the text of a single button in Reactjs

I am having a List of Jobs with Approve button.
Expected Result
Whenever the Approve button next to a Job is clicked, the Job should be approved and the button text should say Approved.
Original Outcome
The text of all the buttons gets changed to Approved, but only a single Job gets approved, i.e. the job the was actually approved. Also the text stays approved for all the jobs until the page is refreshed.
export default class ProfilePage extends Component {
constructor() {
super();
this.state = {
allJobs: [],
loading: true,
newLoading: false,
valueApprove: "Approve",
_id: ""
};
this.handleApproveJob = this.handleApproveJob.bind(this);
}
componentDidMount() {
var apiUrl = `http://localhost:5000/admin/pendingJobs`;
fetch(apiUrl)
.then(response => {
return response.json();
})
.then(data => {
let allJobsFromApi = data.PendingJobs.map(job => {
return {
jobmainid: job._id,
companyName: job.companyNane,
contactPerson: job.contactPerson,
jobTitle: job.jobTitle,
jobDescription: job.description,
jobCategory: job.category.name,
jobDuration: job.duration,
descriptionLink: job.descriptionLink,
status: job.status
}
})
this.setState({ allJobs: allJobsFromApi, loading: false });
})
.catch(error => {
console.log(error);
})
}
handleApproveJob(e) {
this.setState({
newLoading: true
})
e.preventDefault();
var jobId = e.target.getAttribute('jobId');
console.log(jobId);
const job = {
approvedJobId: jobId
};
approveJob(job).then((res, err) => {
if (res) {
this.setState({
newLoading: false,
valueApprove: "Approved"
})
} else {
this.setState({
loading: false,
message: "some error occured"
})
}
})
}
render() {
const { newLoading, valueApprove } = this.state;
return (
<div className="tile-body">
{loading ? <div className="col-md-4 offset-md-4"><img src=
{LoadingSpinner} /></div> : <table
className="table table-hover table-bordered table-responsive"
id="sampleTable"
style={{ border: "none" }}
>
<thead>
<tr>
<th>Company Name</th>
<th>Contact Person At Company</th>
<th>Job Title</th>
<th>Job Description</th>
<th>Job Description Link</th>
<th>Category</th>
<th>Duration</th>
<th>Edit</th>
<th>Approve</th>
</tr>
</thead>
<tbody>
{this.state.allJobs.map((job) =>
<tr key={job.jobmainid}>
<td>{job.companyName}</td>
<td>{job.contactPerson}</td>
<td>{job.jobTitle}</td>
<td>{job.jobDescription}</td>
<td>
{job.descriptionLink}
</td>
<td>{job.jobCategory}</td>
<td>{job.jobDuration}</td>
<td>
<Link to={"/admin/editJob/" + job.jobmainid}>
<button className="btn btn-warning">Edit</button>
</Link>
</td>
<td>
<button
type="submit"
className="btn btn-success"
jobId={job.jobmainid}
onClick={this.handleApproveJob}
>
{ newLoading ? <ButtonLoading /> : valueApprove }
</button>
</td>
</tr>
)}
</tbody>
</table>}
</div>
The code for aproveJob() function
//Approve Job
export const approveJob = job => {
return axios.post("http://localhost:5000/admin/approveJob", {
_id: job.approvedJobId
})
.then(response => {
return response.data;
})
.catch(err => {
console.log(err);
});
}
Right now you are only storing a single variable with the approved state. Instead we need one per button.
You could do something like the below code.
After getting the array jobs from the server inject a value approved: false
On button click find and update that item in your state with approved: true
I'm not certain the code below runs but it will point you in the right direction.
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
export const approveJob = (job) => {
return fetch('http://localhost:5000/admin/approveJob', {
_id: job.approvedJobId
})
.then((response) => {
return response.data
})
.catch((err) => {
console.log(err)
})
}
export default class ProfilePage extends Component {
constructor(props) {
super(props)
this.state = {
allJobs: [],
loading: true,
newLoading: false
}
this.handleApproveJob = this.handleApproveJob.bind(this)
}
componentDidMount() {
const apiUrl = `http://localhost:5000/admin/pendingJobs`
fetch(apiUrl)
.then((response) => {
return response.json()
})
.then((data) => {
const allJobsFromApi = data.PendingJobs.map((job) => {
return {
jobmainid: job._id,
companyName: job.companyNane,
contactPerson: job.contactPerson,
jobTitle: job.jobTitle,
jobDescription: job.description,
jobCategory: job.category.name,
jobDuration: job.duration,
descriptionLink: job.descriptionLink,
status: job.status,
approved: false
}
})
this.setState({ allJobs: allJobsFromApi, loading: false })
})
.catch((error) => {
console.log(error)
})
}
handleApproveJob(e, index) {
e.preventDefault()
this.setState({
newLoading: true
})
const jobId = e.target.getAttribute('jobId')
console.log(jobId)
const job = {
approvedJobId: jobId
}
approveJob(job).then((res) => {
if (res) {
const newAllJobs = this.state.allJobs
newAllJobs[index].approved = true
this.setState({
newLoading: false,
valueApprove: 'Approved',
allJobs: newAllJobs
})
} else {
this.setState({
loading: false,
message: 'some error occured'
})
}
})
}
render() {
const { loading, newLoading, valueApprove, allJobs } = this.state
return (
<div className="tile-body">
{loading ? (
<div className="col-md-4 offset-md-4">
<img src={LoadingSpinner} />
</div>
) : (
<table
className="table table-hover table-bordered table-responsive"
id="sampleTable"
style={{ border: 'none' }}
>
<thead>
<tr>
<th>Company Name</th>
<th>Contact Person At Company</th>
<th>Job Title</th>
<th>Job Description</th>
<th>Job Description Link</th>
<th>Category</th>
<th>Duration</th>
<th>Edit</th>
<th>Approve</th>
</tr>
</thead>
<tbody>
{allJobs.map((job, index) => (
<tr key={job.jobmainid}>
<td>{job.companyName}</td>
<td>{job.contactPerson}</td>
<td>{job.jobTitle}</td>
<td>{job.jobDescription}</td>
<td>
<a href={job.descriptionLink} target="_blank">
{job.descriptionLink}
</a>
</td>
<td>{job.jobCategory}</td>
<td>{job.jobDuration}</td>
<td>
<Link to={'/admin/editJob/' + job.jobmainid}>
<button className="btn btn-warning">Edit</button>
</Link>
</td>
<td>
<button
type="button"
className="btn btn-success"
jobId={job.jobmainid}
onClick={(e) => this.handleApproveJob(e, index)}
>
{newLoading ? <ButtonLoading /> : valueApprove}
</button>
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
)
}
}
try this.
handleApproveJob(e) {
this.setState({
newLoading: true
});
e.preventDefault();
var jobId = e.target.getAttribute('jobId');
console.log(jobId);
const job = {
approvedJobId: jobId
};
let self = this;
approveJob(job).then((res, err) => {
if (res) {
self.setState({
newLoading: false,
valueApprove: "Approved"
})
} else {
self.setState({
loading: false,
message: "some error occured"
})
}
})
}
<button type="submit" className="btn btn-success" jobId={job.jobmainid} onClick={this.handleApproveJob}>
{ this.state.newLoading ? <ButtonLoading /> : this.state.valueApprove }
</button>
I hope I've been there for you. Let me know.
Three things are there.
you need to add approve: false to every item in the jobs
On clicking approve button of any job, you need to pass index of that job in the approveJob(index) function.
Depending on the index value (which was passed in approveJob(index)) you need to updated the particular job in the job array in the state.
Just update the render function code for approve button, as you need change text on clicking button
render() {
const { loading, newLoading, valueApprove, allJobs } = this.state
return (
<div className="tile-body">
{loading ? (
<div className="col-md-4 offset-md-4">
<img src={LoadingSpinner} />
</div>
) : (
<table
className="table table-hover table-bordered table-responsive"
id="sampleTable"
style={{ border: 'none' }}
>
<tbody>
{allJobs.map((job, index) => (
<tr key={job.jobmainid}>
<td>
<button
type="button"
className="btn btn-success"
jobId={job.jobmainid}
onClick={(e) => this.handleApproveJob(e, index)}
>
{job.approved ? "approved" : "approve"}
</button>
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
)
}
}

react state is one state behind button clicks

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>
)
}

Set Component State Synchronously on ComponentWIllReveiveProps

I am passing props from Dashboard component to Modal Component when I click an item in the Dahboard table row. When the row is clicked, the data in that row is passed as props to the modal and the modal set its state on ComponenentWIllReceiveProps synchronously so it renders in the Modal Input box and textarea
How do I set the state in modal with props passed down from Dashboard.js and render them in the modal inpputbox and textarea
Dashboard.js
import React, { Component } from 'react'
import Modal from '../Modal/Modal'
import add from '../../images/add.png'
import addSelected from '../../images/addSelected.png'
import './Dashboard.css'
const TableRow = ({ row, openQuoteDetails, deleteQuote }) => (
<tr>
<th scope="row" onClick={openQuoteDetails}>{row.author}</th>
<td onClick={openQuoteDetails}>{row.quote}<small id="admin" className="form-text text-muted">{row.admin}</small></td>
<td><i className="fa fa-close" onClick={deleteQuote}></i></td>
</tr>
)
const Table = ({ data, openQuoteDetails, deleteQuote }) => (
<table className="table table-hover">
<thead>
<tr className="table-active">
<th scope="col">Author</th>
<th scope="col">Quote</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{data.map((row, index) =>
<TableRow key={index} row={row} openQuoteDetails={() => openQuoteDetails(row, index)} deleteQuote={() => deleteQuote(row, index)} />
)}
</tbody>
</table>
)
class Dashboard extends Component {
constructor() {
super()
this.state = {
quotes: [
{
"quote": "Our industry does not respect tradition - it only respects innovation.",
"author": "Satya Nadella",
"admin": "Joseph Akayesi"
},
{
"quote": "Engineering is the closest thing to magic that exists in the world.",
"author": "Elon Musk",
"admin": "Joseph Akayesi"
},
{
"quote": "For me, it matters that we drive technology as an equalizing force, as an enabler for everyone around the world.",
"author": "Sundar Pichai",
"admin": "Yasmin Adams"
}
],
addSource: add,
isModalOpen: false,
index: '',
author: '',
quote: ''
}
}
onAddMouseOver = () => {
this.setState({ addSource: addSelected })
}
onAddMouseOut = () => {
this.setState({ addSource: add })
}
toggleModalOpenOrClose = () => {
this.setState({ isModalOpen: !this.state.isModalOpen })
this.setState({ index: '' })
this.setState({ author: '' })
this.setState({ quote: '' })
}
openQuoteDetails = (row, index) => {
this.setState({ isModalOpen: true });
this.setState({ index: index, author: row.author, quote: row.quote })
}
deleteQuote = (row, index) => {
this.setState({ isModalOpen: false })
console.log('Row deleted')
console.log(this.state.quotes.splice(index, 1))
}
render() {
return (
<div className='pt-3'>
<Table
data={this.state.quotes}
openQuoteDetails={this.openQuoteDetails}
deleteQuote={this.deleteQuote} />
<div className='text-center align-items-center justify-content-centerpt-5'>
<a href='#add' onClick={this.toggleModalOpenOrClose}>
<img src={this.state.addSource} className='addButton mx-1' alt="add" onMouseOver={this.onAddMouseOver} onMouseOut={this.onAddMouseOut} />
</a>
</div>
<Modal
isModalOpen={this.state.isModalOpen}
toggleModalOpenOrClose={this.toggleModalOpenOrClose}
data={this.state}
onInputChange={this.onInputChange}
addNewQuote={this.addNewQuote}
updateExistingQuote={this.updateExistingQuote} />
</div>
)
}
}
export default Dashboard
Modal.js
import React, { Component } from 'react'
import './Modal.css'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { addQuote } from '../../actions/quoteActions'
class Modal extends Component {
constructor(props) {
super(props)
this.state = {
id: '',
author: '',
quote: '',
errors: {}
}
}
onInputChange = (event) => {
this.setState({ [event.target.id]: event.target.value })
}
switchSaveChangesAction = () => {
return this.props.state.index ? this.addNewQuote : this.updateExistingQuote
}
addNewQuote = () => {
const { user } = this.props.auth
const newQuote = {
admin: user.id,
quote: this.state.quote,
author: this.state.author,
}
console.log('Add New')
console.log(newQuote)
}
componentWillReceiveProps(nextProps) {
console.log('receive props')
if(nextProps.author !== this.props.author){
this.setState({ author: nextProps.author})
}
// this.setState({ id: this.props.data.index })
console.log(nextProps)
this.setState({ author: this.props.data.author }, () => console.log(this.state.author))
console.log(this.state)
}
updateExistingQuote = (index) => {
console.log('Update Existing')
console.log(this.props.state.author)
console.log(this.props.state.quote)
console.log(this.props.state.index)
}
render() {
let showOrHideModal = this.props.isModalOpen ? 'modal d-block' : 'modal d-none'
// let selectedQuoteDetails = {
// id: this.props.data.index ? this.props.data.index : '',
// author: this.props.data.author ? this.props.data.author : '',
// quote: this.props.data.quote ? this.props.data.quote : ''
// };
// let modalInputValue = selectedQuoteDetails ? selectedQuoteDetails : this.state
let saveChangesAction = this.props.data.index >= 0 ? this.updateExistingQuote : this.addNewQuote
return (
<div className={showOrHideModal}>
<div className='modal-dialog' role='document'>
<div className='modal-content'>
<div className='modal-header bg-light'>
<h5 className='modal-title'><b>Add a Quote</b></h5>
<button type='button' className='close' data-dismiss='modal' aria-label='Close' onClick={this.props.toggleModalOpenOrClose}>
<span aria-hidden='true'>×</span>
</button>
</div>
<div className='modal-body'>
<div className='form-group'>
<label htmlFor='author'>Author</label>
<input type='text' className='form-control' id='author' aria-describedby='emailHelp' placeholder='Enter author' onChange={this.onInputChange} defaultValue={this.state.author} />
</div>
<div className='form-group'>
<label htmlFor='quote'>Quote</label>
<textarea className='form-control' id='quote' rows='3' placeholder='Enter quote' onChange={this.onInputChange} value={this.state.quote}></textarea>
</div>
</div>
<div className='modal-footer'>
<button type='button' className='btn btn-primary' onClick={saveChangesAction}>Save changes</button>
<button type='button' className='btn btn-secondary' data-dismiss='modal' onClick={this.props.toggleModalOpenOrClose}>Close</button>
</div>
</div>
</div>
</div>
)
}
}
Modal.propTypes = {
addQuote: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired
}
const mapStateToProps = state => ({
auth: state.auth,
errors: state.errors
})
export default connect(mapStateToProps, { addQuote })(Modal)

Keys should be unique so that components maintain their identity across updates

I have A react component which renders a list of items that have been called from an API and set to setOfAllBooks state. Any time I search for the item, setOfAllBooks state is filtered through by the search ternm and the results are held in searchedBooks state. The results of searchedBooks are then passed to Table component and rendered in a list. At this point it works correctly, but when I search for another item it gets clustered in the Table. What I want to do is anytime I search a new Item after I have searched for a previos term I want the list-items in the Table component to be cleared to make way for the new items that have been searched.
import React, { Component } from 'react';
import './Home.css'
import axios from 'axios';
import Autosuggest from 'react-autosuggest';
var books = []
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
return inputLength === 0 ? [] : books.filter(book =>
book.title.toLowerCase().slice(0, inputLength) === inputValue);
};
const getSuggestionValue = suggestion => suggestion.title;
const renderSuggestion = suggestion => (
<div>
{suggestion.title}
</div>
);
const Table = ({ data }) => (
<table class="table table-hover">
<thead>
<tr class="table-primary">
<th scope="col">Title</th>
<th scope="col">Author</th>
<th scope="col">ISBN</th>
<th scope="col">No. Of Copies</th>
</tr>
</thead>
<tbody>
{data.map(row =>
<TableRow row={row} />
)}
</tbody>
</table>
)
const TableRow = ({ row }) => (
<tr class="table-light">
<th scope="row" key={row.title}>{row.title}</th>
<td key={row.author}>{row.author}</td>
<td key={row.isbn}>{row.isbn}</td>
<td key={row.isbn}>24</td>
</tr>
)
class Home extends Component {
constructor(props) {
super(props);
this.state = {
value: '',
suggestions: [],
setOfAllBooks: [],
searchedBooks: []
};
this.searchBook = this.searchBook.bind(this);
}
componentDidMount(){
axios.get('/api/book/viewAll')
.then(res => {
this.setState({ setOfAllBooks: res.data });
books = this.state.setOfAllBooks;
console.log(this.state.setOfAllBooks)
})
}
onChange = (event, { newValue }) => {
this.setState({
value: newValue
});
};
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: getSuggestions(value)
});
};
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
}
searchBook(event){
event.preventDefault();
this.setState({value: this.state.value});
this.state.searchedBooks = this.state.setOfAllBooks.filter(book => book.title == this.state.value);
this.setState({searchedBook: []})
console.log(this.state.searchedBook);
}
render() {
const { value, suggestions } = this.state;
const inputProps = {
placeholder: 'Enter the name of the book',
value,
onChange: this.onChange
}
return (
<div class="form-group col-lg-4">
<label for="exampleInputEmail1">Email address</label>
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
id="searchFor"
/>
<div className=" form-group">
<label htmlFor="searchFor"> </label>
<button class="form-control btn btn-success" type="submit" onClick={this.searchBook}>Search</button>
</div>
<Table data={this.state.searchedBooks} />
</div>
)
}
}
export default Home;
The results
The Error
You need to add the key prop to the TableRow component as <TableRow key={row.title} row={row} />. Remove the key where you have right now.
.... A good rule of thumb is that elements inside the map() call need keys.
... keys used within arrays should be unique among their siblings. . Doc.
So, it seems title what you used for key will still throw warnings, as they are not uniqe. If you have ID attribute in the row object use that. Adding key to TableRow will remove the first warning, but other warning still be there until title doesn't have the uniq values across all the data.

Resources