I'm trying to render a Table, but I'm getting a render error, I looked up other stack's questions and there was suggested that I should use map for returning object array values. I also used render inside map. My object looks like this:
[
{
amount_left: "100",
category: "vegtable",
food_name: "potatos",
price: "1",
type: "salty"
},
{
amount_left: "100",
category: "cheese",
food_name: "cheese",
price: "0.5",
type: "salty"
},
...
]
My code.
import React, { Component } from 'react';
import { Table } from 'reactstrap';
class Meals extends Component {
getMeals = async () =>{
const api_call = await fetch(`http://127.0.0.1/RFIDSys/rfid_handler.class.php?action=getAllMeals`);
const data = await api_call.json();
console.log(data[0].food_name) // returns potatos
return data.map((item,i) => {
return (<tr><td>{item.food_name}</td></tr>)
})
}
render(){
return (
<div>
<Table>
<tbody>
{this.getMeals()}
</tbody>
</Table>
</div>
);
}
}
export default Meals;
Cant see what's wrong, I'm getting "Objects are not valid as a React child (found: [object Promise]).If you meant to render a collection of children, use an array instead." error.
The error that suggest that use array instead, ain't I using arrays in map function or it's still an object what I'm returning?
Your render function is synchronous function. However, getMeals function is asynchronous function.
Async-await keyword wraps you function into promise, so getMeals function return a promise to your render function, consequently you can't use getMeals in render function.
You can solve your task by using state:
import React, { Component } from "react";
import { Table } from "reactstrap";
class Meals extends Component {
state = { meals: null };
componentDidMount() {
this.loadMeals();
}
loadMeals = async () => {
const api_call = await fetch(
`http://127.0.0.1/RFIDSys/rfid_handler.class.php?action=getAllMeals`
);
const data = await api_call.json();
console.log(data[0].food_name);
this.setState({ meals: data });
};
render() {
if (!this.state.meals) {
return null;
}
return (
<div>
<Table>
<tbody>
{this.state.meals.map((item, i) => (
<tr>
<td>{item.food_name}</td>
</tr>
))}
</tbody>
</Table>
</div>
);
}
}
export default Meals;
Related
I am trying to map multiple arrays at the same time and im not sure if this is how you do it. I am getting the error
TypeError: Cannot read property 'map' of undefined
When trying the following code
import React, { Component } from 'react';
import Axios from 'axios';
import NavBar from '../header-footer/nav-bar'
import Featured from './FeaturedMealplan'
import RecipeItem from './RecipeItem'
export default class MealPlanDetail extends Component {
constructor(props) {
super(props);
this.state = {
currentId: this.props.match.params.slug,
mealplanItem: {}, // Full mealplan
mealplanRecipes: [], // Contains recipe names and difficulty.
}
}
getMealplanItem() {
Axios.get(`http://localhost:5000/get-mealplan/${this.state.currentId}`
).then(response => {
console.log("response", response)
this.setState({
mealplanItem: response.data.mealplan,
mealplanRecipes: this.state.mealplanRecipes.concat(response.data.mealplan["recipes"]),
mealplanIngredients: this.state.mealplanIngredients.concat(response.data.mealplan["recipe_info"]),
recipeItem: response.data.mealplan.recipes
})
}).catch(error => {
console.log("mealplan-detail GET Error ", error)
})
}
componentDidMount() {
this.getMealplanItem();
}
render() {
const renderRecipe = this.state.recipes.map((recipe, idx) => {
return (
<div key={idx}>
<h1>{recipe.recipe_name}</h1>
<h2>Recipe Difficulty: <span>{recipe.recipe_dificulty}</span></h2>
<div>
<RecipeItem recipeItem={this.state.recipeItem} />
</div>
</div>
)
})
return (
<div>
<NavBar/>
<Featured/>
{renderRecipe}
</div>
)
}
}
Data that is given: https://pastebin.com/uYUuRY6U
I just need to be able to format it correctly which this is how I would like it formatted in the renderRecipe return. I am new to mapping and do not know if there is a way to fix or a better way.
Some issues in the code that we can improve on:
this.state.recipes seems to be undefined in your logic. Is it a typo?
I would suggest implementing renderRecipe as a function instead of a variable.
You would only hope to render renderRecipe when there is data, but when your component is being mounted, this.state.recipes is undefined. It would only have value when getMealplanItem gets a response and being defined in the callback. So you should check whether the value is defined before rendering.
Please refer to my comments in the code below.
import React, { Component } from "react";
import Axios from "axios";
import NavBar from "../header-footer/nav-bar";
import Featured from "./FeaturedMealplan";
import RecipeItem from "./RecipeItem";
export default class MealPlanDetail extends Component {
constructor(props) {
super(props);
this.state = {
// ... define `recipes` if that's what you want
};
}
getMealplanItem() {
Axios.get(`http://localhost:5000/get-mealplan/${this.state.currentId}`)
.then((response) => {
console.log("response", response);
// ... set state `recipes` here if that's what you want
this.setState({
mealplanItem: response.data.mealplan,
mealplanRecipes: this.state.mealplanRecipes.concat(
response.data.mealplan["recipes"]
),
mealplanIngredients: this.state.mealplanIngredients.concat(
response.data.mealplan["recipe_info"]
),
recipeItem: response.data.mealplan.recipes
});
})
.catch((error) => {
console.log("mealplan-detail GET Error ", error);
});
}
componentDidMount() {
this.getMealplanItem();
}
render() {
const renderRecipe = () => {
// change renderRecipe from a variable to a function
if (!this.state?.recipes) {
// check whether `recipes` is a defined value
return null;
}
return this.state.recipes.map((recipe, idx) => {
return (
<div key={idx}>
<h1>{recipe.recipe_name}</h1>
<h2>
Recipe Difficulty: <span>{recipe.recipe_dificulty}</span>
</h2>
<div>
<RecipeItem recipeItem={this.state.recipeItem} />
</div>
</div>
);
});
};
return (
<div>
<NavBar />
<Featured />
{renderRecipe()} // it's a function call now
</div>
);
}
}
There is never a this.state.recipes defined. Based on data type and comment
this.state = {
currentId: this.props.match.params.slug,
mealplanItem: {}, // Full mealplan
mealplanRecipes: [], // Contains recipe names and difficulty.
}
I will assume you meant for it to really be this.state.mealplanRecipes.
Your render then becomes
const renderRecipe = this.state.mealplanRecipes.map((recipe, idx) => {...
This can easily handle the initial render with an empty array.
I'm quite new with react, I'm trying to hit an API and I'm getting this response. I need to go over the array and show the elements in a table:
{
"people": [
{
"id": "1",
"name": "philip",
"age": 25,
"timestamp": "2020-10-17T21:59:50.151"
},
{
"id": "2",
"name": "philip2",
"age": 26,
"timestamp": "2020-10-17T21:59:50.152"
},
{
"id": "3",
"name": "philip3",
"age": 27,
"timestamp": "2020-10-17T21:59:50.153"
},
]
}
I'm hitting and getting response from the api correctly but I have some issues trying to parse it.
import React, { Component } from 'react';
import "./App.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
people: []
}
}
componentDidMount() {
fetch('/local/api/people')
.then(res => res.json())
.then(json => json.people)
.then(people => this.setState({'people': people}))
}
render() {
return (
<div className="App">
{this.state.people}
Here I'd need to go over the array and show all the elements
</div>
);
}
}
export default App;
Error: Objects are not valid as a React child (found: object with keys ....... If you meant to render a collection of children, use an array instead.
I tried a lot of things but nothing worked so far
you have to map over the array inside the return like the code below and I am passing a key that's a react way to identify the element
Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity:
and also am checking when the component renders that I will only show the list when the state people array length is true means not 0
import React, { Component } from "react";
import "./App.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
people: [],
};
}
componentDidMount() {
fetch("/local/api/people")
.then((res) => res.json())
.then((json) => json.people)
.then((people) => this.setState({ people: people }));
}
render() {
return (
<div className="App">
{this.state.people.length && this.state.people.map((element, key) => {
return (
<div key={key}>
<span>{element.id}</span>
<span>{element.name}</span>
<span>{element.age}</span>
<span>{element.timestamp}</span>
</div>
);
})}
</div>
);
}
}
export default App;
You can create a member function that maps over the array and creates and returns jsx for each table row. You can then call this function inside a table body tag.
renderTableData() {
return this.state.people.map((person, index) => {
const { id, name, age } = person //destructuring
return (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
</tr>
)
})
}
render() {
return (
<div className="App">
<table id='people'>
<tbody>
{this.renderTableData()}
</tbody>
</table>
</div>
)
}
From reading previous SO posts and blogs I'm not sure if this is related to props. Either way I'm baffled.
I have a class component, responsible for loading data, which uses a functional component for displaying the data. When the delete button, in the functional component, is pressed it calls props.onDelete which does a fetch and reloads the data. The correct row is deleted from the DB but in the browser it's always the bottom row which is removed. On reloading the page the correct data is displayed.
I've put a breakpoint in the functional component and in the class component render and loadStations methods. On clicking delete button I can see that loadStations is called (which calls setState) and then the functional component is called. However, the render method is never called.
Stations.js (the class component parent)
import React, {Component} from "react";
import EditableTable from "../util/EditableTable";
// column definitions
const columns = [
...
]
export default class Stations extends Component {
constructor() {
super();
this.state = {
stations: []
};
}
componentDidMount () {
this.loadStations();
}
loadStations() {
fetch(`/api/stations`)
.then(response => response.json())
.then(response => {
this.setState({
stations: response.data
})
});
}
saveStation = (e, station) => {
...
}
deleteStation = (e, dataRowIdx) => {
e.preventDefault();
var stationId = this.state.stations[dataRowIdx].stationId;
fetch(`/api/station/${stationId}`, {
method: "DELETE"
})
.then(response => response.json())
.then(data => {
if (data.error) {
this.setState({ error: data.error });
} else {
this.loadStations();
}
}).catch(error => {
this.setState({
error: error.message
});
});
}
render() {
return (
<div>
<h4>Stations</h4>
<EditableTable
columns={columns}
data={this.state.stations}
onDelete={this.deleteStation}
onChanged={this.saveStation}
></EditableTable>
</div>
);
}
}
EditableTable.js (the functional component)
import React, { useState } from "react";
import EditableLabel from "./EditableLabel";
export default function Table(props) {
var emptyDataRow = {};
props.columns.forEach( (column) => {
emptyDataRow[column.property] = ""
});
const [newRowState, setNewRowState] = useState(emptyDataRow);
function cellChanged(e, value, dataRowIdx, columnProperty) {
var dataRow = props.data[dataRowIdx];
dataRow[columnProperty] = value;
props.onChanged(e, dataRow);
}
return <table>
<thead>
<tr>
{props.columns.map( (column, idx) =>
<th key={idx} value>{column.label}</th>
)}
<th></th>
</tr>
</thead>
<tbody>
{props.data.map( (dataRow, dataRowIndex) =>
<tr key={dataRowIndex}>
{props.columns.map( (column, columnIndex) =>
<td key={columnIndex}>
<EditableLabel
value={dataRow[column.property]}
column={column}
onChanged={(e, newValue) => { cellChanged(e, newValue, dataRowIndex, column.property); }}
></EditableLabel>
</td>
)}
<td><button onClick={(e) => { props.onDelete(e, dataRowIndex); }}>delete</button></td>
</tr>
)}
</tbody>
</table>
}
This is most likely because you are using the index given by the map method as the key.
This just uses the item's location in the array as the key, which will not be a sufficient identifier if something in the middle of the array has been removed.
You should give a key that is identifiably unique for that item in the array, eg an unchanging ID or name.
I'm new to react and I'm stuck again. I'm trying to map my array to create new array of objects inside of my child component. Here's my issue - my method componentDidMount gets executed before data came from parents props, and my state stays empty. When I'm console.loging this.props and the end of componentDidMount I receive empty array, but when I'm console.loging it on render method it gives me 4 empty arrays, then it fills in to expected 300. What I'm doing wrong?
Parent component:
import "./App.css";
import { CompanyList } from "./components/companylist/companylist.component";
import { Searchfield } from "./components/searchfield/searchfield.component";
class App extends Component {
constructor(props) {
super(props);
this.state = {
companies: [],
searchfield: "",
};
}
componentDidMount = () => {
const URL = "https://xxxxx/companies";
fetch(URL)
.then((response) => response.json())
.then((data) => this.setState({ companies: data }))
.catch((error) => {
console.error("Error", error);
});
};
render() {
const filteredCompanies = this.state.companies.filter((item) =>
item.name.toLowerCase().includes(this.state.searchfield.toLowerCase())
);
return (
<div>
<Searchfield
handleChange={(e) => this.setState({ searchfield: e.target.value })}
/>
<CompanyList companies={filteredCompanies} />
</div>
);
}
}
export default App;
Children component:
import React, { Component } from "react";
import { Company } from "../company/company.component";
export class CompanyList extends Component {
constructor(props) {
super(props);
this.state = {
newArray: [],
};
}
componentDidMount = () => {
const filledArray = this.props.companies.map((item) => {
let result;
fetch(`https://xxxxx/incomes/${item.id}`)
.then((response) => response.json())
.then((data) => {
let transactionsToFloat = data.incomes.map((item) =>
parseFloat(item.value)
);
result = transactionsToFloat.reduce((acc, num) => {
return acc + num;
}, 0);
result = Math.round(result * 100) / 100;
});
return {
id: item.id,
name: item.name,
city: item.city,
totalIncome: result,
};
});
this.setState({ newArray: filledArray });
console.log(this.props);
};
render() {
console.log(this.props);
return (
<div>
<table>
<thead>
<tr>
<th> Id </th>
<th> Name </th>
<th> City </th>
<th> Total income </th>
</tr>
</thead>
{this.props.companies.map((item) => (
<Company key={item.id} company={item} />
))}
</table>
</div>
);
}
}
componentWillMount() happens before render(). componentDidMount() happens after.
This is happening because of how React works fundamentally. React is supposed to feel fast, fluent and snappy. the application should never get logged up with http requests or asynchronous code. The answer is to use the lifecycle methods to control the DOM.
What does it mean when a component mounts?
It might be helpful to understand some of the React vocabularies a little better. When a component is mounted it is being inserted into the DOM. This is when a constructor is called. componentWillMount is pretty much synonymous with a constructor and is invoked around the same time. componentDidMount will only be called once after the first render.
componentWillMount --> render --> componentDidMount
I have one component as below. I am calling on api on its componentDidMount() event. I am not getting why am I not getting its prop value first time when component renders. Also I am not sure why component is rendering 2 times. I have below code.
import React, { Component } from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import AgmtTable from "./AgmtTable";
import * as AgmtAction from "../redux/actions/AgmtAction";
class AgmtContainer extends Component {
constructor(props) {
super(props);
this.state = {};
}
fetch Agmt details.
componentDidMount() {
this.props.dispatch(
AgmtAction.getAgmtsForCustomer(
this.props.match.params.custID,
this.props.match.params.source,
this.props.token
)
);
console.log("componentDidMount", this.props.Agmts);
}
getHeaader = () => {
var tableHeadings = [
"Agmt ID",
"Start Date",
"End Date",
];
return tableHeadings.map((key) => {
return <th key={key}> {key.toUpperCase()}</th>;
});
};
getRowsData = () => {
console.log("in row data", this.props.Agmts);//here I cant see a data though its present in mapStateToProps() function. I am getting error as this.props.agreements.map is not a function.
if (this.props.Agmts) {
return this.props.Agmts.map((value) => {
const {
Agmt_ID,
Agmt_START_DATE,
End_DATE,
} = value;
return (
<tr key={Agmt_ID} className="clickable-row active">
<td> {Agmt_ID} </td>
<td> {Agmt_START_DATE} </td>
<td> {End_DATE} </td>
</tr>
);
});
}
};
render() {
return (
<React.Fragment>
<div>
<table
id="display-table"
className="table table-bordered table-hover table-responsive table-condensed table-striped table-sm"
>
<tbody>
<tr>{this.getHeaader()}</tr>
{this.getRowsData()}
</tbody>
</table>
</div>
</React.Fragment>
);
}
}
function mapStateToProps(state) {
return {
Agmts: state.AgmtsDetails.AgmtsData,//here I have a data
token: state.login.userDetails.token,
};
}
export default connect(mapStateToProps)(AgmtContainer);
Also how can I use the mapStateToProps values to set in state object. When I am running above code I am getting error as this.props.agmts.map is not a function
The dispatch is asynchronous, so you either need to watch for result to be updated in your Redux store with componentDidUpdate or directly return the result from the reducer.
When you get the result, you can manipulate it and store it in local state to reference in your render. Note that unless you need to reference the result in another component somewhere, then you don't need to store it in Redux, you can handle it all in within the component.
Subscribing to the store with componentDidUpdate:
componentDidMount() {
this.props.dispatch(
AgmtAction.getAgmtsForCustomer(
this.props.match.params.custID,
this.props.match.params.source,
this.props.token
)
);
}
componentDidUpdate(prevProps) {
if (JSON.stringify(prevProps.Agmts) !== JSON.stringify(this.props.Agmts)) {
// this is the result of the dispatch
console.log(this.props.Agmts);
}
}
Returning the result directly back:
// in your AgmtAction.getAgmtsForCustomer action
export const getAgmtsForCustomer = () => (dispatch, getState) => {
return axios
.get(..........
.then((res) => {
dispatch(..........
return res.data;
})
.catch((err) => {
...
});
};
// in your `AgmtContainer` component
...
componentDidMount() {
this.props.dispatch(
AgmtAction.getAgmtsForCustomer(
this.props.match.params.custID,
this.props.match.params.source,
this.props.token
)
).then((res) => {
// this is the result of the dispatch
console.log(res);
});
}
In getRowsData function where you are getting error "map is not a function" is due to the data you are getting in this.props.Agmts must be an object type. (Object encloses in curly brackets {}).
You can apply map function only on array not on an object. (Array encloses in square brackets [])