TypeError: this.state.map is not a function - javascript

UPDATE
Response was an array - but the issue came from the backend (Express/Build folder)
Coming back around to an issue that I came across awhile ago.
In my DEV environment - no issues. Once I deploy (Heroku), I am getting the "this.state.workorders.map is not a function". I also attempted "Object.keys and values" in the event it was being treated as such but just gives me back blank values.
This is what I get back below
const WorkOrder = props => (
<tr>
<td>{props.workorder.employee}</td>
<td>{props.workorder.description}</td>
<td>{props.workorder.duration}</td>
<td>{props.workorder.date.substring(0, 10)}</td>
<td>
<Link to={"/edit/" + props.workorder._id}>Edit</Link> |{" "}
<a
href="#"
onClick={() => {
props.deleteWorkOrder(props.workorder._id);
}}
>
Delete
</a>
</td>
<td>
<a
href="#"
onClick={() => {
props.markComplete(props.workorder._id);
}}
>
Completed
</a>
</td>
</tr>
);
class WorkOrdersList extends Component {
constructor(props) {
super(props);
this.state = {
workorders: []
};
this.deleteWorkOrder = this.deleteWorkOrder.bind(this);
this.markComplete = this.markComplete.bind(this);
}
onLogoutClick = e => {
e.preventDefault();
this.props.logoutUser();
};
componentDidMount = () => {
axios
.get("/workorders/")
.then(response => {
this.setState({ workorders: response.data });
console.log(response);
})
.catch(error => {
console.log(error);
});
};
deleteWorkOrder(id) {
axios.delete("/workorders/" + id).then(response => {
console.log(response.data);
});
this.setState({
workorders: this.state.workorders.filter(el => el._id !== id)
});
}
markComplete(id) {
axios
.patch("/workorders/" + id, { isComplete: "true" })
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
this.setState({
workorders: this.state.workorders.filter(el => el._id !== id)
});
}
workordersList = () => {
return this.state.workorders.map(currentworkorder => {
return (
<WorkOrder
workorder={currentworkorder}
deleteWorkOrder={this.deleteWorkOrder}
markComplete={this.markComplete}
key={currentworkorder._id}
/>
);
})
);
};
render() {
return (
<div className="containerMax">
<div className="row">
<div className="col-9">
<h3>Open Work Orders</h3>
</div>
<div className="col-3">
<button
type="button"
class="btn btn-outline-danger"
onClick={this.onLogoutClick}
>
Logout
</button>
</div>
</div>
<table className="table">
<thead className="thead-light">
<tr>
<th>Employee</th>
<th>Description</th>
<th>Duration (minutes)</th>
<th>Scheduled Date</th>
<th>Actions</th>
<th>Completed Job</th>
</tr>
</thead>
<tbody>{this.workordersList()}</tbody>
</table>
<br />
</div>
);
}
}
WorkOrdersList.propTypes = {
logoutUser: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(mapStateToProps, { logoutUser })(WorkOrdersList);

Check in your function componentDidMount() if response.data is an Array:
componentDidMount = () => {
axios
.get("/workorders/")
.then(response => {
this.setState({ workorders: response.data }); <============ HERE
console.log(response);
})
.catch(error => {
console.log(error);
});
};
Then, validate your componentDidMount() function:
workordersList = () => {
if (this.state.workorders && this.state.workorders.length) {
return this.state.workorders.map(currentworkorder => {
return (
<WorkOrder
workorder={currentworkorder}
deleteWorkOrder={this.deleteWorkOrder}
markComplete={this.markComplete}
key={currentworkorder._id}
/>
);
});
} else { return []; }
};

Try binding workordersList in the WorkOrdersList component constructor.
It will look like this:
constructor(props) {
super(props);
this.state = {
workorders: []
};
this.deleteWorkOrder = this.deleteWorkOrder.bind(this);
this.markComplete = this.markComplete.bind(this);
this.workordersList = this.workordersList.bind(this);
}
Also you need to wait until axios in componentDidMount method completely loads all the workorders.
So in your render, in tbody tag you can put the following:
{this.state.workorders.length > 0 ? this.workordersList() : ""}
Hope it helps

Related

React: Not able to write anything in the Textbox, not able to edit?

I am able to pre-fill the textboxes on page load with data in the list coming from api.
Now If user needs to modify the text entered in the textbox, editing textbox is not working. Not able to type anything in the textbox. Please check my code where I am doing wrong. TemplateName and TemplateDescr textboxes not able to type.
import React from "react";
export class Edit_Textbox extends React.Component {
constructor(props) {
super(props);
this.state = {
Template_ID: "",
TemplateInfolist: [],
TemplateName: "",
TemplateDescr:"",
}
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
if (typeof this.props.key_id !== 'undefined') {
const Template_ID = this.props.key_id;
if (Template_ID > 0) {
this.getProductTemplateInfo(Template_ID);
}
}
}
componentDidUpdate(prevProps) {
const Template_ID = this.props.key_id;
if (prevProps.key_id !== this.props.key_id) {
console.log(`key_id: ${this.props.key_id}`);
this.getProductTemplateInfo(Template_ID);
}
}
getProductTemplateInfo(Template_ID) {
fetch(REQUEST_URL, { "Content-Type": "application/xml; charset=utf-8" })
.then(response => response.json())
.then((data) => {
this.setState({
Template_ID: this.props.key_id,
TemplateInfolist: data,
loading: false
})
console.log(this.state.TemplateInfolist);
})
}
handleSubmit(event) {
event.preventDefault();
const Template_ID = this.props.key_id;
const TemplateName = this.state.TemplateName;
const TemplateDescr = this.state.TemplateDescr;
const data = {
Template_ID,
TemplateName,
}
fetch(REQUEST_URL, {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' }
})
.then(response => response.json())
.then((data) => {
this.setState({
ValidationStatus: data
})
})
.catch(error => console.error('Error:', error))
.then(response => console.log('Success', data));
}
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit} >
<div>
{
(this.state.TemplateInfolist.map((item, index) => {
return (
<table border="0" width="100%">
<tr><td> <input type="text" value={item.templateName} onChange={(ev) => this.setState({ TemplateName: ev.target.value })} size="75" maxlength="150" />
<input type="text" value={item.templateDescr} onChange={(ev) => this.setState({ TemplateDescr: ev.target.value })} size="75" maxlength="150" />
</td></tr>
<tr><td><button type="submit" onclick="ResetSession();">Submit</button>
</td></tr>
</table>
)
}
))
}
</div>
</form>
</div>
);
}
}
export default Edit_Textbox;
you are not changing the same variable, in the value prop you have item.templateName (the same as this.state.TemplateInfolist[x].templateName) and in the onChange prop you have this.state.templateName
So, you have to modify this.state.TemplateInfolist in the exact position of the array, you should have an Id in the array to identify it (item.id).
I can propose this for the onChange prop in the input Text:
onChange={(ev) => {
this.setState(prevState => {
const TemplateInfolist = prevState.TemplateInfolist.map(info => {
if (info.id === item.id) {
info.templateName = ev.target.value;
}
return info;
})
return {
TemplateInfolist
}
})
}
}
Just be sure that the fetched data has the ID in each item of the array.

display button upon typing input react

I want to be able to type into my input fields, and then have a button show up beside it upon typing that says submit edit. right now, I have a button that always is there, but I want it to only show up upon typing. this is all in react btw. so far, I have tried jquery, but react doesn't like it.
here's the whole page, to avoid any confusion of what I am doing and where my stuff is located.
import React, { Component } from "react";
import axios from "axios";
import "../styles/TourPage.css";
class TourPage extends Component {
constructor(props) {
super(props);
this.state = {
myData: [],
isLoading: true,
};
}
componentDidMount() {
axios
.get("/getResults")
.then((res) => {
this.setState({
myData: res.data
});
})
.catch((error) => {
// Handle the errors here
console.log(error);
})
.finally(() => {
this.setState({
isLoading: false
});
});
}
deleteById = (id) => {
console.log(id)
axios
.post(`/deleteDoc`, {id: id} )
.then(() => {
console.log(id, " worked")
window.location = "/tour"
})
.catch((error) => {
// Handle the errors here
console.log(error);
})
}
editById = (id, siteLocation, Services, cnum) => {
console.log(id, siteLocation, Services, cnum)
axios
.post(`/editDoc`, JSON.stringify({id: id, location: siteLocation, Services: Services, cnum: cnum}),{
headers: {
"Content-Type": "Application/json"
}
} )
.then(() => {
console.log(id, " worked")
window.location = "/tour"
})
.catch((error) => {
// Handle the errors here
console.log(error);
})
}
render() {
// You can handle the loader part here with isLoading flag. In this case No data found will be shown initially and then the actual data
let { myData, isLoading } = this.state;
return (
<table id="customers">
<tr>
<th>siteLocation</th>
<th>Services</th>
<th>cnum</th>
</tr>
{myData.length > 0
? myData.map(({ location, Services, cnum, _id }, index) => (
<tr key={index}>
<td><input type="text" placeholder={location} name="location" id="location" /> </td>
<td><input type="text" placeholder={Services} name="Services" id="Services" /> </td>
<td><input type="text" placeholder={cnum} name="cnumhide" id="cnumhide" /> </td>
<td><input type="hidden" placeholder={cnum} name="cnum" id="cnum" /> </td>
<button
onClick={(e) => {
e.preventDefault();
this.deleteById(_id);
}}
disabled={isLoading}
>
Delete
</button>
<button
onClick={(e) => {
e.preventDefault();
var siteLocation = document.getElementById('location').value
var Services = document.getElementById('Services').value
var cnum = document.getElementById('cnum').value
this.editById(_id, siteLocation, Services, cnum)
}}
>
Submit Edit
</button>
</tr>
))
: "No Data Found"}
</table>
);
}
}
const script = document. createElement("script"); $('input').keyup(function(){
if($.trim(this.value).length > 0)
$('#location').show()
else
$('#location').hide()
});
export default TourPage;
thanks 4 the help in advance.
You can use onfocus() in the text element. If you want to hide the button, use onfocusout() or in case if you want to track only after input has changed, use onchange() event
...
//class function
onTyping =()=>{
this.setState({
showSubmit:true
})
}
...
//render part
render(){
...
//input which you want to track typing
<input type="text" onfocus={()=>this.onTyping()} placeholder={location} name="location" id="location" />
...
//element submit button
{this.state.showSubmit && <button
onClick={(e) => {
e.preventDefault();
var siteLocation = document.getElementById('location').value
var Services = document.getElementById('Services').value
var cnum = document.getElementById('cnum').value
this.editById(_id, siteLocation, Services, cnum)
}}
>
Submit Edit
</button>}
...
Here is an example that helps you,
const {
useState
} = React;
const Test = () => {
const [show, setShow] = useState(false);
const handleChange = (event) => {
if (event.target.value.length > 0)
setShow(true);
else
setShow(false)
}
return ( <div>
<input type = "text"
onChange = {
(event) => handleChange(event)
}/>
{show && < button > Submit changes now! </button>}
</div>
)
}
ReactDOM.render( < Test / > ,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
There is a way to avoid jquery and continue using your react class component to achieve this.
Map over state.myData to render each item with an input and a button.
Use the array index with the input's onChange event callback to add the inputValue into the correct array item's object within state.
Use the array index with the button's onClick event callback to get the item from state.myData before sending it to the server.
If there is an inputValue for the item, you can conditionally render the button.
import React, { Component } from "react";
import axios from "axios";
class TourPage extends Component {
constructor(props) {
super(props);
this.state = {
myData: [],
isLoading: true
};
}
componentDidMount() {
axios
.get("https://rickandmortyapi.com/api/character")
.then((res) => {
this.setState({
myData: res.data.results
});
})
.finally(() => {
this.setState({
isLoading: false
});
});
}
handleChangeInput = ({ target }, index) => {
const newData = [...this.state.myData];
newData[index].inputValue = target.value;
this.setState({
myData: newData
});
};
handleSubmitEdit = (index) => {
const item = this.state.myData[index];
// submit the edit to the api
console.log(
`Clicked on 'submit edit' for ${item.name} with value ${item.inputValue}`
);
};
render() {
let { myData, isLoading } = this.state;
if (isLoading) {
return "loading...";
}
return (
<div>
{myData.map(({ name, status, species, inputValue }, index) => {
return (
<div key={index}>
<p>{`${name} - ${species} - ${status}`}</p>
<input
type="text"
onChange={(e) => this.handleChangeInput(e, index)}
value={inputValue || ""}
/>
{inputValue && (
<button onClick={() => this.handleSubmitEdit(index)}>
Submit Edit
</button>
)}
</div>
);
})}
</div>
);
}
}
export default TourPage;
If you wanted to have an input per field within each row, you could make some small changes and save your edits to the item's state within a nested object.
Then you could check if there was anything inside that row's edits object to conditionally show the submit button per row.
import React, { Component } from "react";
import axios from "axios";
import isEmpty from "lodash.isempty";
import pick from "lodash.pick";
class TourPage extends Component {
constructor(props) {
super(props);
this.state = {
myData: [],
isLoading: true
};
}
componentDidMount() {
axios
.get("https://rickandmortyapi.com/api/character")
.then((res) => {
this.setState({
// here we create an empty 'edits' object for each row
myData: res.data.results.map((d) => ({
...pick(d, ["name", "status", "species"]),
edits: {}
}))
});
})
.finally(() => {
this.setState({
isLoading: false
});
});
}
handleChangeInput = ({ target }, index) => {
const newData = [...this.state.myData];
const { value, name } = target;
newData[index].edits[name] = value;
this.setState({
myData: newData
});
};
handleSubmitEdit = (index) => {
const item = this.state.myData[index];
// submit the edit to the api
console.log(`Clicked on 'submit edit' for ${item.name} with edits:`);
console.log(item.edits);
console.log("Updated item: ");
const { edits, ...orig } = item;
const newItem = { ...orig, ...edits };
console.log(newItem);
// Once saved to api, we can update myData with newItem
// and reset edits
const newData = [...this.state.myData];
newData[index] = { ...newItem, edits: {} };
this.setState({
myData: newData
});
};
showButton = (index) => {
const { edits } = this.state.myData[index];
return !isEmpty(edits);
};
render() {
let { myData, isLoading } = this.state;
if (isLoading) {
return "loading...";
}
return (
<table>
<tbody>
{myData.map((row, index) => {
const { edits, ...restRow } = row;
return (
<tr key={index}>
{Object.keys(restRow).map((col) => {
return (
<td>
<label>
{col}:
<input
name={col}
value={edits[col] || restRow[col]}
onChange={(e) => this.handleChangeInput(e, index)}
/>
</label>
</td>
);
})}
<td>
{this.showButton(index) && (
<button onClick={() => this.handleSubmitEdit(index)}>
Submit Edit
</button>
)}
</td>
</tr>
);
})}
</tbody>
</table>
);
}
}
export default TourPage;

Why local state also changed when redux store updated

Now I'm building an application using react redux store and local store.
I have two components "tweetTable_Comp" and "likeButton_Comp".
The redux store has all the tweets record "tweets" fetched by an API, and tweetTable_Comp has a local state "filteredTweets" so as to add filter function later and show only selected genre tweets.
And every tweet has likingUserIds.
tweetTable_Comp passes likingUserIds as props to likeButton_Comp so that it can add different style depending on if you already liked the tweet or not.
The problem here is that changing the "tweets[indexNum].likingUserIds" in the redux store when user push like button also affects on the local state "filteredTweets[indexNum].likingUserIds".
I was gonna change the redux info and local state info one by one like in deleteTweet function which already works well.
But this is not intentionally working.
Can anyone teach me why this is happening?
here is reducer.js
redux tweets has objects as below
・title(string)
・text(string)
・createdDate(string)
・likingUserIds(array)
・userId(number)
const defaultState = {
user: {
loggedIn: false,
id: 0,
account: ''
},
tweets: []
}
export default function reducer(state = defaultState, action) {
switch (action.type) {
case 'UPDATE_TWEETS':
return {
...state,
tweets: action.tweets
}
default:
return state;
}
}
here is actions.js
export function getTweets(tweets){
return {
type: 'UPDATE_TWEETS',
tweets: tweets
}
}
here is tweetTable_Comp
class TweetTable_Comp extends Component{
constructor(props){
super(props)
const {dispatch} = props;
this.action = bindActionCreators(actions, dispatch);
this.deleteButtonClicked = this.deleteButtonClicked.bind(this)
this.editButtonClicked = this.editButtonClicked.bind(this)
this.handleChanged = this.handleChanged.bind(this)
this.state = {
filteredTweets: [],
searchWord: ""
}
}
handleChanged(e){
this.setState({[e.target.name]: e.target.value})
}
deleteButtonClicked(id, index){
confirm("削除しますか?") &&
this.deleteTweet(id, index)
}
editButtonClicked(id){
this.props.history.push("/edit/" + id)
}
deleteTweet(id, index){
fetch("http://localhost:8080/twitter/deleteTweet/" + id, {
method: "DELETE"
})
.then((response) => {
if(response.status === 200) {
const newList = this.props.tweets.slice()
newList.splice(index, 1)
this.action.getTweets(newList)
this.setState({filteredTweets: newList})
}
})
}
componentDidMount(){
fetch("http://localhost:8080/twitter/sendAllTweets", {
method: "GET"
})
.then((response) => {
response.json()
.then(json => {
this.action.getTweets(json)
this.setState({filteredTweets: json.slice()})
})
})
}
render(){
return(
<>
<h1 className="text-center">tweet一覧</h1>
<SearchBar searchWord={this.state.searchWord} handleChanged={this.handleChanged}/>
<Container>
<Row>
<Col>
<br/>
<br/>
<Table striped bordered hover>
<thead>
<tr className="text-center">
<th>投稿日</th>
<th>投稿者</th>
<th>タイトル</th>
<th>内容</th>
<th>いいね</th>
<th>削除</th>
<th>編集</th>
</tr>
</thead>
<tbody>
{ this.state.filteredTweets.map((tweet, index) => (
<tr key={tweet.id}>
<td className="text-center">{tweet.createdDate}</td>
<td className="text-center">{tweet.user.account}</td>
<td>{tweet.title}</td>
<td>{tweet.text}</td>
<td className="text-center">
<LikeButton likingUserIds={tweet.likingUserIds} index={index} id={tweet.id} />
</td>
<td className="text-center">
<Button variant="outline-secondary" onClick={() => this.deleteButtonClicked(tweet.id, index)}>
<FontAwesomeIcon icon={faTrashAlt} />
</Button>
</td>
<td className="text-center">
<Button variant="outline-secondary" onClick={() => this.editButtonClicked(tweet.id)}>
<FontAwesomeIcon icon={faEdit} />
</Button>
</td>
</tr>
))
}
</tbody>
</Table>
</Col>
</Row>
</Container>
</>
)
}
}
TweetTable_Comp.propTypes = {
dispatch: PropTypes.func,
tweets: PropTypes.array,
history: PropTypes.object,
user:PropTypes.object
}
function mapStateToProps(state){
return state
}
export default withRouter(connect(mapStateToProps)(TweetTable_Comp))
here is likeButton_Comp
class LikeButton_Comp extends Component {
constructor(props){
super(props)
const {dispatch} = props
this.action = bindActionCreators(actions, dispatch)
this.likeButtonClicked = this.likeButtonClicked.bind(this)
}
likeButtonClicked(func, index){
const data = {
userId:this.props.user.id,
tweetId:this.props.id
}
if(func === "unlike"){
fetch("http://localhost:8080/twitter/like", {
method: "DELETE",
body: JSON.stringify(data)
})
.then((response) => {
if(response.status === 200){
let tweets = this.props.tweets.slice()
const orgLikingUsers = this.props.tweets[index].likingUserIds.slice()
const newLikingUsers = orgLikingUsers.filter(item => item !== this.props.user.id)
tweets[index].likingUserIds = newLikingUsers
this.action.getTweets(tweets)
} else {
alert("処理に失敗しました")
}
})
.catch(error => console.error(error))
} else {
fetch("http://localhost:8080/twitter/like", {
method: "POST",
body: JSON.stringify(data)
})
.then((response) => {
if(response.status === 200){
let tweets = this.props.tweets.slice()
let likingUsers = this.props.tweets[index].likingUserIds.slice()
likingUsers.push(this.props.user.id)
tweets[index].likingUserIds = likingUsers
this.action.getTweets(tweets)
} else {
alert("処理に失敗しました")
}
})
.catch(error => console.error(error))
}
}
render(){
return(
<>
<span>{this.props.likingUserIds.length} </span>
{this.props.tweets.length > 0 && this.props.likingUserIds.includes(this.props.user.id) ?
<Button variant="outline-danger">
<FontAwesomeIcon icon={faHeart} onClick={() => this.likeButtonClicked("unlike", this.props.index)}/>
</Button> :
<Button variant="outline-secondary">
<FontAwesomeIcon icon={faHeart} onClick={() => this.likeButtonClicked("like", this.props.index)}/>
</Button>
}
</>
)
}
}
LikeButton_Comp.propTypes = {
dispatch: PropTypes.func,
user: PropTypes.object,
tweets: PropTypes.array,
likingUserIds: PropTypes.array,
index: PropTypes.number,
id: PropTypes.number
}
function mapStateToProps(state){
return state
}
export default withRouter(connect(mapStateToProps)(LikeButton_Comp))

TypeError: this.state.workorders.map is not a function

MERN app deployed to Heroku - All works well in my dev environment but once I deploy and receive a successful build to production; My landing page and login are fine but once routed to the Home page where a list of workorders should appear - just a blank page with the console stating that "TypeError: this.state.workorders.map is not a function"
I've looked around quite a bit trying to resolve this - I understand that you can only map through an array, etc. but I am sure missing something. Any help is greatly appreciated and will provide more information if the code below is not enough or too vague.
class WorkOrdersList extends Component {
constructor(props) {
super(props);
this.deleteWorkOrder = this.deleteWorkOrder.bind(this);
this.markComplete = this.markComplete.bind(this);
this.state = {
workorders: []
};
}
onLogoutClick = e => {
e.preventDefault();
this.props.logoutUser();
};
componentDidMount() {
axios
.get("/workorders/")
.then(response => {
this.setState({ workorders: response.data });
})
.catch(error => {
console.log(error);
});
}
deleteWorkOrder(id) {
axios.delete("/workorders/" + id).then(response => {
console.log(response.data);
});
this.setState({
workorders: this.state.workorders.filter(el => el._id !== id)
});
}
markComplete(id) {
axios
.patch("/workorders/" + id, { isComplete: "true" })
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
this.setState({
workorders: this.state.workorders.filter(el => el._id !== id)
});
}
workordersList() {
return this.state.workorders.map(currentworkorder => {
return (
<WorkOrder
workorder={currentworkorder}
deleteWorkOrder={this.deleteWorkOrder}
markComplete={this.markComplete}
key={currentworkorder._id}
/>
);
});
}
render() {
return (
<div className="containerMax">
<div className="row">
<div className="col-9">
<h3>Open Work Orders</h3>
</div>
<div className="col-3">
<button
type="button"
class="btn btn-outline-danger"
onClick={this.onLogoutClick}
>
Logout
</button>
</div>
</div>
<table className="table">
<thead className="thead-light">
<tr>
<th>Employee</th>
<th>Description</th>
<th>Duration (minutes)</th>
<th>Scheduled Date</th>
<th>Actions</th>
<th>Completed Job</th>
</tr>
</thead>
<tbody>{this.workordersList()}</tbody>
</table>
<br />
</div>
);
}
}
Try to change workordersList to arrow function. The problem can be because this in workordersList might not referring to the current instance.
workordersList = () => {
return this.state.workorders.map(currentworkorder => {
return (
<WorkOrder
workorder={currentworkorder}
deleteWorkOrder={this.deleteWorkOrder}
markComplete={this.markComplete}
key={currentworkorder._id}
/>
);
});
}
Try to log response.data in componentDidMount() it seems that response.data is not an array.
And one more thing, it is not recommended to directly use this.state in this.setState(), a better approach is to use preState like this
this.setState((preState) => ({
workorders: preState.workorders.filter(el => el._id !== id)
}));
Resulted in the express/build needing some extra eyes on the syntax and layout :)
The call did return an array, therefore issue resolved.

Why it is giving me 'Cannot read property 'deleteProduct' of undefined' error react Js

I am getting an error when deleting one row in react js. error is 'Cannot read property 'deleteProduct' of undefined'. also is there any simple way to delete data from the database using custom api. below is my complete code for deleting data from the database.
Here is my code for deleting row-
import React from 'react';
import ReactDOM from 'react-dom';
export default class FetchedData extends React.Component{
constructor(props){
super(props);
this.state={
UserData:[],
response: {}
};
this.headers=[
{key:1,label:'Name'},
{key:2,label:'Department'},
{key:3,label:'Marks'},
];
this.deleteProduct=this.deleteProduct.bind(this);
}
componentDidMount(){
this.lookupInterval = setInterval(() => {
fetch("https://www.veomit.com/test/zend/api/fetch.php")
.then(response => {
return response.json();
})
.then(result => {
this.setState({
UserData:result
})
.catch(error => {
console.log(
"An error occurred while trying to fetch data from Foursquare: " +error
);
});
});
}, 500)
}
deleteProduct(userId) {
const { UserData } = this.state;
const apiUrl = 'https://www.veomit.com/test/zend/api/delete.php';
const formData = new FormData();
formData.append('userId', userId);
const options = {
method: 'POST',
body: formData
}
fetch(apiUrl, options)
.then(res => res.json())
.then(
(result) => {
this.setState({
response: result,
UserData: UserData.filter(item => item.id !== userId)
});
},
(error) => {
this.setState({ error });
}
)
}
render(){
return(
<div>
<table class="table table-bordered">
<thead>
<tr>
{
this.headers.map(function(h) {
return (
<th key = {h.key}>{h.label}</th>
)
})
}
</tr>
</thead>
<tbody>
{
this.state.UserData.map(function(item, key) {
return (
<tr key = {key}>
<td>{item.name}</td>
<td>{item.department}</td>
<td>{item.marks}</td>
<td><span onClick={() => this.deleteProduct(item.id)}>Delete</span></td>
</tr>
)
})
}
</tbody>
</table>
</div>
);
}
}
Please help me remove this error.
thanks in advance.
Your mapping function is creating a new scope:
this.state.UserData.map(function(item, key) {
return (
<tr key = {key}>
<td>{item.name}</td>
<td>{item.department}</td>
<td>{item.marks}</td>
<td><span onClick={() => this.deleteProduct(item.id)}>Delete</span></td>
</tr>
)
})
Making it an arrow function should solve the issue:
this.state.UserData.map((item, key) => {
return (
<tr key = {key}>
<td>{item.name}</td>
<td>{item.department}</td>
<td>{item.marks}</td>
<td><span onClick={() => this.deleteProduct(item.id)}>Delete</span></td>
</tr>
)
})
This is probably due to losing context here:
{
this.state.UserData.map(function(item, key) {
return (
<tr key = {key}>
<td>{item.name}</td>
<td>{item.department}</td>
<td>{item.marks}</td>
<td><span onClick={() => this.deleteProduct(item.id)}>Delete</span></td>
</tr>
)
})
}
Change the function to an arrow function to autobind the callback:
{
this.state.UserData.map((item, key) => {
return (
<tr key = {key}>
<td>{item.name}</td>
<td>{item.department}</td>
<td>{item.marks}</td>
<td><span onClick={() => this.deleteProduct(item.id)}>Delete</span></td>
</tr>
)
})
}

Categories

Resources