Unable To Access Nested JSON Data from React Component - javascript

I have a map function that renders JSON data retrieved like this:
<div className="items">
{items.map(item =>
<Card key={item.id} price={item.title} />)}
</div>
It successfully iterates over and renders data from un-nested properties, but I am having trouble getting it to render the USD nested prop as shown here:
"price":{
"amounts":{
"GBP":"£8,185",
"USD":"$12,000",
"EUR":"€10.755"
},
I am trying to extract it like this:
<div className="items">
{items.map(item =>
<Card key={item.id} price={item.price.amounts.USD} />)}
</div>
And the error message I'm getting is "TypeError: Cannot read property 'amounts' of null." I have also tried with square brackets to no success.

As you mentioned in comments, some of the price values are null (all the item doesn't contains the price object), thats why it is throwing error:
Cannot read property 'amounts' of null.
Solution is, put the check before accessing the amount property, Like this:
price={item.price? (item.price.amounts||{}).USD||0 : 0}

Although the answer above is 100% correct (and recommended) I would consider using _.get if you are already using lodash:
import get from 'lodash/get'
// in render
// the third argument is the default price is *any* of the path is not defined
price={ get(item, 'price.amounts.USD', 0) }
For me, the pathing system in lodash (get, has, set) are extremely expressive and result in much more readable code. If you are importing just get (using the above import, instead of import { get } from lodash) the size footprint is also quite small.
Update:
An interesting comment by #SimianAngel - 100% correct regarding the default value. 0 is probably not what you want as a default value! Depending on your business logic of course.
Again leveraging the path methods of lodash, creating something readable and expressive is quite simple:
import get from 'lodash/get'
import has from 'lodash/has'
// in render
<div>
{has(item, 'price.amounts.USD'))
? <Card key={item.id} price={get(item, 'price.amounts.USD'} />
: <div>This item isn't available in this region</div>
}
</div>

Related

React app throwing error about key, but I have already assigned a unique key at the top level

Here is the code throwing the error with the console message below:
<div className="col-12">
<h6>Infractions:</h6>
{infractions.map(({ id, itype, dateStamp }) => (
<li key={id} className="col-12">
{itype} {dateStamp}
</li>
))}
</div>
and the error
The error you are seeing is because the keys you have set are not unique, or undefined.
From what you have answered in the comments, you are using some kind of UUID to generate said id values. Since you haven't included that code in your question, you should probably verify the following:
The UUIDs you are generating are indeed unique, and you are not accidentally reusing them or setting the same UUID for all records.
The id field is always populated and React doesn't render the list with the values still undefined
To check both those suggestions, and any further investigations you may need, you have a variety of tools available.
You can use console.log and output the values to the terminal (it's not the cleanest approach, but it gives quick results)
You can also show the ID in the component, and see what's going on:
<li key={id} className="col-12">
(ID: {id}) - {itype} {dateStamp}
</li>

Isolating components from integrated app code in React

I'm looking for help isolating out components from my body of code. I can't figure out how to link the input and output variables that are currently integrated throughout the app code. I've tried pulling out short parts of rendered html(/jsx) and helper functions, but so far no luck. (this is a requirement for a training program.)
My approach has been to cut and paste the desired segment into a new javascript file, replace in the app function with a component call to the new js file (via exporting from component, importing in app), replace the object variable in the component with a simple abstract name (e.g., change "task" to "object") and establish equivilancies in the component call. However, I can't get this to work -- when I remove the original variables and replace with component calls, everything else stops recognizing their variables.
So I'm looking for advice and help on how to make components of the sections detailed below. Sorry if this is vague without the specific error messages and an entire detailing of my approaches so far -- I figure there is something I'm fundamentally missing about how to isolate components from the code I currently have -- All the information I've found that should be relevant relies on props to be attached to property variables in the app component, which is not how I have my app coded. I'm hoping I don't have to gut my code and start over.
To begin with, this is the smallest contained code, the basic content of the html:
//ListItem
<li
key={listItem.index}
onClick={() => {
handleClick(listItem.index);
}}
> {listItem.string}
</li>
after that would be its container element:
//Div
<div>
<h2>w/e</h2>
<ul>
{list
.map((item, index) => ({ ...item, index }))
.filter((item) => item.position === "right")
.map((listItem) => (
<ListItem props.../> // the ListItem component defined before
))}
</ul>
</div>
so that finally, i would have:
<body><Div props... /></body>
the last thing I'm trying to seperate as a component is an object array being used to set state via useState:
//Tasks
//const [list, setList] = useState(
[
{ string: "string", position: "position" } //and many more
]);
For each piece of the code that you extract, ask yourself "what information does this code need to render?" The answer to that question becomes your props. A ListItem needs key, onClick, and its contents. You can pass the contents as a string property, or you can make use of props.children.
Not every single JSX element needs to be its own component. Think about which chunks of code change depending on the variables of your app. Those variables become props and those code chunks become components.

Passing props and mapping data in React

I'm working on a project that requires me to pass data to two functional components.
My axios call to the API seems to work, as well as setting the state with hooks, but I keep receiving these two errors:
Error Cannot convert undefined or null to object I tried checking if the array is empty but that didn't seem to solve the problem
summaryMetrics is not defined - This I don't understand because summartMetrics is defined. Could it be that the elements are being displayed before the data is fetched ?
Here are the files in a codesandbox
Included the API's so people can see the structure of how the JSON is being returned.
Im not sure if I'm passing and mapping the data correctly.
Here are the files in a codesandbox
Any help is very much appreciated
Since your state has this form :
const [summary, setSummaryData] = useState({
summaryMetrics: null,
platformsData: null
});
...you should access your state like this :
<SummaryMetrics
uniqueSocialMediaPost={summary.summaryMetrics[0]["uniqueSocialMediaPost"]}
positiveScore={summary.summaryMetrics[0]["positiveScore"]}
riskScore={summary.summaryMetrics[0]["riskScore"]}
/>
[...]
Note the "summary." before "summaryMetrics" :
summary.summaryMetrics[0]["uniqueSocialMediaPost"]
Fixed Fixed Code Sandbox (other things seems to go wrong though, white screen, but syntax error is fixed) :
Previous errors were :
mocky.io serves content over HTTP, Code Sandbox over HTTPS so mixed content issue : Chrome does not allow this kind of mix in protocols, fixed by using HTTPS protocol to grab infos from mocky.io,
no unique keys for SummaryByPlatform component in map function
Chrome dev tools is your friend when nothing happens as expected :).
By the way you could just
summary.summaryMetrics.map(s => <SummaryByPlatform summaryMetrics={s} />)
...instead of gettings keys, and you could simply pass whole summaryMetrics object to SummaryByPlatform and have only one props. But that's another subject.
Good luck with it.
Your issue is that you're not waiting for summaryMetrics to load. It's trying to read the array before it's been fetched.
What I would do, is put the render inside a function and conditionally load it once the data is available.
So your Summary.js file would look like this:
const renderSummaryMetrics = () =>
summary &&
summary.summaryMetrics && (
<div>
<SummaryMetrics
uniqueSocialMediaPost={summary.summaryMetrics[0]["uniqueSocialMediaPost"]}
positiveScore={summary.summaryMetrics[0]["positiveScore"]}
riskScore={summary.summaryMetrics[0]["riskScore"]}
/>
{Object.keys(summary.summaryMetrics) &&
Object.keys(summary.summaryMetrics).length > 0
? Object.keys(summary.summaryMetrics).map(keyName => (
<SummaryByPlatform
platform={keyName}
mean_sentiment={summary.summaryMetrics[keyName].mean_sentiment}
post_count={summary.summaryMetrics[keyName].post_count}
positive_posts={summary.summaryMetrics[keyName].positive_posts}
negative_posts={summary.summaryMetrics[keyName].negative_posts}
/>
))
: null}
</div>);
return renderSummaryMetrics();
There are several problems:
summaryMetrics is indeed not defined. It was actually defined as summary.summaryMetrics.
summary.summaryMetrics[0][...] will cause another "undefined error", because it was defined with default value of null.
Explanation for Problem 1:
No further explanation needed.
Explanation for Problem 2:
useEffect in React functional component will only be run after the component is being rendered.
This means that when you execute summary.summaryMetrics[0][...], summary.summaryMetrics is actually null because you defined it as the default value. Therefore, the steps behind will generate another error because null[0] is not possible.
What you need to do is to check whether each object properties down the tree is actually a valid object before you call the property. Otherwise, the error will happen down the property tree.
I can't show you how to correct your code, because I would need to change a big part of you code. What you can try is to mainly check the presence of values of summary.summaryMetrics first, before calling it's children properties.

Spread operator is not working in react typescript

I am using React + typescript and encountered with a problem regarding rendering a component.
Here is the code of what I have tried
filterCellComponent = ({ column, ...restProps }) => (
<TableFilterRow.Cell
column={column}
{...restProps} />
);
In the above method, i use the {column} variable to perform some conditions and then I render the with use of {...restProps}. But I am getting a syntax error saying some props are missing. But when I debug, all the props that are required are inside the {...restProps} variable. I do not understand why this is happening.
Here is the image that shows the error:
And this is the error message that i'm getting
Any idea on why this is happening?
Thanks
It seems as if typescript cant determine the type of column. Try specifying the types in the function signature like this:
filterCellComponent = ({column, ...restProps}:InsertTypeHere) => (
To clarify, the problem is that filterCellComponent right now accepts any object that has a column-property. But TableFilterRow.Cell wants column to be a specific type.

How can I make my React child components render even if a prop is missing

TLDR; I need to be able to render child components in React even if a property of this.props is missing.
I have an React app built with Yahoo's Flxubile. The app fetches data from a Wordpress site with WP REST API. Sometimes an image might be missing or something else from the API, this causes the client to break. Here's an example:
I have a file called Articles.js which is connected to ArticlesStore which holds my articles. I then render one Article.js for every article I have and pass props like this:
{ this.props.articles.map(function(el, index) {
return <Article key={index} article={el} />
})
}
Everything is ok here, but then in my Article.js when I try to access properties that is not set I get the following:
Uncaught TypeError: Cannot read property 'sizes' of undefined
This is the line which causes the error:
<img src={this.props.article.meta_fields.image.sizes.large} />
This happens when a image is missing from an article. I understand the javascript error of course but I want to render the Article.js component event if a image url is missing from the API/Store. I have tried the following solutions, but it causes too much clutter and no control:
Conditional set if we have the prop i.e.
{this.props.article.meta_fields.image ? this.props.article.meta_fields.image.sizes.large : "imagemissing.png"}
Setting defaultProps. This does not work because passed properties from parent to child overwrites defaultProps.
Maybe I should try something else than passing props from parent to child? Have a ArticleStore where I can set default values for every article? How would you do it?
If you want to provide a nested structure as a prop (as with article) you'll want to be able to rely on the structure always being pretty much the same. In this case it won't be, sometimes the meta_fields does not have an image-attribute (as your TypeError suggests).
In your case I would consider pulling out the things you actually need/use in the Article component from the article object and pass those as props.
Say that your Article only uses title, body, and image. Then just pass those as props.
<Article title={ article.title } body={ article.body } image={ getImage(article) }/>
function getImage(article) {
if (article.meta_fields.image
&& article.meta_fields.image.sizes
&& article.meta_fields.image.sizes.large
) {
return article.meta_fields.image.sizes.large;
}
return 'default_image.jpg'; // Or whatever you want.
}
One might think that the extra props constitutes more "clutter" here, but given the choice between clutter and TypeError, I choose clutter.
And if you don't want to reinvent the wheel. The problem of accessing data in a nested structure like this have been solved before.
// Lodash
_.get(article, 'meta_fields.image.sizes.large', 'default_image.jpg')
// Ramda
_.pathOr('default_image.jpg', ['meta_fields', 'image', 'sizes', 'large'], article)

Categories

Resources