Flattening nested dictionary React? - Cannot read property 'standard' of undefined - javascript

I am working on a personal WebApp project in React. I am new to this technology but very eager to learn it. I came across a problem. I am using axios to fetch data from Google Youtube API response and it works but I am unable to parse obtained data that is nested. What i mean by that:
Inside Items there are multiple snippets
{
"items": [
{
"snippet": {
"title": "Dolby Atmos - usłysz więcej!",
"description": "W dzisiejszym odcinku opowiem wam o tym czym jest nagłośnienie i system dolby atmos. System i nagłośnienie Dolby atmos znajdziemy obecnie w najlepszych kinach. System wspierają takie filmy jak \"Zjawa\" czy \"Kapitan Ameryka wojna bohaterów\". Jakość dźwięk docenią kinomani i prawdziwi audiofile. Istnieje również stworzenia systemu składającego się z głośników dolby atmos kina domowego, ale jest poważna inwestycja.\nJeżeli jesteś z Łodzi i chcesz poczuć Dolby Atmos na własnej skórze kliknij tutaj:\nhttp://www.helios.pl/47,Lodz/StronaGlowna/\n\nJeżeli dzisiejszym odcinek Ci się spodobał zostaw like'a albo subskrybcję :D\nFanPage:\nhttp://facebook.com/RuchOpornikow\nGoogle+:\nhttps://plus.google.com/u/0/+RuchOpor...\nTwitter:\nhttps://twitter.com/RuchOpornikow",
"thumbnails": {
"standard": {
"url": "https://i.ytimg.com/vi/QWTk3vnztRw/sddefault.jpg",
"width": 640,
"height": 480
},
"maxres": {
"url": "https://i.ytimg.com/vi/QWTk3vnztRw/maxresdefault.jpg",
"width": 1280,
"height": 720
}
},
"resourceId": {
"videoId": "QWTk3vnztRw"
}
}
},
I want to get a random snippet from items and use it's title attribute, description and thumbnails.
At this point I can access description and title but accessing movie.description.thumbnails.standard.url or movie.resourceId.videoId gives me an error.
TypeError: Cannot read property 'standard' of undefined
31 | backgroundPosition: "center center",
32 | }}
33 | >
> 34 | <img src ={`${movie.thumbnails.standard.url}`}/>
35 | <div className="banner_contents">
36 | {/* edge cases */}
37 | <h1 className="banner_title">
Here is my full code :
function Banner() {
const [movie, setMovie] = useState([]);
useEffect(() => {
async function fetchData() {
const request = await axios.get("./data.json");
setMovie(
request.data.items[
Math.floor(Math.random() * (request.data.items.length-1))
].snippet
);
return request;
}
fetchData();
}, []);
return (
<header
className="banner"
style={{
backgroundSize: "cover",
// backgroundImage: `url("${movie.snippet.thumbnails.maxres.url}")`,
backgroundPosition: "center center",
}}
>
<img src ={`${movie.thumbnails.standard.url}`}/>
<div className="banner_contents">
{/* edge cases */}
<h1 className="banner_title">
{movie.title}
</h1>
<div className="banner_buttons">
<button className="banner_button">Play</button>
<button className="banner_button">Original</button>
</div>
<h1 className="banner_description">{movie.description}</h1>
</div>
<div className="banner--fadeBottom" />
</header>
);
}
export default Banner;
Do you know what could be an error and how to fix it? Console.log and JSON.stringify show that those attributes are there.

Issue(s)
Your initial movie state is an array, but you reference into it like it is an object in your render logic. movie.snippet and movie.thumbnails will obviously be undefined.
const [movie, setMovie] = useState([]);
and
movie.thumbnails.standard.url
Solution
Make the initial state type match what it is updated to from the fetch request and how it's accessed in the render logic, i.e. an object
const [movie, setMovie] = useState({});
Use proper null checks and provide fallback values.
Null Checks
movie.thumbnails && movie.thumbnails.standard && movie.thumbnails.standard.url || ""
or using Optional Chaining and Null Coalescing
movie?.thumbnails?.standard?.url ?? ""

you are getting the error because before getting any data your movie object is empty . you should check before accessing nested properties . like this
<img src ={movie.thumbnails ? `${movie.thumbnails.standard.url}` : ""}/>

movie?.thumbnails?.standard?.url should do it. Looks like movie.thumbnails is undefined

assuming movie contains the correct data, the syntax should work. Here's a test: https://jsfiddle.net/yf470pma/1/
I would add a breakpoint and check what is really there.

initial movie is an empty array and it will take some time to fetch from API. refactor to the below
const [movie, setMovie] = useState();
and check movie if it's not null before rendering
return movie?(
<header
className="banner"
style={{
backgroundSize: "cover",
// backgroundImage: `url("${movie.snippet.thumbnails.maxres.url}")`,
backgroundPosition: "center center",
}}
>
<img src ={`${movie.thumbnails.standard.url}`}/>
<div className="banner_contents">
{/* edge cases */}
<h1 className="banner_title">
{movie.title}
</h1>
<div className="banner_buttons">
<button className="banner_button">Play</button>
<button className="banner_button">Original</button>
</div>
<h1 className="banner_description">{movie.description}</h1>
</div>
<div className="banner--fadeBottom" />
</header>
):null;

Related

How can I split an object property which is a string while using map method?

I have an array with this data structure -
const dummy = [
{
id: 1,
label: "Print text",
sequence: "ctrl+p",
},
{
id: 2,
label: "Copy text",
sequence: "ctrl+c",
},
{
id: 3,
label: "Paste text",
sequence: "ctrl+v",
},
];
I want to split the sequence property so that I can get "ctrl", "v" or "ctrl", "c" as separate strings.
The reason for getting this as a separate string because of the UI I have to achieve that is like -
If we see in the right hand the command key is being displayed with a gap.
If I loop through the existing array using
const getSequence = dummy.map((value) => {
return value.sequence.split("+");
});
console.log("Sequence is" + getSequence[1]);
I do get access to the entire string as a whole also it's static.
<div className="keys_container" style={{ ...textStyle }}>
{searchResult.map((value) => {
return (
// eslint-disable-next-line react/jsx-key
<div className="hotkeys" style={{ ...textStyle, paddingBottom: "8px" }}>
<Icon icon={value.icon} />
<div>{value.label}</div>
<div>{value.sequence}</div>
</div>
);
})}
</div>;
I am looping the array here so that the listing shows the dynamic command string but then again it's as a whole.
i.e "ctrl+v" not "ctrl" + "v" + "anyotherkey"
Would appreciate it if anyone can just guide me to the logic and I can code it myself. Many thanks.
It looks like you've got all of the pieces you need to make this work, so kudos on getting that far. If I understand the problem correctly, what's missing is something that looks like the invocation of your function inside of your JSX.
Try looking at the div that has {value.sequence} and consider how that variable is going to be expressed differently from what you're passing in to the console.log after the getSequence declaration -
The best way I can think of to explain it is to think about what you're getting back when you call the function, or maybe consider it as a placeholder for what you're getting back. So, value.sequence.split("+") returns an Array, which means you can use Array methods on it. I think what you want to do from there is .map over that Array and the .map() callback should return your div with the split section of the string.
{value.sequence.split("+").map(sequenceSection => <div>{sequenceSection}</div>}
I created a custom component that accepted the string coming from object property through map() and returning JSX element with split() function.
const KeyBind = ({ sequence }) => {
const keys = sequence.split('+');
return (
<div className="command_container">
<span className="command">{keys[0]}</span>
<span className="command">{keys[1]}</span>
</div>
);
};
Final return code
<div className="keys_container" style={{ ...textStyle }}>
{searchResult.map((value) => {
return (
// eslint-disable-next-line react/jsx-key
<div onClick={value.onClick} className="hotkeys" style={{ ...textStyle, paddingBottom: '8px' }}>
<Icon size={12} icon={value.icon} />
<div>{value.label}</div>
<KeyBind sequence={value.sequence} /> // getting string for each object dynamically
</div>
);
})}
</div>

react-render-html: TypeError: Cannot read property 'length' of undefined

I need to make a bubble rich text editor with react-quill, when I try make a content with rich editor then it's worked fine. but when I try to fetch data from my database with react-render-html then it does not work show me error like this:
TypeError: Cannot read property 'length' of undefined
push../node_modules/react-render-html/node_modules/parse5/lib/tokenizer/preprocessor.js.module.exports.push../node_modules/react-render-html/node_modules/parse5/lib/tokenizer/preprocessor.js.Preprocessor.write
C:/Users/alami/OneDrive/Desktop/MERN stack/online shopping/client/node_modules/react-render-html/node_modules/parse5/lib/tokenizer/preprocessor.js:91
88 | else
89 | this.html = chunk;
90 |
> 91 | this.lastCharPos = this.html.length - 1;
92 | this.endOfChunkHit = false;
93 | this.lastChunkWritten = isLastChunk;
94 | };
I have tried code by this way:
import React, { useEffect, useState } from "react";
import { Button, Descriptions } from "antd";
import renderHTML from "react-render-html";
function ProductInfo(props) {
const [Product, setProduct] = useState({});
useEffect(() => {
setProduct(props.detail);
}, [props.detail]);
return (
<div>
<Descriptions title="Product Info">
<Descriptions.Item label="Price">${Product.price}</Descriptions.Item>
<Descriptions.Item label="Sold">{Product.sold}</Descriptions.Item>
<Descriptions.Item label="View">{Product.views}</Descriptions.Item>
<Descriptions.Item label="Description">
<div>{renderHTML(Product.desc)}</div>
</Descriptions.Item>
</Descriptions>
<br />
<br />
<br />
<div style={{ display: "flex", justifyContent: "center" }}>
<Button shape="round" type="danger">
Contact me
</Button>
</div>
</div>
);
}
export default ProductInfo;
If I removed renderHTML from {renderHTML(Product.desc)} then It worked, but output show me html code, does not show plane text.
Any Suggestion Please.
In my experience its because Product.desc is fetched in an asynchronous manner so it will be undefined at some point. So when renderHTML tries to get the length, it tries to get the length of undefined at some point, before the fetching is finished.
Try this:
<Descriptions.Item label="Description">
{Product.desc ? <div>{renderHTML(Product.desc)}</div> : null}
</Descriptions.Item>
You don't need state, since nothing is changing. Instead just pass props.detail.
import React, { useEffect, useState } from 'react';
import { Button, Descriptions } from 'antd';
import renderHTML from 'react-render-html';
function ProductInfo(props) {
return (
<div>
<Descriptions title="Product Info">
<Descriptions.Item label="Price">${props.detail.price}</Descriptions.Item>
<Descriptions.Item label="Sold">{props.detail.sold}</Descriptions.Item>
<Descriptions.Item label="View">{props.detail.views}</Descriptions.Item>
<Descriptions.Item label="Description">
<div>{renderHTML(props.detail.desc)}</div>
</Descriptions.Item>
</Descriptions>
<br />
<br />
<br />
<div style={{ display: 'flex', justifyContent: 'center' }}>
<Button shape="round" type="danger">
Contact me
</Button>
</div>
</div>
);
}
export default ProductInfo;
This happens when you pass an undefined property to renderHtml. The root course is that we should know useEffect calls after the initial render of the HTML(knowledge on useEffect basics). So when react tries to render the HTML for the first time, the products.desc property is not declared its undefined(useEffect hasn't triggered yet). You can handle it in different ways. I normally render the components which depend on the useEffect hook conditionally only after the useEffect set the states
{checkState? <MyComponent />:'Loading'}

JSX this.state.STATE.map is not a function

Issue: this.state.previousWords.map() is nor recognized as a function
I have a hangman app that is trying to render previously seen words onto the page in their own line. The issue happens when a new word is added to the array in the state previousWords: []. When the page tries to re-render this it errors stating "TypeError: this.state.previousWords.map is not a function." Below is the code snippet where the error is happening.
Based off a prior project this is correct syntax (code has been included). I need help understanding why this is not recognized and how to fix it.
<div className="col-3">
<h3>Previous words:</h3>
<br></br>
{this.state.previousWords.length ? (
<div>
{this.state.previousWords.map( (value, index) => (
<div
key={index}
className="row">
{value}
</div>
))}
</div>
):(<></>)}
</div>
Prior app with functioning .map statement:
{this.state.events.length ? (
<Dropdown.Menu>
{this.state.events.map(events => (
<Dropdown.Item
key={events.event_id}
/* TODO: onClick() */
>
{events.title}
</Dropdown.Item>
))}
</Dropdown.Menu>
) : (
<Dropdown.Menu>
<Dropdown.Item>No Events Available</Dropdown.Item>
</Dropdown.Menu>
)}
exact error
325 |
326 | <div className="col-3">
327 | <h3>Previous words:</h3>
> 328 | <br></br>
| ^ 329 | <>{console.log(this.state.previousWords)}</>
330 |
331 | { this.state.previousWords.length ? (
Setting the state for previousWord:
if( this.loss() ) {
let previousWords = [...this.state.previousWords];
previousWords.push(this.state.apiWord);
console.log("Loss: before state updated: this.state.previousWords: ", this.state.previousWords);
console.log("Loss: before state updated: let previousWords: ", previousWords);
this.setState({
previousWords: this.state.apiWord,
pageLock: true,
losses: this.state.losses + 1,
}, () => {
console.log("Loss: after setState: this.state.previousWords: ", this.state.previousWords);
this.resetGame();
setTimeout(() => {
// this.setWord();
}, 5000);
});
Console log where the state is being set. "test" was added to the initial state for testing purposes.
after API call: this.state.previousWords: ["test"]
App.js:209 Loss: before state updated: this.state.previousWords: ["test"]
App.js:210 Loss: before state updated: let previousWords: (2) ["test", "unintermitting"]
It looks like you correctly build out the new array, but then do not use it to update state:
let previousWords = [...this.state.previousWords];
previousWords.push(this.state.apiWord);
this.setState({
previousWords: this.state.apiWord, // Should be previousWords: previousWords
pageLock: true,
losses: this.state.losses + 1,
},
this.state.previousWords is not an Array that's why you're getting the error.
Easiest way to prevent it - check if this.state.previousWords is an Array Array.isArray(this.state.previousWords) and only then use map.
Array.isArray(this.state.previousWords) && this.state.previousWords.length

How to iterate over array nested in object as props

I have a JSON file that I'm parsing for data but I am trying to map a subarray (nested in an object). However, I am getting an error sayin that the array is not iterable. I logged the array to the console where it prints the array but when I check its type it says "object".
Here is my code:
export default function Projects({ children, ...props }) {
return (
<div>
<div>
<div className={styles.text}>
<p>{props.description}</p>
<ul>
{props.features.map((feature) => (
<li>{feature}</li>
))}
</ul>
</div>
</div>
</div>
);
}
The JSON file:
[
{
"id": 1,
"name": "Netflix Clone",
"img": "/netflix-clone.jpg",
"direction": "row",
"description": "This project is a minimalistic Netflix clone utilising Firefox for storage and authorisation. It utilises Styled Components for styling, compound components, large-scale React architecture, and custom hooks.",
"features": [
"React",
"Styled Components",
"Compound components",
"Large-Scale React Architecture",
"Firebase (Firestore & Auth)",
"Functional components",
"Firebase (Firestore & Auth)",
"Custom hooks"
]
},
]
The error:
TypeError: Cannot read property 'map' of undefined
When loading data async the initial renders of your components will not have access to the data (the data will be undefined).
Your components should be coded to cope with this situation by displaying a different view, such as loading animations.
This could be achieved by simply checking if props.features is defined before rendering out your components:
export default function Projects({ children, ...props }) {
return (
<div>
<div>
<div className={styles.text}>
<p>{props.description}</p>
<ul>
{
/** Conditionally show only when props.features has a truthy value **/
!!props.features && props.features.map((feature) => (
<li>{feature}</li>
))
}
</ul>
</div>
</div>
</div>
);
}
To show another component / text while the data is loading you could use a ternary statement:
export default function Projects({ children, ...props }) {
return (
<div>
<div>
<div className={styles.text}>
<p>{props.description}</p>
<ul>
{
/** Ternary statement to show components when props.features is a truthy value
or loading when a falsy value **/
props.features ?
props.features.map((feature) => (
<li>{feature}</li>
)) :
"Loading..."
}
</ul>
</div>
</div>
</div>
);
}
On initial render, there is no data yet in features. Use condition like this ->
props && props.features && props.features.map((feature) => (
<li>{feature}</li>
))}

Why infinite-scroll in react don't see data from function map()?

I have a problem with this code:
<InfiniteScroll
dataLength={beers.length}
next={fetchMoreBeers}
hasMore={true}
loader={<p>Loading ...</p>}
endMessage={<p id="beers-end">No more beers :(</p>}
>
{beers.map((beer, index) => (
<div key={index}>
<Link
to={{
pathname: `/beer/${beer.id}`,
state: { beer: beer.id },
}}
>
<div className="beer-wrapper">
<div className="beer">
<img
className="beer-img"
src={beer.image_url}
alt={beer.name}
/>
<p className="beer-name">
{beer.name.length < 15
? `${beer.name}`
: `${beer.name.substring(0, 20)}...`}
</p>
<p className="beer-tagline">
{beer.tagline.length < 20
? `${beer.tagline}`
: `${beer.tagline.substring(0, 25)}...`}
</p>
</div>
</div>
</Link>
<Route path="/beer/:id" component={Beer} />
</div>
))}
</InfiniteScroll>;
And when the page is scrolled the error occurs:
"TypeError: Cannot read property 'id' of undefined"
for this line:
pathname: `/beer/${beer.id}`,
This seems like InfiniteScroll doesn't see the data from map() function ....
Maybe some of you know how to fix this problem?
Thanks for any tips!
Perhaps the issue is with template literals. When we add template literals directly it throws such issues. I experienced the same in my projects.
May be you can try in below way
render(){
const { beers } = this.props;
const beerItems = beers.map((beer, index) => {
let pathName = `/beer/${beer.id}`;
return (
<div key={index}>
<Link to={{
pathname: {pathName},
state: {"beer": beer.id}
}} >
<div className="beer-wrapper">
<div className="beer">
<img className="beer-img" src={beer.image_url} alt={beer.name} />
<p className="beer-name">
{beer.name.length < 15 ? beer.name : beer.name.substring(0, 20) }
</p>
<p className="beer-tagline">
{beer.tagline.length < 20 ? beer.tagline : beer.tagline.substring(0, 25) }
</p>
</div>
</div>
</Link>
<Route path="/beer/:id" component={Beer} />
</div>
)
}
return(
<InfiniteScroll
dataLength={beers.length}
next={fetchMoreBeers}
hasMore={true}
loader={<p>Loading ...</p>}
endMessage={<p id="beers-end">No more beers :(</p>}
>
{beerItems}
</InfiniteScroll>
)
}
Maybe there is an undefined value stored in beers array. Try to filter them out, like so:
beers.filter(Boolean).map((beer, index) => (
// ...
)
Also, I've noticed that hasMore prop is always set to true. To see if it helps, try something like:
<InfiniteScroll
dataLength={beers.length}
next={fetchMoreBeers}
hasMore={beers.length < 25} // or hasMore={this.state.hasMore}
loader={<p>Loading ...</p>}
endMessage={<p id="beers-end">No more beers :(</p>}
> ...
EDIT:
You are using Array.from({ length: 20 })). The output array will contain 20 undefined values. That is why you should consider beers.filter(Boolean).
I suppose this is what you really meant:
this.setState({
items: this.state.beers.concat(Array.from(this.state.beers))
})
or:
this.setState({
items: [...this.state.beers, ...this.state.beers]
})
You should check for the status code when fetching the beers. fetch will still let you do your thing even if the request failed. If you exceed your endpoint data limit, you'll get this data added to your beer array:
{
"statusCode": 400,
"error": "Bad Request",
"message": "Invalid query params",
"data": [{
"param": "per_page",
"msg": "Must be a number greater than 0 and less than 80",
"value": "100"
}]
}
As you can see there's no id param there which makes this strange error to show up.
Including a json schema validator (like https://ajv.js.org/) to make sure the data is in expected format would be a perfect choice.
I think that this problem is because of data records limitations in this Api .

Categories

Resources