Toggles a class on an element when another element is clicked React - reactjs

I have this component in react. I would like whenever I click an a tag with class 'btn' to add/toggle a class 'open' to div with class 'smenu' within the same li element. I implemented it naively like the following, but I am sure there should be another more efficient way. Any tips would be appreciated. Thanks in advance
import React, { useState } from "react";
const AccordioMenu = () => {
const [activeP, setActiveP] = useState(false);
const [activeM, setActiveM] = useState(false);
const toggleActiveP = () => {
setActiveP(!activeP);
};
const toggleActiveM = () => {
setActiveM(!activeM);
};
let btnclassesP = ['smenu']
if(activeP){
btnclassesP.push('open')
}
let btnclassesM = ['smenu']
if(activeM){
btnclassesM.push('open')
}
return (
<div className="middle">
<div className="menu">
<li className="item" id="profile">
<a className='btn' href="#" onClick={toggleActiveP}>
Profile
</a>
<div className={btnclassesP.join(' ')}>
Posts
Pictures
</div>
</li>
<li className="item" id="messages">
<a className="btn" href="#" onClick={toggleActiveM}>
Messages
</a>
<div className={btnclassesM.join(' ')}>
New
Sent
</div>
</li>
<li className="item" id="logout">
<a className="btn" href="#">
Logout
</a>
</li>
</div>
</div>
);
};
export default AccordioMenu;

If you want to simplify this even further you could just use one-state value, that way the component has a single source of truth to share.
Let's have a state that stores an array of identifiers. Each identifier is associated with a different set of links. We'll use "message" and "profile" as the identifiers. Naturally, if there is nothing in the array, then all sub-links should be collapsed.
Then we can use just an event-handler to add/remove the identifier into the state array. Lastly, we can use an inline-style to determine whether that set of links corresponding to the identifier should include the open class.
import React, { useState } from "react";
const AccordioMenu = () => {
const [ selectedItems, setSelectedItems ] = useState([])
//event-handler accepts an identifer-string as an argument
const handleSelect = (identifier) => {
//creates a copy of the original state to avoid state-mutation
const selectedItemsCopy = [...selectedItems]
//check if the idenifier that was passed already exists in the state
if(selectedItemsCopy.includes(identifier)){
//it already exists, which means the menu-links are expanded
const foundIndex = selectedItemsCopy.indexOf(identifier)
//you've clicked it to hide it. so remove the identifier from the state
selectedItemsCopy.splice(foundIndex, 1)
setSelectedItems(selectedItemsCopy)
} else {
//if identifier was not found in state. then add it.
setSelectedItems([...selectedItems, identifier])
}
}
return (
<div className="middle">
<div className="menu">
<li className="item" id="profile">
//set up handler to pass identifier
<a className='btn' href="#" onClick={() => handleSelect("profile")}>
Profile
</a>
<div className={selectedItems.includes("profile") ? "smenu open" : "smenu"}>
Posts
Pictures
</div>
</li>
<li className="item" id="messages">
//set up handler to pass identifier
<a className="btn" href="#" onClick={() => handleSelect("message")}>
Messages
</a>
<div className={selectedItems.includes("messages") ? "smenu open" : "smenu"}>
New
Sent
</div>
</li>
<li className="item" id="logout">
<a className="btn" href="#">
Logout
</a>
</li>
</div>
</div>
);
};
export default AccordioMenu;

Christopher Ngo's answer is a good answer that can work.
I just want to provide a different way to handle the same scenario using useReducer.
If you have multiple states that works in conjunction with each other, it sometimes makes it easy to use a reducer, to "co-locate" the related state changes.
import React, { useReducer } from "react";
const initialState = {
profileClass: 'smenu',
menuClass: 'smenu',
};
// 👇 You can see which state are related and how they should change together.
// And you can also see from the action type what each state is doing.
const menuClassReducer = (state, action) => {
switch (action.type) {
case "mark profile as selected":
return { profileClass: 'smenu open', menuClass: 'smenu' };
case "mark menu as selected":
return { profileClass: 'smenu', menuClass: 'smenu open' };
default:
return state;
}
};
const AccordioMenu = () => {
const [{profileClass, menuClass}, dispatch] = useReducer(menuClassReducer, initialState);
const toggleActiveP = () => {
dispatch({type: 'mark profile as selected'})
};
const toggleActiveM = () => {
dispatch({type: 'mark menu as selected'})
};
return (
<div className="middle">
<div className="menu">
<li className="item" id="profile">
<a className="btn" href="#" onClick={toggleActiveP}>
Profile
</a>
1️⃣ 👇
<div className={profileClass}>
Posts
Pictures
</div>
</li>
<li className="item" id="messages">
<a className="btn" href="#" onClick={toggleActiveM}>
Messages
</a>
2️⃣ 👇
<div className={menuClass}>
New
Sent
</div>
</li>
<li className="item" id="logout">
<a className="btn" href="#">
Logout
</a>
</li>
</div>
</div>
);
};
You can see 1️⃣ & 2️⃣ above that you can simply set the state classes that are returned from the reducer.
Those two classes (menuClass & profileClass) are updated automatically on click as events are dispatched from toggleActiveM & toggleActiveP respectively.
If you plan to do something with the "selected" state, you can simply update the reducer by handling new states and you'd still know how each state are updated together in one place.
import React, { useReducer } from "react";
const initialState = {
isProfileSelected: false,
isMenuSelected: false,
profileClass: "smenu",
menuClass: "smenu"
};
const menuClassReducer = (state, action) => {
switch (action.type) {
case "mark profile as selected":
return {
isProfileSelected: true,
isMenuSelected: false,
profileClass: "smenu open",
menuClass: "smenu"
};
case "mark menu as selected":
return {
isProfileSelected: false,
isMenuSelected: true,
profileClass: "smenu",
menuClass: "smenu open"
};
default:
return state;
}
};
const AccordioMenu = () => {
const [
{ profileClass, menuClass, isProfileSelected, isMenuSelected },
dispatch
] = useReducer(menuClassReducer, initialState);
const toggleActiveP = () => {
dispatch({ type: "mark profile as selected" });
};
const toggleActiveM = () => {
dispatch({ type: "mark menu as selected" });
};
return // do something with newly added states;
};

Related

React array mapping, toggles all drop-downs on click, I want to open the dropdown for the clicked card only

TextQuoteCard
import React, {useRef, useState} from 'react'
import {Link} from "react-router-dom";
import {QuoteCardDropdown} from "../../utils/dropdowns";
export const TextQuoteCard = () => {
const [open, setOpen] = useState(false)
const toggle = () => setOpen(!open)
const [textQuote, setTextQuote] = useState([
{
userId: '123',
userName: 'Tr',
userImageUrl: 'https://qph.fs.quoracdn.net/main-thumb-892821828-200-lrcgeycqieflgsovvoxglqawinbcjhtv.jpeg',
quoteId: 'TQ122',
postDateTime: 'Fri',
quoteAuthorId: '123',
quoteAuthorName: 'Jhon Mart',
quoteCategory: 'Motivational',
quoteType: 'textQuote',
quoteText: 'If there’s no market, about finding market opportunities, or creating opportunities. If there’s no market, then you need to grow one',
quoteImageUrl: 'https://qph.',
bookmarkStatus: 2,
likesCount: 3300,
commentsCount: 123,
overallShareCount: 1203,
fbShareCount: 423,
twtShareCount: 1232,
waShareCount: 1023,
viewCount: 1923
},
{
userId: '124',
userName: 'nr',
userImageUrl: 'https://qph.fi.jpeg',
quoteId: 'TQ123',
postDateTime: 'Fri',
quoteAuthorId: '123',
quoteAuthorName: 'Wall Mart',
quoteCategory: 'Motivational',
quoteType: 'textQuote',
quoteText: 'Best thing to do. ',
quoteImageUrl: '',
bookmarkStatus: 1,
likesCount: 3300,
commentsCount: 123,
overallShareCount: 1203,
fbShareCount: 423,
twtShareCount: 1232,
waShareCount: 1023,
viewCount: 1923
}
])
const handleBookmark = (event) => {
console.log(event)
}
const idGetter = (id) =>{
console.log(id)
}
const test = Object.keys(textQuote).map(item => item)
console.log(test)
return(
<div>
{
textQuote.map((quote) => (
<div className="QuoteCardPrimaryContainer" key={quote.quoteId}>
<div>{quote.userName}</div>
<div className="ddContainer">
<span className="QuoteCardEngagementActionButtonIconContainer">
<span className="QuoteCardEngagementActionButtonIcon"
onClick={() => toggle(!open)}
>
options
</span>
</span>
{open && <QuoteCardDropdown targetLink={quote.quoteId}/>}
</div>
</div>
))
}
</div>
)
}
**
QuoteCardDropdown.js
import React, {useState} from 'react'
import {Link} from "react-router-dom";
import '../../global/assets/css/dropdowns.css'
export const QuoteCardDropdown = (props) => {
const [ddItems, SetDdItems] = useState([
{
ddOptionIcon: 'icon',
ddOptionText: 'Share',
ddOptionTip: 'Tip text goes here',
ddOptionBorder: 'no',
targetId: props.targetId,
targetLink: props.targetLink
},
{
ddOptionIcon: 'icon',
ddOptionText: 'Bookmark',
ddOptionTip: 'Tip text goes here',
ddOptionBorder: 'no',
targetId: props.targetId,
targetLink: props.targetLink
}
])
return (
<div>
<div className="quoteCardDropdownPrimaryContainer">
<div className="quoteCardDropdownPrimaryBody">
<div className="quoteCardDropdownPrimaryBodyInner">
{
ddItems.map(item => (
<Link to=
{
item.ddOptionText === 'Edit this Quote' ?
`${'edit/' + props.targetLink}` :
item.ddOptionText === 'Share' ?
`${'share/' + props.targetLink}` : ''
}
>
<div className="quoteCardDropdownContentWrapper">
<div className="quoteCardDropdownContentItem">
<div className="quoteCardDropdownItem" key={item.ddOptionText}>
{item.ddOptionText}
</div>
</div>
</div>
</Link>
))
}
</div>
</div>
</div>
<div className="quoteCardPointer" data-placement='top'> </div>
</div>
)
}
I have array of objects mapping to which showed multiple card on-page/feed. each card has a dropdown that the user can perform several actions for the clicked card. think of FB feed or any other social media feed card that the user can click to open a dropdown and pick option for the card. I am trying to achieve something similar but the problem is when I click on the button to open the dropdown it toggles all the dropdowns for all the cards instead of opening the dropdown for the clicked card.
Expected Behavior: open the dropdown for the clicked card only.
Change the open to take the id:
const [open, setOpen] = useState() // undefined is nothing open
const toggle = id => setOpen(open === id ? undefined : id) // close if currently open
// the JSX
return(
<div>
{textQuote.map((quote) => (
<div className="QuoteCardPrimaryContainer" key={quote.quoteId}>
<div>{quote.userName}</div>
<div className="ddContainer">
<span className="QuoteCardEngagementActionButtonIconContainer">
<span className="QuoteCardEngagementActionButtonIcon"
onClick={() => toggle(quote.quoteId)}>
options
</span>
</span>
{open === quote.quoteId && <QuoteCardDropdown targetLink={quote.quoteId}/>}
</div>
</div>
))}
</div>
)
Yes, you are trying to control all dropdowns using a single state variable open.
{open && <QuoteCardDropdown targetLink={quote.quoteId}/>}
When you click on any dropdown it will toggles open and then all dropdowns will open because that single variable controls all of them.
Instead, what you can do is maintain a separate state variable for each dropdown.
I have an example to maintain separate state variable for dropdown-
toggle = (index) => {
this.setState(prevState => {
[`open+${index}`]: !prevState[`open${index}`]
}
}
This way you can keep track of/or toggles open for particular dropdown you just need to change below code -
{
textQuote.map((quote, index) => ( //add 2nd parameter as index
<div className="QuoteCardPrimaryContainer" key={quote.quoteId}>
<div>{quote.userName}</div>
<div className="ddContainer">
<span className="QuoteCardEngagementActionButtonIconContainer">
<span className="QuoteCardEngagementActionButtonIcon"
onClick={() => this.toggle(index)}
>
options
</span>
</span>
{ this.state[`open${index}`] && <QuoteCardDropdown targetLink={quote.quoteId}/>}
</div>
</div>
</div>
))
}
Note - Unfortunately I am not aware of handling state as dynamically inside the function component, but I have given you the exact same use case using class component.
Use an object indexed by quote ID to track a card's open status by the quote ID. I copied only the relevant code, make sure this has all of the other code you need:
export const TextQuoteCard = () => {
const [openById, setOpenById] = useState({});
const toggle = (quoteId) => {
setOpenById((currOpenById) => {
const nextOpenById = { ...currOpenById };
if (currOpenById[quoteId]) {
delete nextOpenById;
} else {
nextOpenById[quoteId] = true;
}
return nextOpenById
})
}
// ...removed code not relevant to example
return (
<div>
{textQuote.map((quote) => (
<div className="QuoteCardPrimaryContainer" key={quote.quoteId}>
<div>{quote.userName}</div>
<div className="ddContainer">
<span className="QuoteCardEngagementActionButtonIconContainer">
<span
className="QuoteCardEngagementActionButtonIcon"
onClick={() => toggle(quote.quoteId)}
>
options
</span>
</span>
{openById[quote.quoteId] && <QuoteCardDropdown targetLink={quote.quoteId} />}
</div>
</div>
))}
</div>
);
};

react hooks : Uncaught TypeError: data.posts.map is not a function

I'm trying to make a list of jokes but it is saying " TypeError: data.posts.map is not a function ".
I used before same code but different API and it was working.
In console it is showing those posts but on app i get error saying that " data.posts.map "
Can anyone help me?
Thanks
This is my code:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const PostArea = () => {
const [like, showLike] = useState(false);
const [color, setColor] = useState('#000');
const likePrint = () => {
showLike(!like);
}
const [data, setPost] = useState({ posts: [] });
useEffect(() => {
const fetchData = async () => {
const result = await axios('https://sv443.net/jokeapi/v2/joke/Any?blacklistFlags=nsfw,religious,political,racist,sexist&idRange=0-40');
console.log({ posts: result.data });
setPost({ posts: result.data });
};
fetchData();
}, []);
return (
<div>
{data.posts && data.posts.map(({ id, setup, delivery, category }) => {
return (
<div key={id} className="post">
<div className="header">
<a>
<img src="https://unsplash.it/40" alt=" title" />
<div>
<span className="detail">Category :{category}</span>
</div>
</a>
</div>
<div className="content">
<p>
{setup}
</p>
<p>
{delivery}
</p>
</div>
<div className="footer">
<ul>
<li>
<a style={{ color: color }} onClick={() => { likePrint(!like); setColor('#2274a5') }}>
<i className="fas fa-heart"></i>
</a>
</li>
<li>
<a>
<i className="fas fa-comment"></i>
</a>
</li>
<li>
<a>
<i className="fas fa-share-alt-square"></i>
</a>
</li>
</ul>
</div>
</div>
);
})}
</div>
);
}
export default PostArea;
Error : Uncaught TypeError: data.posts.map is not a function
I was looking at the response from the https://sv443.net/jokeapi/v2/joke/Any?blacklistFlags=nsfw,religious,political,racist,sexist&idRange=0-40, and it seems like it is returning an object, rather an array, which is the expected type of data.post.
I guess you would want to include this object as part of the post array?
const fetchData = async () => {
const result = await axios('https://sv443.net/jokeapi/v2/joke/Any?blacklistFlags=nsfw,religious,political,racist,sexist&idRange=0-40');
setPost({ posts: [result.data] });
};

react redux two components

I have a component that will display the products which is coming from backend and a component that receives the products to filter but I have doubt that receive by redux my product list.
should i put for my filters component receive?
or should return the same as I get in my product component?
or should I create an action to filter what I need already?
my home:
return (
<div className="container">
<h1>Shopping</h1>
<hr />
<div className="row">
<div className="col-md-3"><Filters/></div>
<div className="col-md-9"><Products/></div>
</div>
</div>
)
my component products:
import React, { Component } from 'react'
import {connect} from 'react-redux'
import { ProductsFetchData } from '../../store/actions/productsFetch';
import util from '../../util';
class HomeProducts extends Component {
componentDidMount() {
this.props.fetchData('/products');
}
render() {
const productItems = this.props.products.map( product => (
<div className="col-md-4 pt-4 pl-2">
<div className = "thumbnail text-center">
<a href={`#${product.id}`} onClick={(e)=>this.props.handleAddToCard(e,product)}>
<p>
{product.name}
</p>
</a>
</div>
<b>{util.formatCurrency(product.price)}</b>
<button className="btn btn-primary" onClick={(e)=>this.props.handleAddToCard(e,product)}>Add to Cart</button>
</div>
)
)
return (
<div className="container">
<div className="row">
{productItems}
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
products: state.Products,
hasErrored: state.ProductsHasErrored,
isLoading: state.ProductsIsLoading
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: () => dispatch(ProductsFetchData())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeProducts);
my components filter
import React, { Component } from 'react';
import './style.css'
class FilterHome extends Component {
render() {
return (
<>
<div className="row">
<button className="filterbt btn btn-danger btn-rounded">Filters</button>
<div className=" mt-4 d-flex flex-column">
<p className="textCategory">CATEGORY</p>
<div className="category d-flex flex-column">
<p>Stat Trak</p>
<p>Normal</p>
</div>
<p className="textCategory">EXTERIOR</p>
<div className="category d-flex flex-column">
<p>Factory New ()</p>
<p>Minimal Wear ()</p>
<p>Field Tested ()</p>
<p>Well Worn ()</p>
<p>Battle Scarred ()</p>
</div>
</div>
</div>
</>
)
}
}
export default FilterHome;
1.redux-state: this is the registering point for all your api responses(all the data from back-end is stored here as prestine and is available as props to any container when you mapStateToProps).
2.local-state: this lives only in your container and all it's child components.
3.filter:
a)from server:
you make a request to the server and get a response of filtered products. this
is more practical.
eg: you have /products?page=1 and you want to search it by some category, let's
say by a specific company. with the data you have at the moment(page 1), you
may have only let's say 1 or even no product relevant to that company, but in fact there are n-numbers of products of the same company available at the server. so this can only be assumed as the
most practical way.
b)filtering from the local-state:
if this is what your'e trying to achieve,
1. you need only one container, HomeProducts
2. make ProductItems as a component. wer'e gonna reuse this component to render both.
**you wrote your filter as an independent container. but those filter functionality should be availabe inside the home page itself. isn't it, i mean you're filtering from the home page itself not from another page. if so, add it to the home page itself
1.HomePage
import React, { Component } from 'react'
import {connect} from 'react-redux'
import { ProductsFetchData } from '../../store/actions/productsFetch';
import util from '../../util';
import ProductItems from '<<..your relative path>>'
import FilterHome from '<<..your relative path>>'
class HomeProducts extends Component {
constructor(props) {
super(props)
this.state = {
productList: null,
}
}
componentDidMount() {
//this.props.fetchData('/products');
this.props.fetchData('page=1');
}
componentWillReceiveProps(nextProps) {
const { productList } = this.state
const { products } = this.props
// this only handles when local state is empty. add your logic here..
!productList && this.setState(prevState => ({
...prevState,
productList: products,
}))
}
handleFilter = (category) => {
// if it's an api call
const { fetchData } = this.props
fetchData(`page=1&category=${category}`)
//or you simply want to filter this local state(not-recommended)
const { products } = this.props
const productsList = [...products].filter(item => item.category === category)
this.setState(prevState => ({
...prevState,
productsList,
}))
}
render() {
const { productList } = this.state
const { handleFilter } = this
return (
<div className="container">
<FilterHome {...{ handleFilter }} />
<ProductItems {...{ productsList }} />
</div>
)
}
}
const mapStateToProps = (state) => {
return {
products: state.Products,
hasErrored: state.ProductsHasErrored,
isLoading: state.ProductsIsLoading
};
};
//it may not suit depends on your api, but you get the idea..
const mapDispatchToProps = (dispatch) => {
return {
// fetchData: () => dispatch(ProductsFetchData())
fetchData: params => dispatch(ProductsFetchData(`/products?${params}`))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeProducts);
2.ProductItems
import React from 'react'
const ProductItem = ({ product }) => {
return (
<div className="col-md-4 pt-4 pl-2">
<div className = "thumbnail text-center">
<a href={`#${product.id}`} onClick={(e)=>this.props.handleAddToCard(e,product)}>
<p>
{product.name}
</p>
</a>
</div>
<b>{util.formatCurrency(product.price)}</b>
<button className="btn btn-primary" onClick={(e)=>this.props.handleAddToCard(e,product)}>Add to Cart</button>
</div>
)
}
const ProductItems = ({ productList }) => {
return (
<div className="row">
{productList && productList.map(product => <ProductItem key={product.id} {...{ product }} />)}
</div>
)
}
export default ProductItems
3.FilterHome
import React from 'react'
const FilterHome = ({ handleFilter }) => {
return (
<div className="row">
<button className="filterbt btn btn-danger btn-rounded">Filters</button>
<div className=" mt-4 d-flex flex-column">
<p className="textCategory">CATEGORY</p>
<div className="category d-flex flex-column">
<a href="" className="text-decoration-none" onClick={() => handleFilter('stat_trak')}><p>Stat Trak</p></a>
<a href="" className="text-decoration-none" onClick={() => handleFilter('normal')}><p>Normal</p></a>
</div>
<p className="textCategory">EXTERIOR</p>
<div className="category d-flex flex-column">
<a href="" className="text-decoration-none" onClick={() => handleFilter('factory_new')}><p>Factory New ()</p></a>
<a href="" className="text-decoration-none" onClick={() => handleFilter('minimal_wear')}><p>Minimal Wear ()</p></a>
<a href="" className="text-decoration-none" onClick={() => handleFilter('field_tested')}><p>Field Tested ()</p></a>
<a href="" className="text-decoration-none" onClick={() => handleFilter('well_worn')}><p>Well Worn ()</p></a>
<a href="" className="text-decoration-none" onClick={() => handleFilter('battle_scarred')}><p>Battle Scarred ()</p></a>
</div>
</div>
</div>
)
}
export default FilterHome
i roughly re-wrote it, may contain bugs..
first add it to the local state and employ a call back to the filter component..
handleFilter = (category) => {
const { Products } = this.state
const products = {...Products}
//or depends on the type, so it wont mutate the real data>> const products = [...Products]
return products.filter(item => item.category === category)
}
this is what is understood from your comment. is that it?

React Hook select multiple items with icons

I am trying to implement a popover with many items where a user can multi select them. When a user clicks a item, a font-awesome icon is shown to the right.
A user is able to select multiple items and an icon is shown on the right to show it has been checked. This icon toggles when clicking. My problem is my event handler is tied to all the items and whenever I click one, all gets checked.
I am new to hook and react. I am also trying to assign the Id of the selected item in an array. It won't append.
const SettingsComponent = (props) => {
const urlStofTyper = stofTyperUrl;
const stofTyper = [];
const [isPopoverOpen, setPopoverOpen] = useState(false);
const [isItemChecked, setItemChecked] = useState(false);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(null);
const [stoftype, setStoftyper] = useState({ DataList: [] });
const toggle = () => setPopoverOpen(!isPopoverOpen);
const sectionClicked = (e) => {
setItemChecked(!isItemChecked);
let secId = e.target.parentNode.getAttribute("data-section");
if (!isItemChecked) {
stofTyper.push(secId);
} else {
stofTyper.filter((sec) => sec == secId);
}
};
useEffect(() => {
fetchStoftyper({ setError, setLoading, setStoftyper });
}, []);
const fetchStoftyper = async ({ setError, setLoading, setStoftyper }) => {
try {
setLoading(true);
const response = await Axios(urlStofTyper);
const allStofs = response.data;
setLoading(false);
setStoftyper(allStofs);
} catch (error) {
setLoading(false);
setError(error);
}
};
return (
<React.Fragment>
<div className='list-header-icons__fontawesome-icon' id='PopoverClick'>
<FontAwesomeIcon icon={faCog} />
</div>
<Popover
isOpen={isPopoverOpen}
placement='bottom'
toggle={toggle}
target='PopoverClick'>
<PopoverHeader>formatter</PopoverHeader>
<div className='popover-body'>
<ul className='individual-col--my-dropdown-menu-settings'>
{stoftype.DataList.map((item) => (
<li key={item.Id} className='section-items'>
<a
onClick={sectionClicked}
className='dropdown-item'
data-section={item.Sections[0].SectionId}
data-format={
item.Formats.length > 0
? item.Formats[0].FormatId
: ""
}
aria-selected='false'>
<span className='formatter-name'>
{item.Name}
</span>
{isItemChecked && (
<span className='formatter-check-icon'>
<FontAwesomeIcon icon={faCheck} size='lg' />
</span>
)}
</a>
</li>
))}
</ul>
</div>
</Popover>
</React.Fragment>
);
Right now you are using one boolean variable to check if the icon should be displayed, it will not work because every item in your DataList should have it's own individual indicator.
One of the possible solution is to use new Map() for this purposes and store item.id as and index and true/false as a value, so your selected state will be something like this:
Map(3) {1 => true, 2 => true, 3 => false}
After that you can check if you should display your icon as follow:
!!selected.get(item.id)
It will return true if the value in your HashTable is true, and false if it's false or doesn't exist at all. That should be enough to implement the feature you asked for.
For the real example you can check flatlist-selectable section from official Facebook docs They show how to achieve multi-selection using this technique. Hope it helps.
Finally, I have a solution for my own question. Even though, I did not need above solution but I though it would be a good practice to try solve it. Popover is not relevant as that was used only as a wrapper. The below solution can still be placed in a Popover.
CodeSandBox link: Demo here using Class component, I will try to re-write this using hooks as soon as possible.
This solution dependency is Bootstrap 4 and Fontawesome.
import React, { Component } from "react";
import Cars from "./DataSeed";
class DropDownWithSelect extends Component {
constructor(props) {
super(props);
this.toggleDropdown = this.toggleDropdown.bind(this);
this.state = {
isDropDownOpen: false,
idsOfItemClicked: []
};
}
toggleDropdown = evt => {
this.setState({
isDropDownOpen: !this.state.isDropDownOpen
});
};
toggleItemClicked = id => {
this.setState(state => {
const idsOfItemClicked = state.idsOfItemClicked.includes(id)
? state.idsOfItemClicked.filter(x => x !== id)
: [...state.idsOfItemClicked, id];
return {
idsOfItemClicked
};
});
};
render() {
return (
<div className="dropdown">
<button
onClick={this.toggleDropdown}
className="btn btn-secondary dropdown-toggle"
type="button"
id="dropdownMenuButton"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Dropdown button
</button>
{this.state.isDropDownOpen && (
<div
className="dropdown-menu show"
aria-labelledby="dropdownMenuButton"
>
{Cars.map(x => {
return (
<div
key={x.id}
onClick={() => this.toggleItemClicked(x.id)}
className="dropdown-item d-inline-flex justify-content-between"
>
<span className="d-flex"> {x.name} </span>
<span className="d-flex align-self-center">
{this.state.idsOfItemClicked.includes(x.id) && (
<i className="fa fa-times" aria-hidden="true" />
)}
</span>
</div>
);
})}
</div>
)}
</div>
);
}
}
export default DropDownWithSelect;
Data i.e., ./DataSeed
const cars = [
{
id: 1,
name: "BMW",
cost: 450000
},
{
id: 2,
name: "Audi",
cost: 430000
},
{
id: 3,
name: "Mercedes",
cost: 430000
}
];
export default cars;

ReactJS , how to Switching the current Language Flag according to the user choose in changeLanguage()

i am a new on React and i have a React component as in the next code and i cannot find a way to make the currentLanFlag img src to be dynamic and follow the user choose in the drop down languages menu ......
What i want is : when the user click on the anchor tag German, the img with CLass currentLanFlag to be a German Flag, same for English and the others Languages .
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
import { translate, Trans } from 'react-i18next';
import de from '../../../assets/img/de.png';
import en from '../../../assets/img/en.png';
import fr from '../../../assets/img/fr.png';
import pt from '../../../assets/img/pt.png';
import ar from '../../../assets/img/ar.GIF';
import '../../../assets/FontAwesome/css/font-awesome.min.css';
import './topNavComponenets.css';
const { version: appVersion } = require('../../../../package.json');
class TopNavComponenets extends Component {
constructor (props) {
super()
this.state = {
firstName: '',
lastName: ''
}
this.logout = this.logout.bind(this)
axios.get('api/auth/en/profile',{ withCredentials: true })
.then(response => this.setState({
firstName: response.data.firstName,
lastName: response.data.lastName,
}));
}
logout () {
axios.get('api/auth/en/logout',{ withCredentials: true })
}
render () {
const { i18n } = this.props;
const changeLanguage = (lng) => {
i18n.changeLanguage(lng);;
};
let currentLanFlag = en;
return (
<div className="topNavComponenets">
<div className='infoContainer row'>
< div className="col-12 text-right">
<div className="userCont col-4">
<Link to="/user" ><i className="fa fa-user" title={"My Profile"}></i></Link>
<p className="infos">
{this.state.firstName} {this.state.lastName}
</p>
</div>
<div className='version col-4'>
<div className="dropdown show">
<a className="dropdown-toggle" href="" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img
className="currentLanFlag"
src={ currentLanFlag }
alt="Language Flag"/>
</a>
<div className="dropdown-menu" aria-labelledby="dropdownMenuLink">
<a onClick={() => changeLanguage('de')} className="dropdown-item"><img className="flag" src={de} alt=""/><Trans>German</Trans></a>
<a onClick={() => changeLanguage('en')} className="dropdown-item"><img className="flag" src={en} alt=""/><Trans>English</Trans></a>
<a onClick={() => changeLanguage('fr')} className="dropdown-item"><img className="flag" src={fr} alt=""/><Trans>French</Trans></a>
<a onClick={() => changeLanguage('pt')} className="dropdown-item"><img className="flag" src={pt} alt=""/><Trans>Portugues</Trans></a>
<a onClick={() => changeLanguage('ar')} className="dropdown-item"><img className="flag" src={ar} alt=""/><Trans>Arabic</Trans></a>
</div>
</div>
<p title={"CMS Version"}>v.{appVersion}</p>
</div>
<div className='buttonContainer col-4'>
<a href="/login"> <span onClick={this.logout}>
<i className="fa fa-power-off" title={"Logout"}></i>
</span></a>
</div>
</div>
</div>
</div>
)}}
export default translate('translations')(TopNavComponenets);
Personally, I would set currentLanFlag in your state.
this.state = {
...
currentLanFlag: 'en'
};
Then change your image tag to be something along the lines of
<img
className="currentLanFlag"
src={ this.state.currentLanFlag }
alt="Language Flag"/>
Your on click should then change the state of this.state.currentLanFlag.
Something along the lines of
// A function
changeLan = (event) {
this.setState({
currentLanFlag: event.target.data
});
changeLanguage(event.target.data);
}
// in your return
<a data='de' onClick={this.changeLan}><img className="flag" src={de} alt=""/><Trans>German</Trans></a>
this.setState will tell react "something changed, rerender" at that point your language flag will be updated to the new flag.
Please note, the above is not tested.
You can find nice documentation here
https://reactjs.org/docs/state-and-lifecycle.html
https://reactjs.org/docs/handling-events.html
Just you need make some minor changes. This is for if you need to switch between two different language.
import en from '../../../assets/img/en.png';
import de from '../../../assets/img/de.png';
1. set the current flag in your state
state = {
currLangFlag: 'en'
}
2. Replace your current event handler with this one
const changeLan = (lng) => {
this.setState({ currLangFlag: lng })
i18n.changeLanguage(lng)
}
3. Use the ternary operator you can switch the flag url after state update
{currLangFlag === 'en' ? <img src={en} alt={currLangFlag} /> : <img src={de} alt={currLangFlag} />}
4. Call the event handler
<NavLink onClick={() => changeLan('en')} to={this.props.location}>
<img className="flag" src={en} alt=""/><Trans>English</Trans>
</NavLink >
<NavLink onClick={() => changeLan('de')} to={this.props.location}>
<img className="flag" src={de} alt=""/><Trans>German</Trans>
</NavLink >

Resources