react and conditional rendering - javascript

Currently I am using the following code to conditionally render some HTML based on hasLocations variable.
Code works, but I wish to know if there is a better way to achieve the same result, for example, I am not sure having two return is a good practice.
const Finder = ({ locations, onLocationClick }) => {
let hasLocations = locations.length > 0
if (hasLocations) {
return (
<ul>
{locations.map((location, index) =>
<Location
key={index}
{...locations[index]}
onLocationClick={() => onLocationClick(location)}
/>
)}
</ul>
)
} else {
return (null)
}
}

Alternatively you chould use conditional rendering. For your example, this would look like this.
const Finder = ({ locations, onLocationClick }) => {
return (
<ul>
{locations.length > 0 &&
locations.map((location, index) =>
<Location
key={index}
{...locations[index]}
onLocationClick={() => onLocationClick(location)}
/>
)}
</ul>
);
}
EDIT: My solution would be the following.
I would avoid adding any logic in it(AKA presentational component). So it would become
const Finder = ({ locations, onLocationClick }) => {
return (
<ul>
locations.map((location, index) =>
<Location
key={index}
{...locations[index]}
onLocationClick={() => onLocationClick(location)}
/>
)
</ul>
);
}
And when you need to use it you can do something like this:
return (
<div>
{locations.length > 0 && Finder}
</div>
);

There's nothing wrong with using multiple returns in a function, but when you do it's good practice to put a "default" return as the last statement in the function to make it more apparent that the function always returns something.
In your case that means you could move your return (null) (no need to wrap null in brackets, btw) statement out of the else clause and just put it as the last statement of the function.
It's also possible to use a single return statement with ternary in your return, like this:
return locations.length > 0 ? (
<ul>
{locations.map((location, index) =>
<Location
key={index}
{...locations[index]}
onLocationClick={() => onLocationClick(location)}
/>
)}
</ul>
) : null

Related

React Component inside .map() is throwing an error: TypeError: notes.map is not a function

am trying to show Noteitem component which is returned inside a map function.
{notes.map((note) => {
return (
<Noteitem key={note._id} updateNote={updateNote} showAlert={props.showAlert} note={note} />
);
})}
notes should be an array for map function to work. You can check it in following way if notes is not null and is an array using notes.length and apply map function
{notes && notes.length && notes.map((note) => {
return (
<Noteitem key={note._id} updateNote={updateNote} showAlert={props.showAlert} note={note} />
);
})}
You can put if/else statement inside JSX to check the variable whether is exist or not
return (
<>
{
notes.length
? 'fallback'
: notes.map(note => <Noteitem key={note._id} updateNote={updateNote} showAlert={props.showAlert} note={note} />)
}
</>
)
IIFE
{(() => {
if ("check note") {
// fallback
}
return notes.map((note: NoteProps) => (
<Noteitem key={note._id} updateNote={updateNote} showAlert={props.showAlert} note={note} />
));
})()}

How to properly destruct object within map function using javascript?

I would like to know how can i destruct object within .map function using javascript, i have react js component and within return method i have the code below:
return (
<>
{setItems.map(setItem => (
const { childContentfulPartFeatureSetLearnMoreOptionalTextTextNode: learnNode} = setItem
....
</>
and i have the next error: Parsing error: Unexpected token ... = setItem, i thought what it is
EsLinterror and used // eslint-disable-next-line to disable it, but it didn't work.
UPD full return code:
return (
<div className={generalServiceItemClassName} key={guuid()}>
{setItems.map(setItem => (
const { childContentfulPartFeatureSetLearnMoreOptionalTextTextNode: learnNode} = setItem
<div
key={guuid()}
className={cx(columnSizeClass, "service-items__item")}
data-test="service-items"
>
{setItem.learnMore ? (
<LearnMore
className="service-items__item-learn-more-container"
learnMoreLink={setItem.learnMore}
text={}
textClassName="service-items__item-texts-learn-more"
learnMoreText={learnNode ? learnNode.setItem : null}
>
{renderItem(setItem)}
</LearnMore>
) : (
renderItem(setItem)
)}
</div>
))}
</div>
)
You can't have a const declaration within an expression, and when you use the concise form of an arrow function (=> without a { after it), the body is an expression.
You can destructure in the parameter list, though. For instance:
{setItems.map(({childContentfulPartFeatureSetLearnMoreOptionalTextTextNode: learnNode}) => (
// ...use `learnNode` here...
In context:
return (
<div className={generalServiceItemClassName} key={guuid()}>
{setItems.map(({childContentfulPartFeatureSetLearnMoreOptionalTextTextNode: learnNode}) => (
<div
key={guuid()}
className={cx(columnSizeClass, "service-items__item")}
data-test="service-items"
>
{setItem.learnMore ? (
<LearnMore
className="service-items__item-learn-more-container"
learnMoreLink={setItem.learnMore}
text={}
textClassName="service-items__item-texts-learn-more"
learnMoreText={learnNode ? learnNode.setItem : null}
>
{renderItem(setItem)}
</LearnMore>
) : (
renderItem(setItem)
)
}
</div>
))}
</div>
);
Try something like this. (destructure and renaming)
const setItems = [{ abc: 5 }];
return (
<>
{setItems.map((setItem) => {
const { abc: xyz } = setItem;
return <div>{xyz}</div>;
})}
</>
);
// Alternate way, simplified.
return (
<>
{setItems.map(({ abc: xyz }) => (
<div>{xyz}</div>
))}
</>
);

TypeError: Cannot read property 'map' of null

I couldn't understand why...here is the GitHub repository: https://github.com/Dronrom/React-test
That’s because you initialized peopleList as null in your component. So map works only on arrays so you need to check peopleList whether its really an array before doing map on it so
Change
renderItems(arr) {
return arr.map(({id, name}) => {
return (
<li className="list-group-item"
key={id}
onClick={() => this.props.onItemSelected(id)}>
{name}
</li>
);
});
}
To
renderItems(arr) {
if(arr){
return arr.map(({id, name}) => {
return (
<li className="list-group-item"
key={id}
onClick={() => this.props.onItemSelected(id)}>
{name}
</li>
);
});
}
}
I think your issue may be that react renders once before componentDidMount(). This is an issue because your calling map on arr which is null. const { peopleList } = this.state; you set people list to your current state which you set as default to be null, state = {peopleList: null}; then you later call this.renderItems(peopleList); which people list is still null at this moment so you are getting the Cannot read property 'map' of null error.
I belive something like componentWillMount is what you need instead. I recommend looking at this post which has a similar issue of react life cycle methods. React render() is being called before componentDidMount()
the answer is very simple: the type of the input isn't array type, it might be null or undefined. so that it doesn't have .map function.
How to fix:
Make sure your input must be array type before call renderItems().
render(){
const { peopleList } = this.state;
const items = (peopleList && peopleList.length) ? this.renderItems(peopleList) : null;
return(
<ul className="item-list list-group">
{items}
</ul>
);
}
Or:
Make sure your input must be array type before do mapping:
renderItems(arr) {
return !arr ? null : arr.map(({id, name}) => {
return (
<li className="list-group-item"
key={id}
onClick={() => this.props.onItemSelected(id)}>
{name}
</li>
);
});
{product.size?.map(c=>(
<FilterSizeOption key={c}>{c}</FilterSizeOption>
))}
Wrapping the return statement with a if statement worked for me
So changed
return (
<div>
<Navbar />
{countries.map((country, i) => {
return (
<div>
<span key={`${country.name.common}${i}`}>
{country.name.common}
</span>
</div>
);
})}
</div>
);
to this
if (countries) {
return (
<div>
<Navbar />
{countries.map((country, i) => {
return (
<div>
<span key={`${country.name.common}${i}`}>
{country.name.common}
</span>
</div>
);
})}
</div>
);
}

How do I fix "Expected to return a value at the end of arrow function" warning?

Everything works fine, but I have this warning Expected to return a value at the end of arrow function array-callback-return. I tried using forEach instead of map, but then <CommentItem /> doesn't even show. How do I fix this?
return this.props.comments.map((comment) => {
if (comment.hasComments === true) {
return (
<div key={comment.id}>
<CommentItem className="MainComment"/>
{this.props.comments.map(commentReply => {
if (commentReply.replyTo === comment.id) {
return (
<CommentItem className="SubComment"/>
) // return
} // if-statement
}) // map-function
} // map-function __begin
</div> // comment.id
) // return
A map() creates an array, so a return is expected for all code paths (if/elses).
If you don't want an array or to return data, use forEach instead.
The warning indicates that you're not returning something at the end of your map arrow function in every case.
A better approach to what you're trying to accomplish is first using a .filter and then a .map, like this:
this.props.comments
.filter(commentReply => commentReply.replyTo === comment.id)
.map((commentReply, idx) => <CommentItem key={idx} className="SubComment"/>);
The easiest way only if you don't need return something it'ts just return null
The problem seems to be that you are not returning something in the event that your first if-case is false.
The error you are getting states that your arrow function (comment) => { doesn't have a return statement. While it does for when your if-case is true, it does not return anything for when it's false.
return this.props.comments.map((comment) => {
if (comment.hasComments === true) {
return (
<div key={comment.id}>
<CommentItem className="MainComment" />
{this.props.comments.map(commentReply => {
if (commentReply.replyTo === comment.id) {
return (
<CommentItem className="SubComment"/>
)
}
})
}
</div>
)
} else {
//return something here.
}
});
edit you should take a look at Kris' answer for how to better implement what you are trying to do.
The most upvoted answer, from Kris Selbekk, it is totally right. It is important to highlight though that it takes a functional approach, you will be looping through the this.props.comments array twice, the second time(looping) it will most probable skip a few elements that where filtered, but in case no comment was filtered you will loop through the whole array twice. If performance is not a concern in you project that is totally fine. In case performance is important a guard clause would be more appropriated as you would loop the array only once:
return this.props.comments.map((comment) => {
if (!comment.hasComments) return null;
return (
<div key={comment.id}>
<CommentItem className="MainComment"/>
{this.props.comments.map(commentReply => {
if (commentReply.replyTo !== comment.id) return null;
return <CommentItem className="SubComment"/>
})}
</div>
)
}
The main reason I'm pointing this out is because as a Junior Developer I did a lot of those mistakes(like looping the same array multiple times), so I thought i was worth mention it here.
PS: I would refactor your react component even more, as I'm not in favour of heavy logic in the html part of a JSX, but that is out of the topic of this question.
You can use the for loop like so:
for(let i = 0 ; i < comments.length; i++){
if(comments[i].hasComments === true){
return (
<div key={comments[i].id}>
//content Here
</div> // comment.id
)
}
}
class Blog extends Component{
render(){
const posts1 = this.props.posts;
//console.log(posts)
const sidebar = (
<ul>
{posts1.map((post) => {
//Must use return to avoid this error.
return(
<li key={post.id}>
{post.title} - {post.content}
</li>
)
})
}
</ul>
);
const maincontent = this.props.posts.map((post) => {
return(
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
)
})
return(
<div>{sidebar}<hr/>{maincontent}</div>
);
}
}
const posts = [
{id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
{id: 2, title: 'Installation', content: 'You can install React from npm.'}
];
ReactDOM.render(
<Blog posts={posts} />,
document.getElementById('root')
);

How do I resolve React JSX's adjacent component wrapper error when I need to dynamically add markup in between elements?

I have a TypeList component that will show Type components in a table view like so:
I need to give the TypeList CSS with display: table and then add rows for every three types that are listed, so every three types need to be nested inside a <div class="table-row"></div>. I tried to do this with the logic below but get the adjacent JSX error. Is there something I can change to solve this?
TypeList.js
import Type from './Type'
const TypeList = ({info}) => (
<ul className="table">
<div class="table-row">
{
info.map(function (type, idx) {
// onclick details taken care of here
if (idx === map.length - 1){
return (
<Type key={idx} name={type.name} />
</div>
)
} else if (idx === 2) {
return (
<Type key={idx} name={type.name} />
</div>
<div class="table-row">
)
} else {
return (<Type key={idx} name={type.name} />)
}
})
}
</ul>
)
It's a bit ugly but it's the only solution I've found for problems like this.
function renderstuff(...) {
return (
<div>
(...)
</div>
).props.children;
}
This function renders your things in a <div> wrapper and then strips off the wrapper by returning .props.children.
It might work in your case, just extract the contents of your .map callback into a function, wrap your markup and return props.children of the wrapper.
I have rewritten the map function so that you don't have this error:
const TypeList = ({info}) => (
<ul className="table">
<div class="table-row">
{
info.splice(0, 2).map(function (type, idx) {
return <Type key={idx} name={type.name} />;
})
}
</div>
<div class="table-row">
{
info.map(function (type, idx) {
return (
<Type key={idx} name={type.name} />
);
})
}
</div>
</ul>
)

Categories