I have an error of Cannot read property of map, I'm trying to display a table of content on my page by getting the data from the database using restful API and store it into an array.
first time I open the site everything works fine and it displays the table with no problem but when I refresh it gives the error.
This is my function for getting the data and display it in a table using map:
const ListTodos = ()=>{
const [todos,setToods]=useState([]);
const getTodos = async ()=>
{
try{
const res = await fetch("http://localhost:3080/get")
console.log(res)
const jsondata = await res.json();
setToods(jsondata);
}catch(err){
console.error(err.message);
}
}
useEffect(() => {
getTodos();
}, []);
arr = todos[0];
return (
<Fragment>
<table class="table mt-5 text-center">
<thead>
<tr>
<th>ID_Event</th>
<th>Name_Event</th>
<th>Date</th>
<th>Address</th>
<th>Duration</th>
<th>Description</th>
<th>Delete</th>
<th></th>
</tr>
</thead>
<tbody>
{arr.map(arr => (
<tr>
<td>{arr.ID_Event}</td>
<td>{arr.Name_Event}</td>
<td>{arr.Date}</td>
<td>{arr.Address}</td>
<td>{arr.Duration}</td>
<td>{arr.description}</td>
<td>
<button className="btn btn-danger" onClick={() => deleteevnet(arr.ID_Event)}>Delete</button>
</td>
</tr>
))}
</tbody>
</table>
</Fragment>
);
}
By writing arr = todos[0] on top level you are creating a global variable which is undefined until the data is fetched (first element of empty array).
You should declare it ideally as const so it is local to the functional component, and handle the case when it's undefined with something like:
{arr && arr.map(arr => (
<tr>
<td>{arr.ID_Event}</td>
<td>{arr.Name_Event}</td>
<td>{arr.Date}</td>
<td>{arr.Address}</td>
<td>{arr.Duration}</td>
<td>{arr.description}</td>
<td>
<button className="btn btn-danger" onClick={() => deleteevnet(arr.ID_Event)}>Delete</button>
</td>
</tr>
))}
Add a conditional so it runs when your arr is not undefined:
{arr !== "undefined" && arr.map(arr => (
React evaluates our return statement, when it hits the arr.map(...)
line its actually running undefined.map(...) which is obviously an
error in JavaScript.
Read more here
Use the following programming construct to ensure the arr has value inside it:
{arr && arr.map(arr => (
// ...
))}
The useEffect hook works asynchronously so on the first run the arr would be undefined:
arr = todos[0]; // [][0] === undefined
Related
I am trying to render a table on expanding the icon
expandable={{
expandedRowRender: () => {
return (
<table>
<tbody>
{props.data.map(obj => {
Object.entries(obj).forEach(([key, value]) => {
return (
<tr>
<td>{key}</td>
<td>{value}</td>
</tr>
)
})
})}
</tbody>
</table>
)
},
but this isnt rendering anything, The table data is dynamic keys and values from the object. can someone tell me what im doing wrong here?
I have a simple app with 3 React components stacked on top of another:
function App() {
return (
<Fragment>
<div className="container">
<ListSuppliers/>
<InputContact/>
<ListContact/>
</div>
</Fragment>
);
}
In my ListSuppliers component I have a dropdown menu, in my InputContact component I have an input form, and in my ListContact I have an html table like so:
return <Fragment>
<h1>List Contact</h1>
<table className="table mt-5">
<thead>
<tr>
<th>Contact Name</th>
<th>Edit</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{contacts.map(contact => (
<tr key={contact.contact_id}>
<td>{contact.contact_name}</td>
<td><EditContact contact={contact}/></td>
<td><button className="btn btn-danger" onClick={()=> deleteContact(contact.contact_id)}>Delete</button></td>
</tr>
))}
</tbody>
</table>
</Fragment>
I want my html table in the ListContact component to be populated based on the selection from the menu in the ListSuppliers component:
//Select function
const chooseSupplier = async (id) => {
try {
const response = await fetch(`http://localhost:5000/contact_supplier/${id}`,{
method: "GET"
});
const jsonData = await response.json();
console.log(jsonData);
} catch (err) {
console.log(err.message);
}
}
//Route
app.get("/contact_supplier/:id", async (req, res) => {
try {
const {id} = req.params;
const contact = await pool.query('SELECT * FROM contact WHERE supplier_id = $1 ORDER BY contact_id ASC', [id]);
res.json(contact.rows);
} catch (err) {
console.error(err.message);
}
})
So far I am able to receive the query that I need in json format, however I'm not sure how to query from one component target to an object in another in this case.
You need to maintain the state in the parent component (here - App.js)
for const [state,setState] = useState() and then pass the state and setState to the children where they can access them and call them.
I am trying to call an async function using .map() in REACT to populate table data cells. The async function is calling the backend of my application using data from the array that is being iterated through.
The function that is being called returns a number when called properly.
This is my first time posting a question so any help is great! If I need to add more information I will.
Thank you.
{tableInfo.map(info => (
<tr>
<td key={info.name}>{info.name}</td>
<td key={info.price}>{info.price}</td>
<td key={info.amount}>{info.amount}</td>
<td>
{async () => await fetchCurrentPrice.getPrice(info.name)}
</td>
</tr> ))}
async functions always return promises. You can't fetch data asynchronously during rendering.
You need a useEffect hook to run the function which gathers the data, and then a useState hook to store it, along with logic to show a loading state while you wait for it.
const MyRow = ({ info }) => {
const [currentPrice, setCurrentPrice] = useState(null);
useEffect(() => {
fetchCurrentPrice.getPrice(info.name).then(setCurrentPrice);
}, [info]);
return <tr>
<td key={info.name}>{info.name}</td>
<td key={info.price}>{info.price}</td>
<td key={info.amount}>{info.amount}</td>
<td>{currentPrice ?? <Loading />}</td>
</tr>
}
with
{tableInfo.map(info => <MyRow key={info.name} info={info} />)}
Use a separate component for the tr tag and call it within it.
const Component = (props) => {
const {info} = props
useEffect(() => {
// call your api and set it to state
}, [])
return <tr>
<td key={info.name}>{info.name}</td>
<td key={info.price}>{info.price}</td>
<td key={info.amount}>{info.amount}</td>
<td>
{async () => await fetchCurrentPrice.getPrice(info.name)}
</td>
</tr>
}
{tableInfo.map(info => <Component info={info} />)}
Problem: I was able to do a web api call with axios in componentDidMount, then in Render I could loop over the data with .map and create the html with table and bootstrap classes. Issue I am having is that requirement is that I need to have buttons that show whether they are members in local storage or not.
Thus now i think that I need to move all out of Render() and put the loop and if/else checks to display the correct button inside the webapi .then section
What is the best way to do this with html tables and data in axios call?
componentDidMount With Axios web api call:
componentDidMount() {
webApi.get('sai/getofflinemembers?userId=N634806')
.then((event) => {
// Instead of looping with .map in the render(), thinking I need to
// do the looping inside here to correctly create the correct element
// with the right color button and text
for (var i = 0; i < event.data.length; i++) {
//check with local storage if the memberid exist
// need to use a different button style and text
if (localStorage.getItem(event.data[i]["Member_ID"]) != null) {
//document.getElementById(event.data[i]["Member_ID"]).classList.remove('btn-warning');
//above would be after
// need to DO ALL in render const contents html table in here
// <button id={item.Member_ID} type="button" onClick={(e) => this.downloadUser(item.Member_ID,e)}
className="btn btn-success">SAI</button>
}
}
this.setState({
data: event.data
});
});
Render:
render() {
const contents = this.state.data.map(item => (
<tr>
<td>{item.Member_Name}</td>
<td>{item.Member_ID}</td>
<td>{item.Member_DOB}</td>
<td>{item.ProgramType}</td>
<td>
<button id={item.Member_ID} type="button" onClick={(e) => this.downloadUser(item.Member_ID,e)}
className="btn btn-warning">Ready for Download</button>
</td>
</tr>
))
return(
....
{contents}
....
)
}
Lifecycle-methods like componentDidMount and render have a standard segregation of roles. Typically, the former is meant to do some API call (like what you have), and the latter, render is used to generate the markup. You should avoid trying to generate markup in componentDidMount not like it's really possibly anyway.
It sounds like all you're trying to do is check whether an item returned from the API is available in the localStorage, then conditionally change the className for that button depending on its existence. You can just create a helper function to help you accomplish that.
checkItem = (item) => {
let className;
if(localStorage.getItem(item["Member_ID"]) != null){
className = "btn btn-success"
} else {
className = "btn btn-warning"
}
return className
}
Then since you're mapping over the array of items, we can just call this function in-line with your mark-up.
render() {
const contents = this.state.data.map(item => (
<tr>
<td>{item.Member_Name}</td>
<td>{item.Member_ID}</td>
<td>{item.Member_DOB}</td>
<td>{item.ProgramType}</td>
<td>
<button id={item.Member_ID} type="button" onClick={(e) => this.downloadUser(item.Member_ID,e)}
className={this.checkItem(item)}>Ready for Download</button>
</td>
</tr>
))
return(
....
{contents}
....
)
}
Try this:
componentDidMount{
webApi.get('sai/getofflinemembers?userId=N634806')
.then(event => {
this.setState({
data: event.data
});
})
}
const isExistLocalStorage = value => {
return localStorage.getItem(value) ? true : false;
}
render() {
const { data } = this.state;
return (
data && data.map((item, index) => {
const isExist = isExistLocalStorage(item.Member_ID);
<tr key={index}>
<td>{item.Member_Name}</td>
<td>{item.Member_ID}</td>
<td>{item.Member_DOB}</td>
<td>{item.ProgramType}</td>
<td>
<button
id={item.Member_ID}
type="button"
onClick={(e) => this.downloadUser(item.Member_ID, e)}
className={`btn ${isExist ? "btn-success" : "btn-warning"}`}
> {isExist ? "Ready for Download" : "Not ready"}</button>
</td>
</tr>
})
)
}
I have two different pieces of data coming into my component, sometimes an array of objects is passed in, and sometimes just an object. My goal is to loop through each object and spit out some JSX. Here is my code:
(Array.isArray(tableData))
?
(tableData.map(obj => {
(Object.keys(obj).map(key => {
return (
<tr>
<td>{key}</td>
<td>{obj[key]}</td>
</tr>
);
}))
}))
:
(Object.keys(tableData).map(key => {
return (
<tr key={key}>
<td>{key}</td>
<td>{tableData[key]}</td>
</tr>
);
}))
You can see im checking to see if the data coming in is an array, and if not loop through just a regular object. That part works fine, but if the data is an array, nothing gets displayed. What is wrong with my code that react doesnt render anything or throw any error messages?
Because you forgot to use return in this line:
(Object.keys(obj).map, try this:
Array.isArray(tableData))
?
tableData.map(obj => {
return Object.keys(obj).map(key => {
return (
<tr>
<td>{key}</td>
<td>{obj[key]}</td>
</tr>
);
})
})
:
Object.keys(tableData).map(key => {
return (
<tr key={key}>
<td>{key}</td>
<td>{tableData[key]}</td>
</tr>
);
})
Assign the unique key to element otherwise you will get a warning.
Mayank's answer solves the problem, but it's a bit verbose. Recall that if you want to return the result of a single expression (e.g. the result of a function call or a JSX element) from an arrow function, you can omit both the curly braces and return:
Array.isArray(tableData)
? tableData.map(obj =>
Object.keys(obj).map(key => (
<tr>
<td>{key}</td>
<td>{obj[key]}</td>
</tr>
)
))
: Object.keys(tableData).map(key => (
<tr key={key}>
<td>{key}</td>
<td>{tableData[key]}</td>
</tr>
))
I've used parentheses above just for clarity.
However, you're repeating the same code here twice, so for simplicity and readability I suggest extracting it into a function of its own:
const tableRows = obj =>
Object.keys(obj).map(key => (
<tr>
<td>{key}</td>
<td>{obj[key]}</td>
</tr>
)
);
// ...
Array.isArray(tableData) ? tableData.map(tableRows) : tableRows(tableData)