I have a component which receives a list of items through props.
It looks like this:
const component = (props) => {
return (
<ul>
{props.list.map((item) => (
<ListItem key={item.Id} title={item.title} imgSrc={item.img.url} />
))}
</ul>
);
};
edit:
and the child looks like this:
const ListItem = (props) => {
return (
<li key={props.key}>
<h4>{props.title}</h4>
<div>
<img src={props.imgSrc} alt='thumbnail'
/>
</div>
</li>
);
};
The list comes from an API and there are cases in which the values I am assigning will be undefined or not available (imgSrc for example). This breaks the entire rendering of the app.
How can I handle errors in a way that will skip the problematic item and continue with the mapping? It usually means this is a deleted item so I wish to skip it all together.
I usually wrap the code with a try-catch or if statement but I am not allowed to do it here.
There are many options to solve that. For example, you could use the filter method before your .map call.
const component = (props) => {
return (
<ul>
{props.list.filter((item) => item.img.url !== undefined).map((item) => (
<ListItem key={item.Id} title={item.title} imgSrc={item.img.url} />
))}
</ul>
);
};
Another possible option could be Error Boundaries. I don't think that they are what you need, but it could be interesting for you anyways.
You can conditional rendering.
Array.isArray(props.list) &&
props.list.map((item) => (
<ListItem key={item.Id} title={item.title} imgSrc={item.img.url} />
));
You can only map over the array if it is an array as:
const component = (props) => {
return (
<ul>
{Array.isArray(props.list) && props.list.map((item) => (
<ListItem key={item.Id} title={item.title} imgSrc={item.img.url} />
))}
</ul>
);
};
Related
When I just list the items directly, using index works. As in the following.
<ol className="item-list">
{
props.items.map((item, index) => (
<li key={index}>{item}</li>
))
}
</ol>
But when I create a custom component to represent the list item, using index doesn't seem to qualify as being unique... And I end up getting a warning, as in the following.
<ol className="item-list">
{
props.items.map((item, index) => (
<ShoppingItem
index={index}
item={item}
/>
))
}
</ol>
The ShoppingItem is a simple component, like the following.
const ShoppingItem = props => (
<li key={props.index}>{props.item}</li>
);
And the warning I get in the console is the following.
Warning: Each child in a list should have a unique "key" prop.
You should read carefully the react docs for Lists and Keys: Extracting components with keys. The key goes on the component being mapped, not what it renders.
Incorrect
const ShoppingItem = props => (
<li key={props.index}>{props.item}</li>
);
<ol className="item-list">
{
props.items.map((item, index) => (
<ShoppingItem
index={index}
item={item}
/>
))
}
</ol>
Correct
<ol className="item-list">
{
props.items.map((item, index) => (
<ShoppingItem
key={index} // <-- key goes here
item={item}
/>
))
}
</ol>
(I apologize for the ugly code in advance -- currently refactoring)
I'm making a Table of content where the nested content appear when I click on its parent component.
For my logic, I need to pass the value of the list key to its children but I keep receiving an undefined error or nothing at all. I tried to pass the value like this: key={node2.objectId} and keyId={node2.objectId}
I read the specifications on how to pass the key value as a prop here and here
Yet, nothing works.
Here's my code:
import React from "react";
const TocContent = (props) => {
return (
<div className="">
{props.TOC.map((header) => (
<ul
key={header.objectId}
onMouseDown={(e) => e.stopPropagation()}
onClick={(e) =>
props.handleHeaderClick(
header.level,
header.treepath,
header.containsLaw,
header.sections,
header.secNum,
header.objectId,
header.id,
e.stopPropagation(),
)
}
className="TOC TOCsection"
>
{header._id}
{props.headerIndex === header.objectId
? props.headers2.map((node2) => (
<HeaderList
key={node2.objectId}
header={node2}
props={props}
keyId={node2.objectId}
>
{console.log(props.keyId)}
//--problem is here-- {props.headerIndex2 === props.keyId
? props.headers3.map((node3) => (
<HeaderList
key={node3.objectId}
header={node3}
props={props}
>
{props.headerIndex3 === node3.objectId
? props.headers4.map((node4) => (
<HeaderList
header={node4}
key={node4.objectId}
props={props}
/>
))
: null}
</HeaderList>
))
: null}
</HeaderList>
))
: null}
</ul>
))}
</div>
);
};
const HeaderList = ({ header, props }) => {
return (
<ul
onMouseDown={(e) => e.stopPropagation()}
onClick={(e) =>
props.handleHeaderClick(
header.level,
header.treepath,
header.containsLaw,
header.sections,
header.secNum,
header.objectId,
header.id,
e.stopPropagation(),
)
}
>
{header._id}
</ul>
);
};
export default TocContent;
I finally resorted to change the structure a bit. Instead of the code above, I opted to render the HeaderList component directly in its own component (as a child of itself). This way, I'm able to read header.objectId and make the code shorter.
Here's the new code:
import React from "react";
const TocContent = (props) => {
return (
<div className="">
{props.TOC.map((header) => (
<HeaderList key={header.objectId} header={header} props={props} />
))}
</div>
);
};
const HeaderList = ({ header, props }) => {
return (
<ul
onMouseDown={(e) => e.stopPropagation()}
onClick={(e) =>
props.handleHeaderClick(
header.level,
header.treepath,
header.containsLaw,
header.sections,
header.secNum,
header.objectId,
header.id,
e.stopPropagation(),
)
}
>
{header._id}
{/* // if savedIndex === CurrentParent Index */}
{props.headerIndex === header.objectId &&
props.headers2.map((node2) => (
<HeaderList key={node2.objectId} header={node2} props={props} />
))}
{props.headerIndex2 === header.objectId &&
props.headers3.map((node3) => (
<HeaderList key={node3.objectId} header={node3} props={props} />
))}
{props.headerIndex3 === header.objectId &&
props.headers4.map((node4) => (
<HeaderList header={node4} key={node4.objectId} props={props} />
))}
</ul>
);
};
export default TocContent;
I understand this is maybe not the cleanest code, but an improvement nonetheless. If someone wants to propose something better, it will be much appreciated.
I have the following React.js component where I get 10 multiple-choice trivia questions via an API call using fetch and recursively list them on the page via nested components.
The API provides 'correct_answer' as string, and 'incorrect_answers' separately as an array of strings. In my current code, I am only able to list the 'correct' and 'incorrect' answers in their own components.
What I would like to do is combine the 'correct' and 'incorrect' answers into a single array and then randomise the output of them, so that the correct answer is not always in the same place in the list. How would I alter my current code to that? I am an absolute beginner at React.js so any pointers are welcome, thanks.
import React, { Component } from "react";
class QuestionContainer extends Component {
constructor() {
super();
this.state = {
questions: []
};
}
componentWillMount() {
const RenderHTMLQuestion = (props) => (<p dangerouslySetInnerHTML={{__html:props.HTML}}></p>)
const RenderHTMLAnswer = (props) => (<li dangerouslySetInnerHTML={{__html:props.HTML}}></li>)
fetch('https://opentdb.com/api.php?amount=10&category=9&type=multiple')
.then(results => {
return results.json();
}).then(data => {
let questions = data.results.map((question, index) => {
return(
<div key={index} className="questionWrapper">
<div className="question" key={question.question}>
<RenderHTMLQuestion HTML={question.question} />
</div>
<ul className="answers">
<RenderHTMLAnswer key={question.correct_answer} HTML={question.correct_answer} />
{question.incorrect_answers.map((answer, index) => (
<RenderHTMLAnswer key={index} HTML={answer} />
))}
</ul>
</div>
)
})
this.setState({questions: questions});
})
}
render() {
return (
<div className="container2">
{this.state.questions}
</div>
)
}
}
export default QuestionContainer;
You can try this,
let questions = data.results.map((question, index) => {
let correctAnswer = false
return (
<div key={index} className="questionWrapper">
<div className="question" key={question.question}>
<RenderHTMLQuestion HTML={question.question} />
</div>
<ul className="answers">
{question.incorrect_answers.map((answer, index) => {
if(Math.floor(Math.random() * Math.floor(question.incorrect_answers.length-1)) === index && !correctAnswer) {
correctAnswer = true
return <> <RenderHTMLAnswer key={question.correct_answer} HTML={question.correct_answer} /> <RenderHTMLAnswer key={index} HTML={answer} /> </>
}
return <RenderHTMLAnswer key={index} HTML={answer} />
})}
</ul>
</div>
)
})
Math.random()
Note: Don't use componentWillMount (UNSAFE), instead you can go for componentDidMount for your API call.
Update
You can also try this,
let questions = data.results.map((question, index) => {
question.incorrect_answers.splice(Math.floor(Math.random() * Math.floor(question.incorrect_answers.length + 1)), 0, question.correct_answer)
return (
<div key={index} className="questionWrapper">
<div className="question" key={question.question}>
<RenderHTMLQuestion HTML={question.question} />
</div>
<ul className="answers">
{question.incorrect_answers.map((answer, index) => {
return <RenderHTMLAnswer key={index} HTML={answer} />
})}
</ul>
</div>
)
})
Array.prototype.splice()
Demo
You can try using lodash
https://lodash.com/docs/4.17.15#shuffle
const answers = _.shuffle(_.concat(correct_answers, incorrect_answers));
return (
<ul className="answers">
{answers.map((answer, index) => (<RenderHTMLAnswer key={index} HTML={answer} />))}
</ul>
)
This is code
const UserItem = (user, index) => (
<div key={index} className="accordion__item js-accordion-item">
<div className="accordion-header js-accordion-header">{(index+1)} . {user.invoiceId}
</div>
<div className="accordion-body js-accordion-body">
<div className="accordion-body__contents">
{user.sender.city}
<button href="#" onClick={handleClick} id={user.invoiceId}>
Click me
</button>
</div>
</div>
</div>
);
You probably used your component UserItem like this in your code
{
users.map((user, index) => <UserItem user={user} index={index} />)
}
In this case you must declare your component like this
const UserItem = ({user, index}) => ( ... );
instead of
const UserItem = (user, index) => ( ... );
Finally you should set the key property on UserItem, not on the div inside UserItem so basically you shoud write
{
users.map((user, index) => <UserItem user={user} index={index} key={index} />)
}
or event better with a unique id
{
users.map((user, index) => <UserItem user={user} index={index} key={user.invoiceId}/>)
}
I have a need, in a site I'm building, for a list component that is reused several times. However, the list is purely for rendering and is not responsible for the state of the app at all. I know you either cannot, or are not supposed to have dumb components containing any logic, but I am not sure how to proceed without using a smart component, which is entirely unnecessary. Here is my smart component that works:
class Menu extends Component {
renderItems(items) {
return this.props.items.map((i, index) => {
return (
<li key={index} style={{marginLeft: 10}}>
{i}
</li>
)
});
}
render() {
const { listStyle } = styles;
return (
<div>
<ul style={listStyle}>
{this.renderItems()}
</ul>
</div>
)
}
}
And I've tried this:
function Menu(props) {
return props.items.map((i, index) => {
<li key={index} style={{marginLeft: 10}}>
{i}
</li>
});
}
And then calling it inside Nav like this, which does not throw an error but does not render anything from menu either:
const Nav = () => {
const { listStyle, containerStyle } = styles;
return (
<div style={containerStyle}>
<Logo url={'#'}
src={PickAPlayLogo}
width={300} />
<Menu items={pageLinks} />
<Menu items={socialMediaLinks} />
<Logo url={'#'}
src={AppStoreLogo}
width={170} />
</div>
);
};
Also, worth noting, I have never come across a function that is supposed to be rendered like a component, but was trying it based on the example on this page
Heres an answer similar to what you have going on
function Menu(props) {
this.renderItems = () => {
return (
<ul>
{props.items.map((i, index) => {
return (
<li>{i}</li>
)
})}
</ul
)
}
return(
this.renderItems()
)
}
Here we go:
function Menu(props) {
const {listStyle} = styles;
const listItems = props.items.map((i, index) =>
<li key={index} style={{marginLeft: 10}}>
{i}
</li>
);
return (
<ul style={listStyle}>{listItems}</ul>
);
}