React - Generating different Icons within a map function - reactjs

I have a sidebar components whose list items are generated inside of a map function (movie genres fetched from an api). I would like to have a different icon next to each of the movie genres but have been unable to figure out a way to create a different for each run of the map function.
My thoughts are
1st: make an array of the icons I would like to display, in the order that I would like them to display in.
const icons = [
"AiOutlineHome ",
"FaUserNinja",
"GiSwordman",
"GiBabyFace",
"FaLaughBeam",
"GiPistolGun",
"GiPineTree",
"GiDramaMasks",
"GiFamilyHouse",
"GiElfEar",
"GiScrollUnfurled",
"GiScreaming",
"GiMusicalNotes",
"GiMagnifyingGlass",
"FaRegKissBeam",
"GiMaterialsScience",
"GiHalfDead",
"GiGreatWarTank",
"GiCowboyBoot",
];
Then in my map function generate a different icon for each list item
{movieGenres.map((genre) => {
return (
<li key={genre.id} className="nav-text">
<button
className="genre-btn"
onClick={() => genreSelectionHandler(genre.id)}
>
*<GENERATE ICON HERE />*
<span className="nav-item-title">{genre.name}</span>
</button>
</li>
);
})}
Is it possible to pragmatically create components like this? So that I can avoid creating each list item individually if I want different icon.

You can just take the index of your genre to get the icon. The index is the second argument to the callback of your map() function:
{movieGenres.map((genre, idx) => (
<li key={genre.id} className="nav-text">
<button
className="genre-btn"
onClick={() => genreSelectionHandler(genre.id)}
>
<Icon icon={icons[idx]} />
<span className="nav-item-title">{genre.name}</span>
</button>
</li>
))}
The Icon component would render the icon depending on what kind of icons you are using.
EDIT:
As you want to use react-icons you need to import the actual icon components rather than just using strings:
import {AiOutlineHome} from "react-icons/ai";
import {FaUserNinja} from "react-icons/fa";
// ....
const icons = [
AiOutlineHome,
FaUserNinja,
// ...
];
And render them:
{movieGenres.map((genre, idx) => {
// must be a capitalized name in order for react to treat it as a component
const Icon = icons[idx];
return (
<li key={genre.id} className="nav-text">
<button
className="genre-btn"
onClick={() => genreSelectionHandler(genre.id)}
>
<Icon />
<span className="nav-item-title">{genre.name}</span>
</button>
</li>
)
})}

Related

How to add a className to single .map item?

I want to add className to a .map item, but only for the one which is clicked.
Tried useState ( {state ? "class" : ""} ) but it added class for all items inside my loop.
I also tried useRef but I learned that useRef does not cause re-render so result didn't update on screen.
example code:
{posts.map((post)=> {
return (
<div className=`PLACE FOR CLASS` key={post.id}>
{post.title}
<button onClick={()=> onclick event to add class}>add</button>
</div>
)
})}
So what is the way to add class only for item which is clicked in this case? ( I think I need to use Key but don't know how )
If you don't want to add this information to the main state (posts) you can create a separate list of selected ids, it'll be something like this (assuming you're using function components)
const [selectedPosts, setSelectedPosts]=useState([]);
...
{posts.map((post)=> {
return (
<div className={selectedPosts.includes(post.id) && "myClass"} key={post.id}>
{post.title}
<button onClick={()=> {setSelectedPosts(s => [...s, post.id])}}>add</button>
</div>
)
})}

Is It possible to change the property from hover to click during a specific break point using media queries in React?

Right now my li tag has a mouseenter and mouseleave effect, but when I shrink my site down to mobile view, I want it to change to onClick instead.
So the code below shows my menu with the dropdown. Whenever I click on the hamburger menu in mobile view, it will trigger the active class on the <ul> and open up the mobile view. Then when I hover over the li tag it will display the dropdown menu.
Instead of it displaying when I hover, I need it to display when I click the ul tag only at the breakpoint of 960px.
<ul className={click ? 'nav-menu active' : 'nav-menu'}>
<li
className='nav-item'
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
<Link to='/' className='nav-links' onClick={() => setClick(false)}>
Home <i class='fas fa-caret-down' />
</Link>
{dropdown && <Dropdown />}
</li>
<li className='nav-item'>
<Link
to='/services'
className='nav-links'
onClick={() => setClick(false)}
>
Services
</Link>
</li>
</ul>
Is that something that is posssible to do? Or would I have to completely redo my code?
It most definitely is. And there are many options of how you could do it, these are some that come to my mind at first:
Option #1:
You could render 2 completely different things based on media query, something like this:
<div className="hidden-xs">
<ol onClick={() => {}}></ol>
</div>
<div className="hidden-lg">
<ol onMouseLeave={() => {}} onMouseEnter={() => {}}></ol>
</div>
Option #2:
You could disable certain functions based on current viewport
const onMouseEnter = () => {
if (window.width > something) {
return;
}
do something otherwise
}
const onClick = () => {
if (window.width < something) {
return;
}
do something otherwise
}
I would recommend taking a look at some of these articles & modules:
Medium post about media queries in React
react-media
react-responsive

Target specific child inside React loop with refs and state onClick

I've got a mobile nav menu with a couple dropdown menus:
/* nav structure */
/about
/portfolio
/services
/service-1
/service-2
contact
etc..
I'm creating this menu dynamically with a loop in my render function.
I've set up a state variable to assign a class to the active dropdown menu. I'm using an onClick event/attribute on the trigger element (a small arrow image) to apply an active class to the respective dropdown menu. Kinda...
const myNavMenu = () => {
const [isSubMenuOpen, toggleSubMenu] = useState(false)
return (
<nav id="mainNav">
<ul>
{navItems.items.map((item, i) => (
<li key={i} className={
(item.child_items) !== null ? 'nav-item has-child-items' : 'nav-item'}>
<Link to={item.slug}>{item.title}</Link>
{(item.child_items === null) ? null :
<>
<img className="arrow down" src={arrowDownImg} onClick={() => toggleSubMenu(!isSubMenuOpen)} />
{printSubMenus(item)}
</>
}
</li>
))}
</ul>
</nav>
)
}
/**
* Prints nav item children two levels deep
* #param {Obj} item a navigation item that has sub items
*/
function printSubMenus(item) {
return (
<ul className={(isSubMenuOpen.current) ? "sub-menu sub-menu-open" : "sub-menu"}>
{item.child_items.map( (childItem, i) => (
<li className="nav-item" key={i}>
<Link to={childItem.slug}>{childItem.title}</Link>
{(childItem.child_items === null) ? null :
<>
<ul className="sub-sub-menu">
{childItem.child_items.map( (subItem, i) => (
<li className="sub-nav-item" key={i}>
<img className="arrow right" src={arrowRight} alt={''} />
<Link to={subItem.slug}>{subItem.title}</Link>
</li>
))}
</ul>
</>
}
</li>
))}
</ul>
)
}
}
*<Link> is a Gatsby helper component that replaces <a> tags.
The issue is that when I click my trigger, the active class is being applied to both (all) sub-menus.
I need to insert some sort of index (or Ref) on each trigger and connect it to their respective dropdowns but I'm not quite sure how to implement this.
I was reading up on useRef() for use inside of function components. I believe that's the tool I need but I'm not sure how to implement it in a loop scenario. I haven't been able to find a good example online yet.
Thanks,
p.s. my functions and loops are pretty convoluted. Very open to refactoring suggestions!
Move sub-menu to a new react component and put all the related logic in there (applying class included).

React State Hook - toggle a class

I'm trying to build a sidebar navigation menu and thought I'd take advantage of the new State hook in React. I've read the docs but can't seem to find an example similar to what I need, which is quite simply to toggle a CSS class on click which will in turn open and close my menu.
Here's what I've tried:
const SidebarMenuItem = ({ component }) => {
const [ menuActive, setMenuState ] = useState(false);
return (
<li className="p-sidebar-menu-item">
menuActive:
{ menuActive }
<button className="p-sidebar-menu-item__link" onClick={() => setMenuState(!menuActive)}>{ component.component }</button>
{ component.children && (
<ul className="p-sidebar-menu">
<li><a href={`/${component.slug}`}>Overview</a></li>
{ component.children.map((subPage, key) => (
<li key={ key }>
<a href={`/${subPage.slug}`}>{ subPage.name }</a>
</li>
))}
</ul>
)}
</li>
)
}
export default SidebarMenuItem;
Any ideas where I'm going wrong?
Thanks
Just make the className dynamic, so instead of setting
<li className="p-sidebar-menu-item">
transform it in a template literal
<li className={`p-sidebar-menu-item`}>
and then add your class conditionally (the "yellow" class in my example)
<li className={`p-sidebar-menu-item ${menuActive ? "yellow" : ""}`}>
Take a look at this CodeSandbox: here I've just added your component and changed the way the className attribute is generated.
If you want to avoid the ternary operator you could use the classnames module and then update your code to
import c from "classnames";
...
...
...
<li className={c("p-sidebar-menu-item", {yellow: menuActive})}>
Another clean solution can be to generate the className string in advance, for example
let classes = "p-sidebar-menu-item";
if(menuActive) {
classes += " yellow";
}
<li className={classes}>
Let me know if you need some more help 😉
I think you just need
const [ menuActive, setMenuState ] = useState(false);
change the name of setState to setMenuState in your code also
Don't forget to use the prevState or you can have a bug.
<button
className="p-sidebar-menu-item__link"
onClick={() => setMenuState((prevMenuActive) => !prevMenuActive)}>
{component.component}
</button>

React router not rendering the component on click of a Array item

I'm creating a component which has array of items and showing them on one page using map function.
Need help on how I can show a detail view of a item by updating route value dynamically based on the item I clicked.
Currently when I click on a item know more though the url change to some wrong value I don't see any change in the page.
Component:
{location[area].map((item, index) =>
<div key={index} className="col-md-4 mt-3">
<div className="card">
<div className="card-body">
<h5 className="card-title">{item.title}</h5>
<p className="card-text">{item.description}</p>
<Link to={'/view/${item.title}'} onClick={() => addDetail(item)} className="btn btn-primary">Know more</Link>
</div>
</div>
</div>
)}
Router:
<Route path='/view/:id' exact component={DetailPage} />
DetailPage Component:
class DetailPage extends Component {
render() {
const selectedItem = this.props.selectedItem;
return(
<div>
<DetailedView selectedItem={this.props.selectedItem} />
</div>
);
}
}
Result Anchor Tag:
<a class="btn btn-primary" href="/view/${item.title}">Know more</a>
Onclick result url:
http://localhost:3000/view/$%7Bitem.title%7D
You need to use backticks for the to prop of your Link components so that template literals will be used and the variable will be inserted into the string.
<Link
to={`/view/${item.title}`}
onClick={() => addDetail(item)}
className="btn btn-primary"
>
Know more
</Link>
Well,As #tholle suggested use template literal.Your route seems fine, just make use of react life cycle method componentWillReceiveProps in the Detail Page Component.Here is the code
class DetailPage extends Component {
componentWillReceiveProps(nextProps){
if(this.props.match.params.id !== nextProps.match.params.id){
//make a call with updated nextProps.match.id or filter the existing data store
with nextProps.match.id
}
}
render() {
const selectedItem = this.props.selectedItem;
return(
<div>
<DetailedView selectedItem={this.props.selectedItem} />
</div>
);
}
}

Resources