How can I do this with sass and react? - reactjs

I have a component that render some buttons in the page.
To style the buttons I am using .scss so the classes looks like this:
className={styles.tag}
and the corresponding scss is like this:
.tagsContainer {
display: flex;
flex-flow: row;
flex-wrap: wrap;
justify-content: center;
align-items: center;
.tag {
display: block;
background-color: #f4f4f4;
i {
margin-left: 50px;
font-size: 1.4em;
}
}
I was lookin to this codePen that does what I want but is using normal css: Adding and removing classes
How can I adapt that code to use it with scss nad more exactly with this syntax: {styles.tag}?
Regards
Americo
THIS IS AN UPDATE:
I tested this code:
<div onClick={(e) => { e.stopPropagation(); this.collectTags(tag); }}>
<p className={`${this.state.addClass == true ? styles.tag : ""}`}
># {tag.title} <i className="ms-Icon ms-Icon--CirclePlus"></i></p>
</div>
look closely to the p-tag. this code is changing the style onClick but the problem is that is changing all the p-tags in the compoenent and not only the one that was clicked.

Is that what you want?
class App extends React.Component {
state = {
addButtonClass: false
};
toggleClass = () => {
this.setState({
addButtonClass: !this.state.addButtonClass
});
};
render() {
const { addButtonClass } = this.state;
return (
<div className="App">
<button
className={`button ${addButtonClass ? " button--red" : ""}`}
onClick={this.toggleClass}
>Click me </button>
</div>
);
}
}
style.scss
.button {
font-size: 24px;
color: white;
background-color: green;
&--red {
background-color: red;
}
}

All the style reference does is return a string className, so you can manipulate it just like you would any other string. Here's a generic example:
// this can be any string, even an empty string to start
let testCircleClass = `${cssModule.infoCircle}`;
if(test > 0) {
coursesCircleClass += ` ${cssModule.hasContent}`;
}
So you'd probably need something like:
An event handler for the click event.
A state to handle if this event has been processed.
To then tie the styles.tag to this state, once activated.

Related

How can i display the quantity of the items in my shopping cart on every pages of my react/nextjs application

I've been having this particular problem with my nextjs eCommerce application for more than one week now and it just seems unsolvable for me and I must submit this project by this week as it is a project I am having from a web dev BootCamp that I am currently taking.
I have a shopping cart 🛒 which has the information of all the products added to it and on my header, I have a shopping cart 🛒 logo that should(required) display the amount of the items in my shopping cart on every page on the application. I could get the quantity to display but only when I am viewing the shopping cart, Once I click away from the shopping cart page then the quantity will be undefined. After doing a rigorous search on the internet I learned about the lifting state up in react and I have tried everything I can to pass this information through from their common parents which in my case is the _app.js and the same problem still persist. I read about the react context but the BootCamp is not allowing us to do this with the react context which has left me completely stuck on this. Please I am reaching out to anybody that can help me out, I don't know if I completely got everything wrong or if I just don't understand this react props concept or the particular thing that I am doing wrong in this regard.
I am sharing a screenshot here with the cart and my file structure. The code is already too much to copy and paste here so I am sharing the complete application link on code sandbox and anybody can take a look at the complete code there. I will appreciate any help I can get, please. Thanks in advance.
Here is the Codesandbox demo
please if there is any more information that I would need to provide, please just ask. Thanks a lot for any help.
Here is the code from my cart component where I am supposed to be updating the cartQuantity state variable.
import { css } from '#emotion/react';
import Head from 'next/head';
import Link from 'next/link';
// import image from 'next/image';
import { useEffect, useState } from 'react';
// import Image from 'next/image';
import Layout from '../../../components/Layout.js';
import { getParsedCookie, setParsedCookie } from '../../../util/cookies';
import { calculateTotalPrice } from '../../../util/priceChecker';
const cartStyles = css`
max-width: 100vw;
min-height: 100vh;
background: #f2f2ff;
padding-bottom: 1rem;
.heading {
text-align: center;
}
.topInfoWrap {
width: 60%;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
a {
font-size: 1rem;
border: none;
cursor: pointer;
background: transparent;
text-decoration: none;
}
div:last-of-type {
display: flex;
justify-content: space-around;
gap: 5px;
button {
font-size: 1.1rem;
padding: 0 1rem;
background: #191959;
color: white;
}
button:last-of-type {
background: #353434;
}
}
}
.cartDisplayWrapper {
width: 70%;
margin: 0 auto;
background: #e1d1f5;
border-radius: 10px;
padding: 0.5rem;
.itemsCount {
margin-left: 0.5rem;
}
.tableHeaders {
background: #e1d1f5;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 0.5rem;
border-top: 2px solid black;
border-bottom: 2px solid black;
margin: 0;
h2:nth-child(2) {
width: 250px;
text-align: right;
}
}
.tableContentWrapper {
width: 100%;
ul {
width: 100%;
padding: 0;
li {
list-style: none;
.tableDisplayFlex {
width: 100%;
padding: 0 0.5rem;
display: flex;
justify-content: space-between;
align-items: center;
background: #e1d1f5;
border-bottom: 2px solid black;
.itemsBox {
display: flex;
justify-content: space-around;
align-items: flex-start;
padding: 0;
max-width: 300px;
gap: 5px;
img {
width: 60px;
}
}
.colorBox {
margin-left: 1rem;
}
.quantityBox {
width: 110px;
height: 40px;
background: #fefefe;
display: flex;
justify-content: space-evenly;
align-items: center;
font-size: 1.2rem;
border-radius: 10rem;
margin-right: -1.5rem;
p {
font-weight: bolder;
width: 30px;
text-align: center;
}
.deleteFromCart {
background: #fb2e86;
}
button {
font-size: 1.2rem;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
flex-basis: 35px;
height: 35px;
background: #353434;
color: white;
border: none;
border-radius: 50%;
span {
font-weight: bolder;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
display: inline;
border-radius: inherit;
}
}
}
.priceBox {
background: #353434;
color: white;
width: 110px;
display: inline-flex;
justify-content: center;
align-items: center;
padding: 0;
border-radius: 8px;
}
}
}
}
}
}
.totalPriceDisplayBox {
padding: 0 0.5rem;
width: 100%auto;
.itemsPrice,
.taxPrice,
.shippingPrice,
.totalPrice {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
p {
font-size: 1.2rem;
}
strong {
font-size: 1.2rem;
}
}
.totalPrice strong {
font-size: 1.5rem;
height: 60px;
display: flex;
justify-content: center;
align-items: center;
padding: 0 0.5rem;
background: #353434;
color: white;
border-radius: 8px;
}
.totalPrice {
font-weight: bold;
}
}
`;
export default function Cart(props) {
console.log('props from the cart page: ', props);
// getting all the cookie objects back from the browser
const shoppingCartCookies = getParsedCookie('cartInside') || [];
// setting state variables for all prices related codes!
const [productsPrice, setProductsPrice] = useState(0);
// setting the quantities
// i must check this code and how it works is is still equals zero when i log it in
const [itemQuantity, setItemQuantity] = useState(0);
// const [cartInside, setCartInside] = useState(
// getParsedCookie('cartInside') || [],
// console.log('This is the item Q State', itemQuantity);
// );
// const [shoppingCartQuantity, setShoppingCartQuantity] = useState(0);
// finding the product id that matches the cookie object id that i fetched from the browser
const foundProductsWithCookie = shoppingCartCookies.map(
(individualCookieObj) => {
const itemAndCookieMatched = props.products.find((product) => {
return Number(product.id) === individualCookieObj.id;
});
return itemAndCookieMatched;
},
);
// All the items in the cart
console.log('ITEMS AMOUNT IN CART');
console.log(foundProductsWithCookie);
useEffect(() => {
props.setCartQuantity(foundProductsWithCookie.length);
setProductsPrice(calculateTotalPrice(foundProductsWithCookie));
}, [foundProductsWithCookie]);
// calculating the tax Price and shipping price and then add all together as the total price.
const taxPrice = productsPrice * 0.13;
const shippingPrice = productsPrice > 2000 ? 0 : 50;
const totalPrice = Number(productsPrice) + (taxPrice + shippingPrice);
function makeQuantityIncrement(singleProductObj) {
// getting the current quantity back from the cookie
const currentCookieQuantity = getParsedCookie('cartInside') || [];
// the found product with cookie here is an array of all the product objects that is added to the cart
currentCookieQuantity.find((singleCookieObj) => {
// i looped over it to find a match for the product in the cart and the corresponding cookie.
if (Number(singleProductObj.id) === singleCookieObj.id) {
// i didn't have to return anything so i just used the value true or false to increase the
// quantity of the item in the cart when true and nothing when not.
console.log('New value should start here');
const newQuantityValue = (singleProductObj.quantity += 1);
// making the cookie quantity the same as the quantity of the items in the cart
singleCookieObj.quantityCount = newQuantityValue;
setItemQuantity(newQuantityValue);
}
});
console.log('Checking new cookie Value');
console.log(currentCookieQuantity);
// setting the new cookie quantity to reflect in the browser
setParsedCookie('cartInside', currentCookieQuantity);
}
// function that decreases the quantity
function makeQuantityDecrement(singleProductObj) {
// getting the current quantity back from the cookie
const currentCookieQuantity = getParsedCookie('cartInside') || [];
// the found product with cookie here is an array of all the product objects that is added to the cart
currentCookieQuantity.find((singleCookieObj) => {
if (Number(singleProductObj.id) === singleCookieObj.id) {
console.log('New value should start here');
const newQuantityValue = (singleProductObj.quantity -= 1);
// making the cookie quantity the same as the quantity of the items in the cart
singleCookieObj.quantityCount = newQuantityValue;
setItemQuantity(newQuantityValue);
}
});
console.log('Checking new cookie Value');
console.log(currentCookieQuantity);
// setting the new cookie quantity to reflect in the browser
setParsedCookie('cartInside', currentCookieQuantity);
}
// Function that handles decrements limit
function stopDecrement() {
console.log('can not be lower than one');
}
// #############################
// function for deleting item from cart
function itemDeletionHandler(singleProductObj) {
const currentCookie = getParsedCookie('cartInside') || [];
const isItemInCart = currentCookie.some((cookieObj) => {
return cookieObj.id === Number(singleProductObj.id);
});
let newCookies;
if (isItemInCart) {
newCookies = currentCookie.filter((cookieObj) => {
return cookieObj.id !== Number(singleProductObj.id);
});
setParsedCookie('cartInside', newCookies);
}
}
// #######################################
return (
<Layout
catQuantity={props.catQuantity}
setCartQuantity={props.setCartQuantity}
>
{' '}
{/* Check please,, trying to pass props through the layout component */}
<section css={cartStyles}>
<Head>
<title>Cart Section</title>
<meta
name="description"
content="The Best Next eCommerce shop around here"
/>
</Head>
<h1 className="heading">SHOPPING CART</h1>
<div className="topInfoWrap">
<div>
<Link href="/products">
<a className="backToShopping">BACK TO SHOPPING</a>
{/* Check here for duplicate */}
</Link>
</div>
<div>
<button>PayPal Checkout</button>
<p>OR</p>
<button>PROCEED WITH YOUR ORDER</button>
</div>
</div>
<div className="cartDisplayWrapper">
<div>
<h3 className="itemsCount">
{foundProductsWithCookie.length !== 0
? `ITEMS ADDED TO YOUR SHOPPING CART (${foundProductsWithCookie.length})`
: `Your Cart is Empty`}
</h3>
</div>
<div className="tableHeaders">
<h2>ITEMS</h2>
<h2>COLOR</h2>
<h2>Quantity</h2>
<h2>PRICE</h2>
</div>
{/* Return a table for the cart items and prices and quantity and others */}
<div className="tableContentWrapper">
<ul>
{foundProductsWithCookie.map((itemWithCookie) => {
return (
<li key={`item-li- ${itemWithCookie.id}`}>
<div className="tableDisplayFlex">
{/* First row */}
<div className="itemsBox">
<div>
{/* <image
src={`/images/public/${itemWithCookie.id}.jpg`}
alt={itemWithCookie.title}
width={400}
height={500}
/> */}
<img
src={itemWithCookie.image}
alt={itemWithCookie.title}
/>
</div>
<div>
<h3>{itemWithCookie.name}</h3>
<p>{itemWithCookie.title}</p>
</div>
</div>
{/* second row */}
<div className="colorBox">
<p>COLOR COMES HERE</p>
</div>
{/* Third row */}
<div className="quantityBox">
{itemWithCookie.quantity <= 1 ? (
<button
className="deleteFromCart"
value={itemWithCookie.id}
onClick={(event) => {
console.log(
'clicked' + event.currentTarget.value,
);
itemDeletionHandler(itemWithCookie);
}}
>
<span>×</span>
</button>
) : (
<button
value={itemWithCookie.id}
onClick={(event) => {
console.log(
'clicked' + event.currentTarget.value,
);
itemWithCookie.quantity > 1
? makeQuantityDecrement(itemWithCookie)
: stopDecrement();
}}
>
<span>−</span>
</button>
)}
<p>{itemWithCookie.quantity}</p>
<button
value={itemWithCookie.id}
onClick={(event) => {
console.log('clicked' + event.currentTarget.value);
makeQuantityIncrement(itemWithCookie);
}}
>
<span>+</span>
</button>
</div>
{/* Fourth Row */}
<div className="priceBox">
<h2>{`€ ${itemWithCookie.price}`}</h2>
</div>
</div>
</li>
);
})}
<div className="totalPriceDisplayBox">
<div className="itemsPrice">
<p>Items Price</p>
<strong>{`€ ${productsPrice}`}</strong>
</div>
<div className="taxPrice">
<p>Tax Price</p>
<strong>{`€ ${taxPrice.toFixed(2)}`}</strong>
</div>
<div className="shippingPrice">
<p>Shipping Price</p>
<strong>{`€ ${shippingPrice.toFixed(2)}`}</strong>
</div>
<div className="totalPrice">
<p>Total Price</p>
<strong>{`€ ${totalPrice.toFixed(2)}`}</strong>
</div>
</div>
</ul>
</div>
</div>
</section>
</Layout>
);
}
// Server side code via getServerSideProps
export async function getServerSideProps(context) {
// getting the products from the dataBase
const { DUUMMY_PRODUCTS } = await import('../../../util/database');
// i get information back from the cookie in the browser which should be the cookies that the user
// has created as he or she clicked the add to cart button which means that the information contained in this cookies
// should have a matching product from the database
const cookies = context.req.cookies.cartInside || '[]';
const cartInside = JSON.parse(cookies);
// mapping through the products array and getting the match between the information from the cookies and the matching products.
const itemInsideCart = DUUMMY_PRODUCTS.map((product) => {
const isTheItemInCart = cartInside.some((productCookieObj) => {
return Number(product.id) === productCookieObj.id;
});
const userObj = cartInside.find((cookieOBJ) => {
return cookieOBJ.id === Number(product.id);
});
if (isTheItemInCart) {
return {
...product,
cartInside: isTheItemInCart,
// if the item is in the cart then the quantity i got back from the cookie should be added to it, if not, it should be null.
quantity: isTheItemInCart ? userObj.quantityCount : null,
};
} else {
return ''; /* Make sure this is working the way it should */
}
});
return {
props: {
products: itemInsideCart,
// products: DUUMMY_PRODUCTS || null
},
};
}
I had the cartQuantity state variable and its setter inside the cart component but after reading about lifting state up I moved them to the _app.js component where I then passed them through props back to the cart component but it only shows the values in the cart when I am viewing the cart component.. everything I try doesn't change anything.. once I leave the cart component then the value becomes undefined.
below is the code from my _app.js where I passed the state variable props from:
import { css, Global } from '#emotion/react';
import Head from 'next/head';
import { useState } from 'react';
function MyApp({ Component, pageProps }) {
const [shoppingCartQuantity, setShoppingCartQuantity] = useState();
console.log('props from app.js: ', pageProps);
return (
<>
<Global
styles={css`
html,
body {
margin: 0;
padding: 0;
*,
*:before,
*:after {
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue,
sans-serif, Josefin Sans, Lato;
}
}
`}
/>
<Head>
<link rel="icon" href="/favicon.ico" />
</Head>
<Component
catQuantity={shoppingCartQuantity}
setCartQuantity={setShoppingCartQuantity}
{...pageProps}
/>
<h1>{shoppingCartQuantity}</h1>;
</>
);
}
export default MyApp;
All eCommerce websites stores cart items into local storage, that's why every time you visit their website, you can see what has been already in the cart. The best way is to store cart items in local storage. since the cart icon will be inside the header and the header will be displayed on each page, inside useEffect read the local storage data.
First you need to write the items on localstorage. In your addToCart function (what ever you named it), when you add the items in your cart, write them to localstorage. (if you were using redux, best place would be inside addToCart action)
Since you are using next.js
if (typeof window !== undefined) {
localStorage.setItem(
"cartItems",
JSON.stringify(getState().cartItems)
);
}
I assume you are not using redux,so best way inside Header component in useEffect:
useEffect(()=>{
// since we are in useEffect, that means we are in browser and window is already defined
// So you do not need to use (typeof window !== "undefined") in useEfect
// in case you want to use somewhere else, keep in mind you have to use (typeof window !== "undefined")
// you do not just store cartitems in localstorage, you c an store all nonsensitive data in localstorage, thats why I put others too
// go any ecommerce website, add something to cart, and in browser dev tools, application tab, check localstorage. you see bunch of stored data
if (typeof window !== "undefined") {
shippingAddressFromStorage = localStorage.getItem("shippingAddress")
? JSON.parse(localStorage.getItem("shippingAddress"))
: null;
paymentMethodFromStorage = localStorage.getItem("paymentMethod")
? JSON.parse(localStorage.getItem("paymentMethod"))
: null;
cartItemsFromStorage = localStorage.getItem("cartItems")
? JSON.parse(localStorage.getItem("cartItems") )
: [];
}
})

The Dialog component is not closing correctly

I have a problem with the React.js Dialog component which contains a list of items. There is also an overlay in the Dialog. It closes automatically when the user clicks on it. But the scenario is something different. The dialog also includes a list of items that have some external links wrapped around the <a> element and some with the <div>.
But the method of closing the dialog is not working properly. The Dialog automatically closes when I click inside any list item.
Please see this:
My goal was to close the dialog whenever the user clicked on the overlay or whenever the user clicked on the list item wrapped with the <a> element. Otherwise, it should not be closed.
I still can't figure out What is the correct approach to tackle this issue?
CodeSandbox link
App.js
import React, { useState } from 'react';
import Dialog from './Dialog';
import './App.css';
const App = () => {
const [isOpen, setIsOpen] = useState(false);
const onClickHandler = (isFalse) => {
if (!isFalse) {
setIsOpen(false);
} else {
setIsOpen(!isOpen);
}
};
return (
<div>
<button type="button" onClick={onClickHandler}>
Open Dialog
</button>
<Dialog isOpen={isOpen} onClick={onClickHandler} />
</div>
);
};
export default App;
Dialog.js
import React from 'react';
const Dialog = (props) => {
const { isOpen, onClick } = props;
const onCloseHandle = () => {
onClick(false);
};
return (
<div
className={isOpen ? 'dialog-wrap open' : 'dialog-wrap'}
onClick={onCloseHandle}
>
<div className="dialog">
<ul className="dialog-list">
<li>
<a
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
React.js
</a>
</li>
<li>
<a
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Another Link
</a>
</li>
<li>
<div>Should Not Close onClick</div>
</li>
<li>
<div>Should Not Close onClick</div>
</li>
</ul>
</div>
</div>
);
};
export default Dialog;
App.css
*,:before,:after {
box-sizing: border-box;
}
.dialog-wrap.open {
display: flex;
}
.dialog-wrap {
display: none;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 999;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
padding: 16px;
background-color: rgba(0,0,0,0.66);
}
#media (min-width: 640px) {
.dialog-wrap {
padding: 48px 32px;
}
}
#media (min-width: 1024px) {
.dialog-wrap {
padding-left: 40px;
padding-right: 40px;
}
}
.dialog {
position: relative;
margin: auto;
background-color: #fff;
border: 1px solid rgba(0, 0, 0, 0.1);
box-shadow: 0 12px 16px rgba(0, 0, 0, 0.08);
border-radius: 4px;
width: 500px;
}
.dialog-list {
margin-bottom: 0;
padding-left: 0;
color: #333;
list-style: none;
}
.dialog-list > li {
border-bottom: 1px solid #eee;
padding: 24px;
}
.dialog-list > li > a {
color: #333;
display: block;
text-decoration: none;
}
.dialog-list > li > div {
cursor: pointer;
}
in this situation, I would create a .dialog-background element and give onCloseHandle to that.
I also hive my a elements the onCloseHandle as onClick prop as well
If you do not want to change your react tree you can decide that from the event like this:
<div
className={isOpen ? "dialog-wrap open" : "dialog-wrap"}
onClick={(e) => {
if (!e.target.closest('.dialog') || e.target.closest('a')) {
onClick(false)
}
}}
/>
I think your click event is propagating up the elements, and triggering onCloseHandle on your .dialog-wrap.
The simplest solution to this would be to use Event.stopPropagation() on the <div className="dialog">
E.g.
<div className="dialog" onClick={ e => e.stopPropagation() }>
(React technically uses "synthetic events", but they still include a stopPropagation option as long as it's being interacted with inside React)

Multiple Conditions not working in Styled-Component

I am creating Form Buttons where some would be Primary and some would be Secondary. On them, I would apply the UX color codes. The first one works, the second one doesn't.
Am I doing something wrong?
Here is the code:
const FormActionBtns = styled.span`
height: 50px;
max-width: 100px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
background-color: red;
color: white;
${props => props.theme && props.theme.ACCENT && `
font-size: ${props.theme.FONT_SIZE.TOPIC_HEADER};
color: ${props.theme.PRIMARY_STAGE.FORM_COMPONENTS.FONT.REVERSE_BRIGHT};
${props.isPrimary && `
background-color: ${props.theme.ACCENT.IDLE};
`}
${props.isSecondary && `
background-color: ${props.theme.COMPLEMENTARY.IDLE};
`}
`}
`;
export const SubmitBtn = (props) => {
console.log(props);
return(
<FormActionBtns isPrimary={true}>
{props.submitTxt}
</FormActionBtns>
)
}
export const RevertBtn = (props) => {
return (
<FormActionBtns isSecondary={true}>
{props.revertTxt}
</FormActionBtns>
)
}
The Submit button works brilliantly as the correct accent color gets applied based on the theme. The Reset button is not working properly having passed isSecondary=true.
Note: When I remove the condition isPrimary from the style, Reset then gives proper result
In order to implement CSS dynamically in React, I'd just use the style attribute, similarly to this code:
const FormActionBtns = (props) => {
const styleProp = { height: '50px', maxWidth: '100px' }; // etc.
if (props.isPrimary) {
styleProp.backgroundColor = props.theme.ACCENT.IDLE;
} else if (props.isSecondary) {
styleProp.backgroundColor = props.theme.COMPLEMENTARY.IDLE;
}
return (
<span style={styleProp}>
{props.children}
</span>
);
}
You can also use .attrs.
styled.span.attrs(({ isPrimary, theme }) => ({
background-color: isPrimary ? theme.ACCENT.IDLE : theme.COMPLEMENTARY.IDLE;
}))
`
// other styles
`
You're missing a semi-colon:
${props.isPrimary && `
background-color: ${props.theme.ACCENT.IDLE};
`}; // here
${props.isSecondary && `
background-color: ${props.theme.COMPLEMENTARY.IDLE};
`}

Styled Components Selected Nav Item

I am trying to learn Styled Component's rendering based off props. I am just trying to create a simple Nav bar. Yes, I do realize that you can set links active by using React Router based off the location. This is more of just a learning experience for me.
class Nav extends Component {
constructor (props) {
super(props)
this.state = {
isActive: 0
}
this.handleClick = this.handleClick.bind(this)
}
handleClick (n) {
console.log('Hello There')
this.setState = {
isActive: n
}
}
render () {
const NAV = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
background-color: black;
`
const SPAN = styled.span`
font-size: 3.5vmin;
cursor: pointer;
color: white;
${props =>
props.selected &&
css`
color: cornflowerblue;
`}
`
const TEXT = styled.p``
return (
<NAV links={this.props.links}>
{this.props.links.map((i, ii) => (
<SPAN
onClick={e => this.handleClick(ii)}
key={ii}
selected={this.state.isActive === ii ? true : ''}
>
<TEXT>{i}</TEXT>
</SPAN>
))}
</NAV>
)
}
}
I am trying to make the link active by changing it's text color. When I map over the links I provide the Nav, if the index of the array if equal to the active state, I make that link active.
Then, onClick, I update the state's isActive to the index that was selected. Needless to say, it's not working. I guess the map's index is only available at render and not at the onClick event. I am not sure what to pass the handleClick function, however.
The App is rendering on my local environment just fine. I made a Codepen with the example, but it's not rendering on there. I've never used Codepen for React, here is the link: https://codepen.io/anon/pen/daqGQQ
Also, I realize I can use the className prop to make a CSS class to give the selected link an active status, but, I'd rather learn the 'styled components' way.
Any help would be much appreciated. Thanks
EDIT
Reformat based on comment:
import React, { Component } from 'react'
import styled, { css } from 'styled-components'
const NAV = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
background-color: black;
`
const SPAN = styled.span`
font-size: 3.5vmin;
cursor: pointer;
color: white;
${props =>
props.selected &&
css`
color: cornflowerblue;
`}
`
const TEXT = styled.p``
class Nav extends Component {
constructor (props) {
super(props)
this.state = {
isActive: 0
}
this.handleClick = this.handleClick.bind(this)
}
handleClick (e) {
console.log('Hello There')
console.log(e.target.key)
this.setState = {
isActive: 1
}
}
render () {
return (
<NAV links={this.props.links}>
{this.props.links.map((i, ii) => (
<SPAN
id={ii}
onClick={e => this.handleClick(e)}
key={ii}
selected={this.state.isActive === ii ? true : ''}
>
<TEXT>{i}</TEXT>
</SPAN>
))}
</NAV>
)
}
}
export default Nav
I figured it out by using e.currentTarget.textContent and then setting the state isSelected: e.currentTarget.textContent onClick. Code:
import React, { Component } from 'react'
import styled, { css } from 'styled-components'
const NAV = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
background-color: black;
`
const SPAN = styled.span`
font-size: 3.5vmin;
cursor: pointer;
color: white;
${props =>
props.selected &&
css`
color: cornflowerblue;
`}
`
const TEXT = styled.p``
class Nav extends Component {
constructor (props) {
super(props)
this.state = {
isSelected: 'Home'
}
this.handleClick = this.handleClick.bind(this)
}
componentDidMount () {}
handleClick (e) {
this.setState({
isSelected: e.currentTarget.textContent
})
}
render () {
return (
<NAV links={this.props.links}>
{this.props.links.map((i, ii) => (
<SPAN
id={ii}
onClick={e => this.handleClick(e)}
key={ii}
selected={this.state.isSelected === i ? true : ''}
>
<TEXT>{i}</TEXT>
</SPAN>
))}
</NAV>
)
}
}
export default Nav

react Semantic-UI - multi-select checkbox drop-down

i want to built an multi select checkbox dropdown in react with es6
my requirement is as below specified in image
I tried doing this click here but it is not working.
You can use one parent component that will keep values in its state and toggle list items. Then you can create component for each list item that will keep active property in state that you can toggle on click.
class ListItem extends React.Component {
constructor(props) {
super(props);
this.state = {active: false}
}
render() {
return (
<a
onClick={() => {
this.setState(prevState => {
let newState = !prevState.active;
this.props.handleClick(newState, this.props.value);
return {active: newState}
})
}}
className={!this.state.active ? '' : 'selected'}
href="#">
{this.props.value}</a>
)
}
}
class Select extends React.Component {
constructor(props) {
super(props);
this.state = {
showList: false,
value: []
}
this.handleItemClick = this.handleItemClick.bind(this)
}
componentDidMount() {
document.addEventListener('mousedown', (e) => {
if(!this.node.contains(e.target)) {
this.setState({showList: false})
}
})
}
componentWillUnmount() {
document.removeEventListener('mousedown');
}
renderValue() {
let {value} = this.state;
if(!value.length) return "Select..."
else return value.join(', ')
}
toggleList() {
this.setState(prevState => ({showList: !prevState.showList}))
}
handleItemClick(active, val) {
let {value} = this.state;
if(active) value = [...value, val]
else value = value.filter(e => e != val);
this.setState({value})
}
render() {
return (
<div
ref={node => this.node = node}
className="select">
<button onClick={this.toggleList.bind(this)}>
<span className="select_value">
{this.renderValue()}
</span>
</button>
<div
className={"select_list " + (!this.state.showList && 'hide')}>
<ListItem handleClick={this.handleItemClick} value="Lorem" />
<ListItem handleClick={this.handleItemClick} value="Ipsum" />
<ListItem handleClick={this.handleItemClick} value="Dolor" />
</div>
</div>
)
}
}
ReactDOM.render(
<Select />,
document.getElementById('container')
);
button {
background: white;
width: 100%;
padding: 10px 15px;
border: 1px solid rgba(0, 0, 0, .1);
border-radius: 5px;
cursor: pointer;
text-align: left;
}
.select_list {
width: 100%;
background: white;
border: 1px solid rgba(0, 0, 0, .1);
border-radius: 5px;
}
.select_list a {
padding: 10px 15px;
display: flex;
color: black;
text-decoration: none;
position: relative;
align-items: center;
}
.select_list a:before {
width: 15px;
height: 15px;
content: '';
border: 1px solid rgba(0, 0, 0, .1);
border-radius: 5px;
margin-right: 10px;
display: block;
}
.select_list a.selected:before {
background: #0493D1;
content: '✓';
color: white;
font-size: 11px;
text-align: center;
line-height: 15px;
}
.hide {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
Semantic-UI React Approach
After much digging, I found an old conversation between eugenetumachov and Semantic-UI developers(?). One of the users provided incredibly helpful code that answers this question using Semantic-UI's Dropdown component.
This is done by making use of Dropdown's Dropdown.Menu and Dropdown.Item. Then looping through your options via map to create checkboxes. The only downside is that the workaround does not seem to allow scrolling and will require more CSS. Additionally, based on CSS the checkbox items' background color may turn transparent if you double-click on the dropdown, and the dropdown will collapse on mouse hover. You can bypass the transparency issue by using a class or style property for your Dropdown.Menu and Dropdown.Item.
Semantic-UI developer's response to this type of question appears to be a flat "no" or a
Active items are automatically removed from the Dropdown menu. So you cannot show a "checked" state for an item in the menu.
You could create a similar component out of an Input as a trigger for
a Popup containing a Menu or List of Checkboxes.
Are dropdowns with checkboxes possible? #2417
eugenetumachov's workaround

Resources