I'm trying to render some JSON and the error I get references some fields that don't exist in my JSON structure. The fields are getting logged to the console properly.
Which object is this referring to, and how do I fix it?
Error: Objects are not valid as a React child (found: object with keys {_events, _eventsCount, _maxListeners, uri, callback, readable, writable, _qs, _auth, _oauth, _multipart, _redirect, _tunnel, headers, setHeader, hasHeader, getHeader, removeHeader, method, localAddress, pool, dests, __isRequestRequest, _callback, proxy, tunnel, setHost, originalCookieHeader, _disableCookies, _jar, port, host, path, httpModule, agentClass, agent}). If you meant to render a collection of children, use an array instead.
Postlist:
class PostList extends React.Component {
render() {
return (
request('http://194.5.192.153:3044/api/posts/', function (error,response,body) {
let items = JSON.parse(body).items;
for(let i=0; i<items.length; i++) {
console.log(items[i].author,items[i].desc,items[i].updatedAt,items[i].title);
return (
<Post author={items[i].author} desc={items[i].desc} image={items[i].image} title={items[i].title} createdAt={items[i].createdAt} updatedAt={items[i].updatedAt} />
);
}
})
)
}
}
Post:
const Post = (props) => {
return (
<>
<img src={props.image} alt="" />
<h1>{props.title}</h1>
<h2>by {props.author}</h2>
<div>Created at {props.createdAt}</div>
<div>Updated at {props.updatedAt}</div>
<div>{props.desc}</div>
</>
);
}
export default Post;
In your code, you are effectively trying to render the return value from request, which is certainly not what you want. Since request is asynchronous, the general pattern here is to set state in the callback and then map over that state in the render method.
class PostList extends React.Component {
state = { items: [] };
componentDidMount() {
request(
"http://194.5.192.153:3044/api/posts/",
(error, response, body) => {
const items = JSON.parse(body).items;
this.setState({ items });
}
);
}
render() {
return this.state.items.map((item) => (
<Post
key={item.title}
author={item.author}
desc={item.desc}
image={item.image}
title={item.title}
createdAt={item.createdAt}
updatedAt={item.updatedAt}
/>
));
}
}
Related
I am trying to connect to the USDA Food Central database using an API.
let uri = encodeURI(`https://api.nal.usda.gov/fdc/v1/foods/search?api_key=${MY_API_KEY}&query=${search}`)
I want to use the API to map certain fields.
class AddFoodItemList extends Component {
static contextType = AddFoodContext;
render() {
const listItems = this.context.FoodSearch.map((foods) =>
<FoodItem
key={foods.brandOwner}
brandOwner={foods.brandOwner}
fdcId={foods.fdcId}
/>
);
return (
<div id="AddFoodItemList">
{listItems}
</div>
);
}
}
export default AddFoodItemList;
The returned JSON is this screenshot attached:
Returned JSON
I am getting an error, TypeError: Cannot read property 'map' of undefined.
Why do you think this is the case? Any sort of help or suggestions are appreciated!
You are attempting to access a property FoodSearch on the value of your AddFoodContext provider. The error tells you that this property is undefined. If the object in your screenshot is the value of your context then you want to access the property foods instead. This is an array whose elements are objects with properties brandOwner and fdcId.
On your first render this data might now be loaded yet, so you should default to an empty array if foods is undefined.
It's honestly been a long time since I've used contexts in class components the way that you are doing it. The style of code is very dated. How about using the useContext hook to access the value?
const AddFoodItemList = () => {
const contextValue = useContext(AddFoodContext);
console.log(contextValue);
const listItems = (contextValue.foods || []).map((foods) => (
<FoodItem
key={foods.fdcId} // brandOwner isn't unique
brandOwner={foods.brandOwner}
fdcId={foods.fdcId}
/>
));
return <div id="AddFoodItemList">{listItems}</div>;
};
Here's a complete code to play with - Code Sandbox Link
const MY_API_KEY = "DEMO_KEY"; // can replace with your actual key
const getUri = (search) => `https://api.nal.usda.gov/fdc/v1/foods/search?api_key=${MY_API_KEY}&query=${encodeURIComponent(search)}`;
const AddFoodContext = createContext({});
const FoodItem = ({ brandOwner, fdcId }) => {
return (
<div>
<span>{fdcId}</span> - <span>{brandOwner}</span>
</div>
);
};
const AddFoodItemList = () => {
const contextValue = useContext(AddFoodContext);
console.log(contextValue);
const listItems = (contextValue.foods || []).map((foods) => (
<FoodItem
key={foods.fdcId} // brandOwner isn't unique
brandOwner={foods.brandOwner}
fdcId={foods.fdcId}
/>
));
return <div id="AddFoodItemList">{listItems}</div>;
};
export default () => {
const [data, setData] = useState({});
useEffect(() => {
fetch(getUri("cheese"))
.then((res) => res.json())
.then(setData)
.catch(console.error);
}, []);
return (
<AddFoodContext.Provider value={data}>
<AddFoodItemList />
</AddFoodContext.Provider>
);
};
What is the best way of accessing ag-Grid API inside of React function component?
I have to use some of the methods from API (getSelectedNodes, setColumnDefs etc.) so I save a reference to the API (using useState hook) in onGridReady event handler:
onGridReady={params => {
setGridApi(params.api);
}}
and then I can call the API like this: gridApi.getSelectedNodes()
I haven't noticed any problems with this approach, but I'm wondering if there's more idiomatic way?
Stack:
ag-grid-community & ag-grid-react 22.1.1
react 16.12.0
We find the most idiomatic way to use a ref. As the api is not a state of our component. It is actually possible to simply do:
<AgGridReact ref={grid}/>
and then use it with
grid.current.api
Here an example:
import React, { useRef } from 'react'
import { AgGridReact } from 'ag-grid-react'
import { AgGridReact as AgGridReactType } from 'ag-grid-react/lib/agGridReact'
const ShopList = () => {
const grid = useRef<AgGridReactType>(null)
...
return (
<AgGridReact ref={grid} columnDefs={columnDefs} rowData={shops} />
)
}
The good thing here is, that you will have access to the gridApi but als to to the columnApi. Simply like this:
// rendering menu to show/hide columns:
{columnDefs.map(columnDef =>
<>
<input
type='checkbox'
checked={
grid.current
? grid.current.columnApi.getColumn(columnDef.field).isVisible()
: !(columnDef as { hide: boolean }).hide
}
onChange={() => {
if (grid.current?.api) {
const col = grid.current.columnApi.getColumn(columnDef.field)
grid.current.columnApi.setColumnVisible(columnDef.field, !col.isVisible())
grid.current.api.sizeColumnsToFit()
setForceUpdate(x => ++x)
}
}}
/>
<span>{columnDef.headerName}</span>
</>
)}
Well I am doing it in my project. You can use useRef hook to store gridApi.
const gridApi = useRef();
const onGridReady = params => {
gridApi.current = params.api; // <== this is how you save it
const datasource = getServerDataSource(
gridApi.current,
{
size: AppConstants.PAGE_SIZE,
url: baseUrl,
defaultFilter: props.defaultFilter
}
);
gridApi.current.setServerSideDatasource(datasource); // <== this is how you use it
};
I'm running into the same issue but here is a workaround that at least can get you the selected rows. Essentially what I'm doing is sending the api from the agGrid callbacks to another function. Specifically I use OnSelectionChanged callback to grab the current row node. Example below:
const onSelectionChanged = params => {
setDetails(params.api.getSelectedRows());
};
return (<AgGridReact
columnDefs={agData.columnDefs}
rowSelection={'single'}
enableCellTextSelection={true}
defaultColDef={{
resizable: true,
}}
rowHeight={50}
rowData={agData.rowData}
onCellFocused={function(params) {
if (params.rowIndex != null) {
let nNode = params.api.getDisplayedRowAtIndex(params.rowIndex);
nNode.setSelected(true, true);
}
}}
onSelectionChanged={function(params) {
onSelectionChanged(params);
params.api.sizeColumnsToFit();
}}
onGridReady={function(params) {
let gridApi = params.api;
gridApi.sizeColumnsToFit();
}}
deltaRowDataMode={true}
getRowNodeId={function(data) {
return data.id;
}}
/>);
I'm trying to save an array of JSON objects returned from an API call to state in React (so that I can use the data to render a table). I'm getting the error Error: Objects are not valid as a React child (found: object with keys {street, suite, city, zipcode, geo}). If you meant to render a collection of children, use an array instead.
I can't figure out how to fix this. It looks like the JSON is being stored inside an array as it should be. However, there are also nested objects inside the objects that may be causing an issue, for example:
address": {
"street": "Victor Plains",
"suite": "Suite 879",
"city": "Wisokyburgh",
"zipcode": "90566-7771",
Any assistance would be much appreciated. Here's my code below:
let tableData = []
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => response.json())
.then(data => {
tableData = data
props.addItem(tableData)
})
Here's the addItem function:
addItem(item) {
this.setState(function(prevState) {
return {
tables: [...prevState.tables, item]
}
})
}
UPDATE
Here's how I am rendering the data:
App.js:
render() {
return (
<div>
{this.state.tables.map(item => {
return (<TableComponent key={item} data={item} />)
})}
</div>
)
}
TableComponent.js:
class TableComponent extends React.Component {
constructor(props){
super(props);
this.getHeader = this.getHeader.bind(this);
this.getRowsData = this.getRowsData.bind(this);
this.getKeys = this.getKeys.bind(this);
}
getKeys = function(){
return Object.keys(this.props.data[0]);
}
getHeader = function(){
let keys = this.getKeys();
return keys.map((key, index)=>{
return <th key={key}>{key.toUpperCase()}</th>
})
}
getRowsData = function(){
let items = this.props.data;
let keys = this.getKeys();
return items.map((row, index)=>{
return <tr key={index}><RenderRow key={index} data={row} keys={keys}/></tr>
})
}
render() {
return (
<div>
<table>
<thead>
<tr>{this.getHeader()}</tr>
</thead>
<tbody>
{this.getRowsData()}
</tbody>
</table>
</div>
);
}
}
const RenderRow = (props) =>{
return props.keys.map((key, index)=>{
return <td key={props.data[key]}>{props.data[key]}</td>
})
}
The error message threw me off here because it made it seem like the issue was in saving the objects to state. However, as was pointed out in the comments, the error happened during rendering. To solve the issue I changed RenderRow to the following:
const RenderRow = (props) =>{
return props.keys.map((key, index)=>{
return <td key={props.data[key]}>{typeof props.data[key] === "object" ? JSON.stringify(props.data[key]) : props.data[key]}</td>
})
}
Specifically, the piece that I changed is to first check whether a specific element is an object, and if it is, to use JSON.stringify() to convert it to a string before rendering it to the screen.
I have setup a project with react-redux and I use redux-thunk in my action-creators to do fetching. Here is an example of my thunk:
export const doPostRequest = id => {
return (dispatch, getState) => {
const { id : initiailId } = getState().currentSelection
return api.post(id).then(response => {
if (!isEqual(initialId, getState().currentSelection.id)){
return;
}
dispatch(someOtherAction(id))
return Promise.resolve(true)
})
.catch(err => {})
}
}
As you can see i want to escape the doPostRequest if the currentSelection of my state is changed by the time the response is receieved. Otherwise I return Promise.resolve(true) so the onSubmit in MyComponent can reset the form:
Inside a component (which is a form) i have the following for onSubmit:
class MyComponent extends React.PureComponent{
onSubmit = id => {
this.props.dispatch(doPostRequest(id))
.then(shouldReset => shouldReset && resetForm())
}
render(){
return <form onSubmit={this.onSubmit}>.....</form>
}
}
In most cases, when I really dont have to do anything else except fetching values, I dont do a Promise-chain on the thunk, even though it returns a promise, but here I need to do a resetForm once the postrequest is a success.
Is this implementation good enough, also when it comes to GC ? How are Promises garbage collected ? Is there a problem if I return a fetch().then() without chaining it further?
I currently have a service that gets an array of json objects from a json file which displays a list of leads. Each lead has an id and when a lead within this list is clicked it takes the user to a view that has this id in the url ie ( /lead/156af71250a941ccbdd65f73e5af2e67 )
I've been trying to get this object by id through my leads service but just cant get it working. Where am I going wrong?
Also, i'm using two way binding in my html.
SERVICE
leads;
constructor(private http: HttpClient) { }
getAllLeads() {
return this.http.get('../../assets/leads.json').map((response) => response);
}
getLead(id: any) {
const leads = this.getAllLeads();
const lead = this.leads.find(order => order.id === id);
return lead;
}
COMPONENT
lead = {};
constructor(
private leadService: LeadService,
private route: ActivatedRoute) {
const id = this.route.snapshot.paramMap.get('id');
if (id) { this.leadService.getLead(id).take(1).subscribe(lead => this.lead = lead); }
}
JSON
[
{
"LeadId": "156af71250a941ccbdd65f73e5af2e66",
"LeadTime": "2016-03-04T10:53:05+00:00",
"SourceUserName": "Fred Dibnah",
"LeadNumber": "1603041053",
},
{
"LeadId": "156af71250a999ccbdd65f73e5af2e67",
"LeadTime": "2016-03-04T10:53:05+00:00",
"SourceUserName": "Harry Dibnah",
"LeadNumber": "1603021053",
},
{
"LeadId": "156af71250a999ccbdd65f73e5af2e68",
"LeadTime": "2016-03-04T10:53:05+00:00",
"SourceUserName": "John Doe",
"LeadNumber": "1603021053",
}
]
You didn't used the newly created leads array (const leads is not this.leads), so do this:
getLead(id: any) {
return this.getAllLeads().find(order => order.LeadId === id);
}
And change your map to flatMap, because from the server you get an array, but you have to transform it to a stream of its items:
getAllLeads() {
return this.http.get('../../assets/leads.json').flatMap(data => data);
}
Don't forget to import it if you have to: import 'rxjs/add/operator/flatMap';
You can have getLead in your component level itself since you are not making any api to get the information. In your component,
this.lead = this.leads.find(order => order.id === id);
or to make the above service work, just do leads instead of this.leads
const lead = leads.find(order => order.id === id);