JSX conditional modification of outer element - javascript

I'm trying to do something like this:
render() {
let a = <a className="nav-link" href={this.props.href} />
if (this.props.hasCollapse) {
a = <a className="nav-link"
href={this.props.href}
data-toggle="collapse"
data-target={this.props.collapseId} />
}
return (
{a}
<i className="fas fa-fw fa-cog"></i>
<span>{this.props.title}</span>
{a}
)
}
where the {a} in the return is replaced by the appropriate version as determined by the hasCollapse property. How might I achieve this?

You should change the properties for the link. For example
function AwesomeLink({ collapseId, hasCollapse, href, title }) {
let hrefProps = {};
if (hasCollapse) {
hrefProps = {
'data-toggle': 'collapse',
'data-target': collapseId,
};
}
return (
<a className='nav-link' href={href} {...hrefProps}>
<i className='fas fa-fw fa-cog' />
<span>{title}</span>
</a>
);
}

I would do this with simple conditionals:
const Link = (props) => {
return (<a className="nav-link"
href={props.href}
data-toggle={props.hasCollapse ? 'collapse' : null}
data-target={props.hasCollapse ? props.collapseId : null}
>
<i className="fas fa-fw fa-cog"/>
<span>{props.title}</span>
</a>
)
}
Also, it looks like you're setting data properties for usage with Bootstrap or a similar library. You may want to look into React Bootstrap and make use of their components designed to be used in React without dealing with messy DOM manipulation.

Related

Can't pass target.value to state hook return undefined - React.js React

When clicked I want to pass name prop to state hook but it returns undefined
const [fav, setFav ] = useState([]);
useEffect(() => {
dispatch(fetchProfileAction(user));
dispatch(fetchReposAction(user));
}, [user, dispatch]);
{reposList?.name !== "Error" &&
reposList?.map(repo => (
<>
<div className="col-6 g-3 ">
<div className="border rounded-pill px-3 py-2 bg-secondary ">
<i
class="far fa-star pe-2" role={ 'button'}
name={repo?.name} role={'button'}
onClick={e => setFav(...fav, e.target.name)}
></i>
<a
target="_blank"
href={repo?.html_url}
className="text-light"
name={repo?.name} role={'button'}
onClick={e => setFav(...fav, e.target.name)}
>
{repo?.name}
</a>
<strong className="text-light ps-2">({repo?.language})</strong>
</div>
</div>
</>
))}
If you want to append a the name to the existing array of fav, then use functional updates instead:
onClick={e => setFav(fav => [...fav, e.target.name])}
It is recommend that states should be treated as readonly: any modifications to the current state that is based on the previous state should use the functional update pattern. This allows React to batch changes to the state together: see further explanation in this answer.
According to https://docs.w3cub.com/dom/element/name name officially only applies to the following elements: <a>, <applet>, <button>, <form>, <frame>, <iframe>, <img>, <input>, <map>, <meta>, <object>, <param>, <select>, and <textarea>.
So, since you have used the name attribute on i tag, you should try to get the name value by getAtrribute, like this:
<i
class="far fa-star pe-2" role={ 'button'}
name={repo?.name}
role={'button'}
onClick={e => setFav( [...fav, e.target.getAttribute('name')] )}

How to optimize toggling className between different menu items by React?

I have header component, where I want to toggle className between all the elements of menu (if one of the elements of menu is active and user is clicking to another element - this element become active and all others no). I have a code like this
import React, { useState } from 'react';
import './header.scss';
export const Header = ({ favoriteCount }) => {
const [activeIndex, setActiveIndex] = useState(0);
function toggleClass(index) {
setActiveIndex(index);
}
return (
<header className="header">
<div className="container header-container">
<ul className="header-menu">
<li>
<a
className={
activeIndex === 0
? 'header-menu__link active'
: 'header-menu__link'
}
onClick={() => {
toggleClass(0);
}}
href="##"
>
Characters
</a>
</li>
<li>
<a
className={
activeIndex === 1
? 'header-menu__link active'
: 'header-menu__link'
}
onClick={() => {
toggleClass(0);
}}
href="##"
>
Favorites
</a>
</li>
</ul>
<div className="header-favorite-count">
<i className="far fa-heart"></i>
{favoriteCount}
</div>
</div>
</header>
);
};
and styles to visualise toggling classes
&-menu__link {
color: lightgray;
}
.active {
color: #fff;
}
This approach is working but looks creepy. Maybe somebody knows how to optimize it?
I wouldn't use the index, I'd use the text of the item. I'd also include that text in the href so that there's an indication of what the anchor leads to. To avoid repeated code, you might put the menu items in a reusable array, something like this:
const menuItems = [
"Characters",
"Favorites",
];
export const Header = ({ favoriteCount }) => {
const [activeItem, setActiveItem] = useState("");
const setActiveItem = useCallback((event) => {
setActiveItem(event.currentTarget.href.substring(2));
}, []);
const list = menuItems.map(item =>
<li key={item}>
<a
className={`header-menu__link ${item === activeItem ? "active" : ""}`}
onClick={setActiveItem}
href={"##" + item}>
{item}
</a>
</li>
);
return (
<header className="header">
<div className="container header-container">
<ul className="header-menu">
{list}}
</ul>
<div className="header-favorite-count">
<i className="far fa-heart"></i>
{favoriteCount}
</div>
</div>
</header>
);
};

Mapping nested array inside mapped array in React JSX

I'm trying to write a dashboard sidebar which has a couple of "primary" buttons which (via Bootstrap) collapse a number of "secondary" buttons. I want to be able to easily update and style the whole thing so writing static markup is out of the picture. Here is one object out of the array:
const menuArray = [
{
primaryText: "Applications",
primaryIcon: "fa fa-rocket",
primaryURL: "/applications",
secondaries: [
{
secondaryText: "Softwares",
secondaryURL: "/softwares",
},
{
secondaryText: "Videogames",
secondaryURL: "/videogames",
},
{
secondaryText: "Tools",
secondaryURL: "/tools",
},
],
},
]
And here is the function rendering the array which i'm simply calling in the JSX markup by {renderMenuArray}
const renderMenuArray = menuArray.map((menuItem) => (
<li className="py-2">
<button
data-target={`#${menuItem.primaryText}Submenu`}
data-toggle="collapse"
aria-expanded="false"
className="btn btn-dark btn-menu btn-block pl-0 mb-1"
>
<Link to={menuItem.primaryURL}>
<span className="mr-3">
<i className={menuItem.primaryIcon}></i>
</span>
{menuItem.primaryText}
</Link>
</button>
<div
className="card-body collapse ml-5"
id={`${socialItem.primaryText}Submenu`}
>
<ul className="list-unstyled">
<li>
<Link className="small" to="/applications/softwares">
<span className="mr-3">
<i className="fa fa-chevron-right"></i>
</span>
Softwares
</Link>
</li>
</ul>
</div>
));
I can render the "primary" objects with no problem at all, but I want each "primary" object ( each iteration of the parent array) to each iterate through the count of "secondaries" array ( which is going to be different for each "primary" object).
I'm a beginner developer.
The object passed into the callback function for the map, menuItem has the included property secondaries. You can use map on this property since secondaries is an array, simply place the map inside of your JSX.
<div
className="card-body collapse ml-5"
id={`${socialItem.primaryText}Submenu`}
>
<ul className="list-unstyled">
{menuItem.secondaries.map((subItem) => {
...
})
</ul>
</div>
P.S. you forgot to close your last div tag, and you have a second </li> instead of a </ul>
This'll work (made it a bit more functional, to give you an example of what's possible with functional React components)
const menuArray = [
{
primaryText: "Applications",
primaryIcon: "fa fa-rocket",
primaryURL: "/applications",
secondaries: [
{
secondaryText: "Softwares",
secondaryURL: "/softwares"
},
{
secondaryText: "Videogames",
secondaryURL: "/videogames"
},
{
secondaryText: "Tools",
secondaryURL: "/tools"
}
]
}
];
export default function App(props) {
function renderSecondaryMenu(items) {
return items.map(secondaryItem => {
return (
<li>
<Link className="small" to={secondaryItem.secondaryURL}>
<span className="mr-3">
<i className="fa fa-chevron-right" />
</span>
{secondaryItem.secondaryText}
</Link>
</li>
);
});
}
function renderMenuArray() {
return menuArray.map(menuItem => {
return (
<li className="py-2">
<button
data-target={`#${menuItem.primaryText}Submenu`}
data-toggle="collapse"
aria-expanded="false"
className="btn btn-dark btn-menu btn-block pl-0 mb-1"
>
<Link to={menuItem.primaryURL}>
<span className="mr-3">
<i className={menuItem.primaryIcon} />
</span>
{menuItem.primaryText}
</Link>
</button>
<div
className="card-body collapse ml-5"
id={`${menuItem.primaryText}Submenu`}
>
<ul className="list-unstyled">
{renderSecondaryMenu(menuItem.secondaries)}
</ul>
</div>
</li>
);
});
}
return <div className="App">{renderMenuArray()}</div>;
}
I don't think you gain a lot from defining the sidebar as an object, unless you are using the same Sidebar component with different sets of buttons and you wanna be able to switch between them. JSX is already a markup language, so it's declarative.
You could define simply the Sidebar as
const Sidebar = () => (
<PrimaryItem text="Applications" icon="fa fa-rocket" url="/applications">
<SecondaryItem text="Softwares" url="/softwares" />
<SecondaryItem text="Videogames" url="/videogames" />
<SecondaryItem text="Tools" url="/tools" />
</PrimaryItem>
)
And then implement each component as you need to display them. I think this is cleaner and easier to maintain and modify.

How to Divide React Components into Presentational and Container Components

Still new to react and redux and have been working on a MERN user registration application which I got working now.
In the redux documentation I found the creators recommend splitting their code up into two types of components when integrating redux with react: Presentational (concerns with how things look) and Container (concerns with how things work). See https://redux.js.org/basics/usagewithreact.
I think this would allow for better management and scalability of the application.
For people unfamiliar, here is a good explanation of the advantages: https://www.youtube.com/watch?v=NazjKgJp7sQ
I only struggle in grasping the concept and rewriting the code in such a way.
Here is an example of a post component I written using to display user created comments. It is receiving the data from the post in a higher-level component passed down as props. In the return I have all my markup with bootstrap styling applied. I am subscribing to redux actions I imported and using by creating event handlers.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import classnames from 'classnames';
import { Link } from 'react-router-dom';
import { deletePost, addLike, removeLike } from '../../actions/postActions';
class PostItem extends Component {
onDeleteClick(id) {
this.props.deletePost(id);
}
onLikeClick(id) {
this.props.addLike(id);
}
onUnlikeClick(id) {
this.props.removeLike(id);
}
findUserLike(likes) {
const { auth } = this.props;
if (likes.filter(like => like.user === auth.user.id).length > 0) {
return true;
} else {
return false;
}
}
render() {
const { post, auth, showActions } = this.props;
return (
<div className="card card-body mb-3">
<div className="row">
<div className="col-md-2">
<a href="profile.html">
<img
className="rounded-circle d-none d-md-block"
src={post.avatar}
alt=""
/>
</a>
<br />
<p className="text-center">{post.name}</p>
</div>
<div className="col-md-10">
<p className="lead">{post.text}</p>
{showActions ? (
<span>
<button
onClick={this.onLikeClick.bind(this, post._id)}
type="button"
className="btn btn-light mr-1"
>
<i
className={classnames('fas fa-thumbs-up', {
'text-info': this.findUserLike(post.likes)
})}
/>
<span className="badge badge-light">{post.likes.length}</span>
</button>
<button
onClick={this.onUnlikeClick.bind(this, post._id)}
type="button"
className="btn btn-light mr-1"
>
<i className="text-secondary fas fa-thumbs-down" />
</button>
<Link to={`/post/${post._id}`} className="btn btn-info mr-1">
Comments
</Link>
{post.user === auth.user.id ? (
<button
onClick={this.onDeleteClick.bind(this, post._id)}
type="button"
className="btn btn-danger mr-1"
>
<i className="fas fa-times" />
</button>
) : null}
</span>
) : null}
</div>
</div>
</div>
);
}
}
PostItem.defaultProps = {
showActions: true,
};
PostItem.propTypes = {
deletePost: PropTypes.func.isRequired,
addLike: PropTypes.func.isRequired,
removeLike: PropTypes.func.isRequired,
post: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired,
};
const mapStateToProps = state => ({
auth: state.auth,
});
export default connect(mapStateToProps, { deletePost, addLike, removeLike })(PostItem);
As you can see is the code not as neat and compact as I would like. My goal is to make the presentational component unaware of redux, and do all styling and bootstrap stuff here, while the container component have the redux and connect functionalities. Does anyone know how I should approach this?
I saw people using connect to link these types components together:
const PostItemContainer = connect(
mapStateToProps,
{ deletePost, addLike, removeLike }
)(PostItem);
export default PostItemContainer;
But I have no idea how to achieve this in practice.
If you could help me explain and provide some example code that would be amazing.
Thanks in advance!
I would always put my html like ( presentation ) code in another file, which in react they call stateless component,
The key component is PostItemComponent which does not know anything about redux.
see the code below :
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import classnames from 'classnames';
import { Link } from 'react-router-dom';
import { deletePost, addLike, removeLike } from '../../actions/postActions';
const PostItemComponent = ({
post,
showActions,
auth,
onLikeClick,
findUserLike,
onUnlikeClick,
onDeleteClick
}) => {
return (
<div className="card card-body mb-3">
<div className="row">
<div className="col-md-2">
<a href="profile.html">
<img
className="rounded-circle d-none d-md-block"
src={post.avatar}
alt=""
/>
</a>
<br />
<p className="text-center">{post.name}</p>
</div>
<div className="col-md-10">
<p className="lead">{post.text}</p>
{showActions ? (
<span>
<button
onClick={(event) => onLikeClick(event, post._id)}
type="button"
className="btn btn-light mr-1">
<i
className={classnames('fas fa-thumbs-up', {
'text-info': findUserLike(post.likes)
})}
/>
<span className="badge badge-light">{post.likes.length}</span>
</button>
<button
onClick={(event) => onUnlikeClick(event, post._id)}
type="button"
className="btn btn-light mr-1"
>
<i className="text-secondary fas fa-thumbs-down" />
</button>
<Link to={`/post/${post._id}`} className="btn btn-info mr-1">
Comments
</Link>
{post.user === auth.user.id ? (
<button
onClick={(event) => onDeleteClick(event, post._id)}
type="button"
className="btn btn-danger mr-1"
>
<i className="fas fa-times" />
</button>
) : null}
</span>
) : null}
</div>
</div>
</div>
);
};
class PostItem extends Component {
constructor(props) {
super(props);
this.onDeleteClick = this.onDeleteClick.bind(this);
this.onLikeClick = this.onLikeClick.bind(this);
this.onUnlikeClick = this.onUnlikeClick.bind(this);
this.findUserLike = this.findUserLike.bind(this);
}
onDeleteClick(event, id) {
event.preventDefault();
this.props.deletePost(id);
}
onLikeClick(event, id) {
event.preventDefault();
this.props.addLike(id);
}
onUnlikeClick(event, id) {
event.preventDefault();
this.props.removeLike(id);
}
findUserLike(likes) {
const { auth } = this.props;
if (likes.filter(like => like.user === auth.user.id).length > 0) {
return true;
} else {
return false;
}
}
render() {
const { post, auth, showActions } = this.props;
return (
<PostItemComponent
post={post}
auth={auth}
showActions={showActions}
onDeleteClick={this.onDeleteClick}
onLikeClick={this.onLikeClick}
onUnlikeClick={this.onUnlikeClick}
/>
);
}
}
PostItem.defaultProps = {
showActions: true,
};
PostItem.propTypes = {
deletePost: PropTypes.func.isRequired,
addLike: PropTypes.func.isRequired,
removeLike: PropTypes.func.isRequired,
post: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired,
};
const mapStateToProps = state => ({
auth: state.auth,
});
export default connect(mapStateToProps, { deletePost, addLike, removeLike })(PostItem);
This is a very similar suggestion to #jsDevia's answer but I don't create a separate component here since you said your Post component is already connected to Redux. So, you can grab all action creators and state there and pass those to your PostItem component.
The second difference is I use a functional component instead of class component since you don't need any state or lifecycle method here.
The third difference is a small one. I removed all binding from your onClick handlers. For the this scope issue, I'm using arrow functions for the handlers. Again, we don't need any argument, like post._id to pass those function because we already have post as a prop here. This is the beauty of separating our components.
Using bind or an arrow function in callback handlers cause some performance issues for larger apps which have so many components like Post. Since those functions recreated every time this component renders. But, using the function reference prevents this.
const PostItem = ({
post,
deletePost,
addLike,
removeLike,
auth,
showActions,
}) => {
const onDeleteClick = () => deletePost(post._id);
const onLikeClick = () => addLike(post._id);
const onUnlikeClick = () => removeLike(post._id);
const findUserLike = likes => {
if (likes.filter(like => like.user === auth.user.id).length > 0) {
return true;
} else {
return false;
}
};
return (
<div className="card card-body mb-3">
<div className="row">
<div className="col-md-2">
<a href="profile.html">
<img
className="rounded-circle d-none d-md-block"
src={post.avatar}
alt=""
/>
</a>
<br />
<p className="text-center">{post.name}</p>
</div>
<div className="col-md-10">
<p className="lead">{post.text}</p>
{showActions ? (
<span>
<button
onClick={onLikeClick}
type="button"
className="btn btn-light mr-1"
>
<i
className={classnames("fas fa-thumbs-up", {
"text-info": findUserLike(post.likes),
})}
/>
<span className="badge badge-light">{post.likes.length}</span>
</button>
<button
onClick={onUnlikeClick}
type="button"
className="btn btn-light mr-1"
>
<i className="text-secondary fas fa-thumbs-down" />
</button>
<Link to={`/post/${post._id}`} className="btn btn-info mr-1">
Comments
</Link>
{post.user === auth.user.id ? (
<button
onClick={onDeleteClick}
type="button"
className="btn btn-danger mr-1"
>
<i className="fas fa-times" />
</button>
) : null}
</span>
) : null}
</div>
</div>
</div>
);
};
By the way, do not struggle with the example that is given on Redux's documentation. I think it is a little bit complex for newcomers.

Build dropdown menu with array passed from parent component [duplicate]

This question already has answers here:
map function not working in React
(3 answers)
Index inside map() function
(4 answers)
Closed 25 days ago.
I am trying to build a dropdown-menu and add data from my array to the dropdown-item. My current code isn't returning anything into my const Users. How can I use the array to add data into the dropdown-item?
UserDisplay component
const UserDisplay = ({ users }) => {
const Users = users.map(user => {
let i = 0;
<a className="dropdown-item" href="#">
{user[i]}
</a>;
i++;
});
return (
<div className="dropdown-menu" id="users">
<a className="dropdown-item" href="#">
Online Users
</a>
<div className="dropdown-divider" />
{Users}
</div>
);
};
Parent Component ChatLayout
return (
<div className="chat navbar fixed-bottom">
<div className="btn-group dropup">
<button
type="button"
className="btn btn-secondary dropdown-toggle"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Chat
</button>
<UserDisplay users={[this.state.users]} />
</div>
<ul id="messages">
<div />
</ul>
<form onSubmit={this.onSubmit}>
<textarea
name="message"
placeholder="Enter your message here"
autoComplete="off"
type="submit"
onKeyDown={this.onEnterPress}
value={this.state.message}
onChange={this.onChange}
/>
<input type="submit" className="btn btn-info btn-block mt-4" />
</form>
</div>
);
You don't need to define and iterate i.. the .map already keeps track of the array index. Assuming users has data it should work like this...
UserDisplay(users) {
const Users = users.map((user,i) => {
return (
<a className="dropdown-item" key={i} href="#">
{user}
</a>)
});
return (
<div className="dropdown-menu" id="users">
<a className="dropdown-item" href="#">
Online Users
</a>
<div className="dropdown-divider" />
{Users}
</div>
);
};
Working Codeply: https://www.codeply.com/go/DnqpGhozra
You are destructing props object and get users out of it so its fine.
basically map returns a list of array so you dont have return anything.
You need to iterate to the first element of users like
const Users = users[0].map((user,i) => {
console.log(user);
return (
<a className="dropdown-item" key={i} href="#">
{user}
</a>)
});
OR just pass users directly
<UserDisplay users={this.state.users} />

Categories

Resources