I have a small webapp and I need to display a dropdown menu when you hover over the word "collective" but the menu should be still there untill I click on a word in the menu or hover out of the menu but it instantly disappears when I move my mouse out from the word "collective" and I don't even get to move my mouse pointer to any of the drop down menu item. I want my drop down menu to be like the one in https://www.geeksforgeeks.org/
my sandbox so far
https://codesandbox.io/s/funny-river-c76hu
For the app to work, you would have to type in the input box "collective", click analyse, then a progressbar will show, click on the blue line in the progressbar, an underline would show under the word "collective" then you should hover over "collective" word and a drop down menu should be displayed.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { Content, Dropdown, Label, Progress, Button, Box } from "rbx";
import "rbx/index.css";
function App() {
const [serverResponse, setServerResponse] = useState(null);
const [text, setText] = useState([]);
const [loading, setLoading] = useState(false);
const [modifiedText, setModifiedText] = useState(null);
const [selectedSentiment, setSentiment] = useState(null);
const [dropdownContent, setDropdownContent] = useState([]);
const [isCorrected, setIsCorrected] = useState(false);
const [displayDrop, setDisplayDrop] = useState(false);
useEffect(() => {
if (serverResponse && selectedSentiment) {
const newText = Object.entries(serverResponse[selectedSentiment]).map(
([word, recommendations]) => {
const parts = text[0].split(word);
const newText = [];
parts.forEach((part, index) => {
newText.push(part);
if (index !== parts.length - 1) {
newText.push(
<u
className="dropbtn"
data-replaces={word}
onMouseOver={() => {
setDropdownContent(recommendations);
setDisplayDrop(true);
}}
onMouseOut={() => setDisplayDrop(false)}
>
{word}
</u>
);
}
});
return newText;
}
);
setModifiedText(newText.flat());
}
}, [serverResponse, text, selectedSentiment]);
const handleAnalysis = () => {
setLoading(true);
setTimeout(() => {
setLoading(false);
setServerResponse({ joy: { collective: ["inner", "constant"] } });
}, 1500);
};
const handleTextChange = event => {
setText([event.target.innerText]);
};
const replaceText = wordToReplaceWith => {
const replacedWord = Object.entries(serverResponse[selectedSentiment]).find(
([word, recommendations]) => recommendations.includes(wordToReplaceWith)
)[0];
setText([
text[0].replace(new RegExp(replacedWord, "g"), wordToReplaceWith)
]);
setModifiedText(null);
setServerResponse(null);
setIsCorrected(true);
setDropdownContent([]);
};
const hasResponse = serverResponse !== null;
return (
<Box>
<Content>
<div
onInput={handleTextChange}
contentEditable={!hasResponse}
style={{ border: "1px solid red" }}
>
{hasResponse && modifiedText
? modifiedText.map((text, index) => <span key={index}>{text}</span>)
: isCorrected
? text[0]
: ""}
</div>
<br />
{displayDrop ? (
<div
id="myDropdown"
class="dropdown-content"
onClick={() => setDisplayDrop(false)}
>
{dropdownContent.map((content, index) => (
<>
<strong onClick={() => replaceText(content)} key={index}>
{content}
</strong>{" "}
</>
))}
</div>
) : null}
<br />
<Button
color="primary"
onClick={handleAnalysis}
disabled={loading || text.length === 0}
>
analyze
</Button>
<hr />
{hasResponse && (
<Label>
Joy{" "}
<Progress
value={Math.random() * 100}
color="info"
onClick={() => setSentiment("joy")}
/>
</Label>
)}
</Content>
</Box>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Related
Whenever I'm clicking on a card to go to the product details page it is automatically going to the bottom of the next page without even scrolling it here is the sample of my code
import React from "react";
import { Link } from "react-router-dom";
import { Rating } from "#material-ui/lab";
const ProductCard = ({ product }) => {
const options = {
value: product.ratings,
readOnly: true,
precision: 0.5,
};
return (
<Link className="productCard" to={`/product/${product._id}`}>
<img src={product.images[0].url} alt={product.name} />
<p>{product.name}</p>
<div>
<Rating {...options} />
<span className="productCardSpan">
{""}({product.numOfReviews} Reviews)
</span>
</div>
<span>{`₹${product.price}/-`}</span>
</Link>
);
};
export default ProductCard;
product details page is starting from here
import React, { useEffect, useState } from "react";
import Carousel from "react-material-ui-carousel";
import "./ProductDetails.css";
import { useSelector, useDispatch } from "react-redux";
import {
clearErrors,
getProductDetails,
newReview,
} from "../../actions/productAction";
import ReviewCard from "./ReviewCard.js";
import Loader from "../layout/Loader/Loader";
import { useAlert } from "react-alert";
import MetaData from "../layout/MetaData";
import { addItemsToCart } from "../../actions/cartAction";
import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Button,
} from "#material-ui/core";
import { Rating } from "#material-ui/lab";
import { NEW_REVIEW_RESET } from "../../constants/productConstants";
const ProductDetails = ({ match }) => {
const dispatch = useDispatch();
const alert = useAlert();
const { product, loading, error } = useSelector(
(state) => state.productDetails
);
const { success, error: reviewError } = useSelector(
(state) => state.newReview
);
const options = {
size: "large",
value: product.ratings,
readOnly: true,
precision: 0.5,
};
const [quantity, setQuantity] = useState(1);
const [open, setOpen] = useState(false);
const [rating, setRating] = useState(0);
const [comment, setComment] = useState("");
const increaseQuantity = () => {
if (product.Stock <= quantity) return;
const qty = quantity + 1;
setQuantity(qty);
};
const decreaseQuantity = () => {
if (1 >= quantity) return;
const qty = quantity - 1;
setQuantity(qty);
};
const addToCartHandler = () => {
dispatch(addItemsToCart(match.params.id, quantity));
alert.success("Item Added To Cart");
};
const submitReviewToggle = () => {
open ? setOpen(false) : setOpen(true);
};
const reviewSubmitHandler = () => {
const myForm = new FormData();
myForm.set("rating", rating);
myForm.set("comment", comment);
myForm.set("productId", match.params.id);
dispatch(newReview(myForm));
setOpen(false);
};
useEffect(() => {
if (error) {
alert.error(error);
dispatch(clearErrors());
}
if (reviewError) {
alert.error(reviewError);
dispatch(clearErrors());
}
if (success) {
alert.success("Review Submitted Successfully");
dispatch({ type: NEW_REVIEW_RESET });
}
dispatch(getProductDetails(match.params.id));
}, [dispatch, match.params.id, error, alert, reviewError, success]);
console.log(product.images);
return (
<>
{loading ? (
<Loader />
) : (
<>
<MetaData title={`${product.name} -- ECOMMERCE`} />
<div className="ProductDetails">
<div>
{product.images &&
product.images.map((item, i) => (
<img
className="CarouselImage"
key={i}
src={item.url}
alt="product"
/>
))}
</div>
<div>
<div className="detailsBlock-1">
<h2>{product.name}</h2>
<p>Product # {product._id}</p>
</div>
<div className="detailsBlock-2">
<Rating {...options} />
<span className="detailsBlock-2-span">
{" "}
({product.numOfReviews} Reviews)
</span>
</div>
<div className="detailsBlock-3">
<h1>{`₹${product.price}`}</h1>
<div className="detailsBlock-3-1">
<div className="detailsBlock-3-1-1">
<button onClick={decreaseQuantity}>-</button>
<input readOnly type="number" value={quantity} />
<button onClick={increaseQuantity}>+</button>
</div>
<button
disabled={product.Stock < 1 ? true : false}
onClick={addToCartHandler}
>
Add to Cart
</button>
</div>
<p>
Status:
<b className={product.Stock < 1 ? "redColor" : "greenColor"}>
{product.Stock < 1 ? "OutOfStock" : "InStock"}
</b>
</p>
</div>
<div className="detailsBlock-4">
Description : <p>{product.description}</p>
</div>
<button onClick={submitReviewToggle} className="submitReview">
Submit Review
</button>
</div>
</div>
<h3 className="reviewsHeading">REVIEWS</h3>
<Dialog
aria-labelledby="simple-dialog-title"
open={open}
onClose={submitReviewToggle}
>
<DialogTitle>Submit Review</DialogTitle>
<DialogContent className="submitDialog">
<Rating
onChange={(e) => setRating(e.target.value)}
value={rating}
size="large"
/>
<textarea
className="submitDialogTextArea"
cols="30"
rows="5"
value={comment}
onChange={(e) => setComment(e.target.value)}
></textarea>
</DialogContent>
<DialogActions>
<Button onClick={submitReviewToggle} color="secondary">
Cancel
</Button>
<Button onClick={reviewSubmitHandler} color="primary">
Submit
</Button>
</DialogActions>
</Dialog>
{product.reviews && product.reviews[0] ? (
<div className="reviews">
{product.reviews &&
product.reviews.map((review) => (
<ReviewCard key={review._id} review={review} />
))}
</div>
) : (
<p className="noReviews">No Reviews Yet</p>
)}
</>
)}
</>
);
};
export default ProductDetails;
I tried linking in it but it won't work and someone told me to use useRef but i don't know how to use it
You are using React router dom. In React Router there is the problem that if we redirect to the new route, it won't automatically take you to the top of the page. Such behavior is normal when you navigate between pages.
Since you are using functional components
Try to use the following window scroll to the top when the component mounts.
useEffect(() => {
window.scrollTo(0, 0)
}, [])
Browser scroll your page.
If you don't want to let browser auto scroll your page,
use History.scrollRestoration method
Prevent automatic page location restoration
if (history.scrollRestoration) {
history.scrollRestoration = 'manual';
}
Read more at MDN History.scrollRestoration
Now, I am making a multi-select component that has a heading inside the select box.
I made the tag inside the div component and every item is tag.
This is my code.
import React, { useState, useRef, useEffect } from "react";
import "./style.css";
function Select({ title, data, changeSelect, selectedItem }) {
const [categories, setCategories] = useState(data);
const [selectedItems, setSelectedItems] = useState([]);
const wrapperRef = useRef(null);
const [isVisible, setIsVisible] = useState(false);
const handleClickOutside = event => {
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
setIsVisible(false);
}
};
useEffect(() => {
document.addEventListener("click", handleClickOutside, false);
return () => {
document.removeEventListener("click", handleClickOutside, false);
};
}, []);
const selectItem = (item) => {
if (selectedItems.indexOf(item) === -1) {
if (categories.length === 1) {
}
setCategories((prevCategories) =>
prevCategories.filter((value) => value !== item)
);
setSelectedItems((prevItems) => [...prevItems, item]);
}
};
const removeItem = (item) => {
setSelectedItems((items) => items.filter((value) => value !== item));
setCategories((prevCategories) => [...prevCategories, item]);
};
return (
<div className="custom-select" ref={wrapperRef} >
{/* <label className="append-label">{title}</label> */}
<div className="multi-select" >
<title>{title}</title>
<div className="multi-select-wrapper">
{selectedItems.length > 0 &&
selectedItems.map((item) => (
<span onClick={() => removeItem(item)}>{item}</span>
))}
</div>
</div>
<div className={`dropDown-wrapper ${isVisible ? "active" : ""}`}>
<ul>
{categories.length > 0 &&
categories.map((item) => (
<li onClick={() => selectItem(item)}>{item}</li>
))}
<li
style={{
display: `${categories.length === 0 ? "block" : "none"}`,
}}
>
No Result
</li>
</ul>
</div>
</div>
);
}
export default Select;
When I click the select button, it shows a dropdown box.
The most important thing here is that I used react-onclickoutside npm library to get the click event outside the current element but it is not working well.
I used several libraries but all of them do not work at all.
PS: I have to use 3 multi-selects.
react: 16.14.0
react-scripts: 3.4.3
react-onclickoutside: 6.10.0
I had a solution by using another npm library and stopPropagation() function.
import React, { useState, useRef, useEffect } from "react";
import ClickOutside from "react-click-outside";
import "./style.css";
function Select({ title, data, changeSelect, selectedItem }) {
const [categories, setCategories] = useState(data);
const [selectedItems, setSelectedItems] = useState([]);
const [isActive, setIsActive] = useState(false);
// const onClickSelect = (e, active) => {
// setIsActive(active);
// console.log(e.target.value, "asdfasdfasdfs");
// };
const selectItem = (item) => {
if (selectedItems.indexOf(item) === -1) {
if (categories.length === 1) {
setIsActive(false);
}
setCategories((prevCategories) =>
prevCategories.filter((value) => value !== item)
);
setSelectedItems((prevItems) => [...prevItems, item]);
}
};
const removeItem = (e, item) => {
e.stopPropagation();
setSelectedItems((items) => items.filter((value) => value !== item));
setCategories((prevCategories) => [...prevCategories, item]);
};
return (
<ClickOutside onClickOutside={() => setIsActive(false)}>
<div className="custom-select" onClick={() => setIsActive(!isActive)}>
<div className="multi-select">
<title>{title}</title>
<div className="multi-select-wrapper">
{selectedItems.length > 0 &&
selectedItems.map((item) => (
<span onClick={(e) => removeItem(e, item)}>{item}</span>
))}
</div>
</div>
<div className={`dropDown-wrapper ${isActive ? "active" : ""}`}>
<ul>
{categories.length > 0 &&
categories.map((item) => (
<li onClick={() => selectItem(item)}>{item}</li>
))}
<li
style={{
display: `${categories.length === 0 ? "block" : "none"}`,
}}
>
No Result
</li>
</ul>
</div>
</div>
</ClickOutside>
);
}
export default Select;
I hope this will help you who has same issue with me.
I want to add to Chip an startIcon={<Icon />}
when click on a Chip.
The state of the icon is managed by chipsState.
In this code,
the state of all chips would change.
How can I change only the chipsState of the element that is clicked?
In this code, the state of all chips will change.
How can I change only the chipsState of the element that is clicked?
const Modal:React.FC<Props>= (props) => {
const {modalData} = props;
const [chipsState, setChipsState] = useState(false);
const onChipClick = (element:any) => {
setChipsState(chipsState => !chipsState);
}
return (
<div>
{
modalData.symtoms.map((element:any, index:number) => (
<div key={index}>
<Chip onClick={() => onChipClick(element)} startIcon={chipsState && <Icon />}>{element.description}</Chip>
</div>
))}
</div>
);
}
export default Modal;
To handle local state (and better testing), you should create a new custom Chip component with dedicated chipState.
interface CustomChipProps {
description: string
}
const CustomChip = (props: CustomChipProps) => {
const [chipState, setChipState] = useState(false);
return <Chip onClick={() => setChipState(prev => !prev)} startIcon={chipState && <Icon />}>{props.description}</Chip>;
}
const Modal:React.FC<Props>= (props) => {
const {modalData} = props;
return (
<div>
{
modalData.symtoms.map((element:any, index:number) => (
<div key={index}>
<CustomChip description={element.description} />
</div>
))}
</div>
);
}
export default Modal;
You can achieve your desired output by changing chipState state from boolean to object.
So first let's change to object state instead of boolean
const [chipsState, setChipsState] = useState({});
Now we will change onChipClick function to change value of selected chip state
const onChipClick = (element:any) => {
setChipsState({...chipsState, chipsState[element]: !chipsState[element]});
}
And finally we will read correct value of each chipsState element.
<Chip onClick={() => onChipClick(element)} startIcon={chipsState[element] && <Icon />}>{element.description}</Chip>
You can try like the following
import React, { useState, useCallback } from "react";
import ReactDOM from "react-dom";
import { Grid, Row } from "react-flexbox-grid";
const ChipSet = ({ symtomsData }) => {
const data = symtomsData.map((symtom) => ({ ...symtom, isSelcted: false }));
const [chipSets, setChipSets] = useState(data);
const onSelectChipSet = useCallback(
(e, index) => {
const updatedChipSets = chipSets.map((chip, i) =>
i === index ? { ...chip, isSelcted: e.target.checked } : chip
);
setChipSets(updatedChipSets);
},
[chipSets]
);
console.log("chipSets", chipSets);
return (
<div>
<h1>Symtoms Data</h1>
{chipSets.map((x, i) => (
<div key={i}>
<label>
<input
onChange={(e) => onSelectChipSet(e, i)}
type="checkbox"
value={x.isSelcted}
/>
{x.description}
</label>
</div>
))}
</div>
);
};
class App extends React.Component {
render() {
const symtomsData = [
{
description: "mild"
},
{
description: "cold"
}
];
return (
<Grid>
<Row>
<ChipSet symtomsData={symtomsData} />
</Row>
</Grid>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
I have an array of categories, each category opens to a list of options. I am creating a button that once you click it should close the current category and open the next. The category and current index are passed as props from surveyForm, I can't figure out why I can't increment to the next index and how I would tell it to open the next category.
When I console log the c_index, I get the right index, when I console log the category I get [object, object]
Right now my openNext button only toggles the current category closed.... please help!
const SPQuestion = ({ category, c_index }) => {
const [isOpen, setIsOpen] = useState(false);
const [index, setIndex] = useState(0);
const diversitySurvey = useSelector(state => state.appData.diversitySurvey)
const categories = diversitySurvey.categories
const toggle = () => setIsOpen(!isOpen);
const openNext = (c_index) => {
if( c_index === categories.length - 1){
toggle()
} else {
toggle()
setIndex(index + 1) // I also tried c_index++ and categories[c_index]++
setIsOpen(true)
}
}
return (
<div key={category.title} className="sp-question active">
<Button onClick={toggle} className={`${category.hasFollowUps ? 'parent-category' : ''}`} ><legend>{category.title}</legend></Button>
<div border border-primary>
<Collapse isOpen={isOpen}>
<Field model={`appData.diversitySurvey.categories[${c_index}].selectedValue`} >
<div>
<span className="sp-instructions">
{category.select === 'one' && 'Select One'}
{category.select === 'many' && 'Select all that apply'}
</span>
<Row>
<Col className="py-2 rounded foo" sm="12">
<Row sm="2" xl="3">
{category.options.map((option, o_index) => {
return (
<SPOption
c_index={c_index}
o_index={o_index}
option={option}
select={category.select}
/>
)
}
)}
</Row>
<div id="fixme" className="d-flex flex-row-reverse">
<Button active className="float-right" style={{backgroundColor: '#70d9b8'}} size="sm" onClick={() => openNext(c_index)} >Next</Button>
</div>
</Col>
</Row>
</div>
</Field>
</Collapse>
</div>
</div>
)
}
export const SurveyForm = ({ handleSubmit, survey }) => {
const diversitySurvey = useSelector(State => State.appData.diversitySurvey)
const errorMessage = useSelector(state => state.errorMessage)
const dispatch = useDispatch()
return (
<Form model="appData" onSubmit={handleSubmit} >
<Row>
<Col md="12" className="title"><h1>{diversitySurvey.title}</h1></Col>
</Row>
<Row>
<Col md="12" className="questions">
{diversitySurvey.categories.map((category, c_index) => {
return (
<SPQuestion category={category} c_index={c_index} modelPrefix="appData.diversitySurvey" />
)
})}
<div className="sp-button-container">
<Button className="sp-submit" onClick={() => dispatch(submitSurvey(diversitySurvey))}>Submit</Button>
</div>
{errorMessage &&
<Alert className="sp-fetch-error" color="danger">
<p className="text-danger">{errorMessage}</p>
</Alert>
}
</Col>
</Row>
</Form>
)
}
I think you should add a new state,selected index state, in the parent component. Then add a selected index props and a function props to set value in it in SPQuestion component and use the new props to compare whether the collapse is open or not.
const SPQuestion = ({ category, c_index ,s_index,setSelectedIndex}) => {
const toggle = () => {
if( c_index == s_index){
setSelectedIndex(-1);
}else{
setSelectedIndex(c_index)
}
}
const openNext = () => {
if( c_index < categories.length){
setSelectedIndex(c_index + 1);
}
}
<Collapse isOpen={c_index === s_index}>
}
Then in parent component:
const [index, setIndex] = useState(-1);
<SPQuestion category={category} c_index={c_index} s_index={index} setSelectedIndex={setIndex} modelPrefix="appData.diversitySurvey" />
I haven't tested it yet so if you can provide sandbox, please do.
I'm building a React tab navigation component with emotion. I'm having trouble finding a solution that would allow me to:
Initially hide all content except for the buttons and not style the buttons.
When you click on a button activate the style and show the content associated with that button.
And finally when you click outside or the input is empty reset to initial state.
Here is the code:
Code
import React, { useState } from "react";
import ReactDOM from "react-dom";
import styled from "#emotion/styled";
import "./styles.css";
const StyledShowButton = styled("button", {
shouldForwardProp: (prop) => ["active"].indexOf(prop) === -1
})`
color: ${({ active }) => (active ? "red" : "black")};
`;
function App() {
const [active, setActive] = useState(0);
const [showInput, setShowInput] = useState(false);
const handleInputChange = (e) => {
if (e.target.value < 1) {
console.log("Reset Everyting");
}
};
const handleTabClick = (e) => {
const index = parseInt(e.target.id, 0);
if (index !== active) {
setActive(index);
}
if (!showInput) {
setShowInput(!showInput);
}
};
return (
<div className="App">
<StyledShowButton
type="button"
id={0}
active={active === 0}
onClick={handleTabClick}
>
First
</StyledShowButton>
<StyledShowButton
type="button"
id={1}
active={active === 1}
onClick={handleTabClick}
>
Second
</StyledShowButton>
{/* CONTENT */}
{active === 0 ? (
<input placeholder="First input" onChange={handleInputChange} />
) : (
<input placeholder="Second input" onChange={handleInputChange} />
)}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Just ask if I didn't make my self clear enough,
Thanks beforehand!
Erik
You can hide inpts in this way at first by assigning a null value to the active state.
You can also initialize values from 1 so that id and state state are not confused.
I made the arrangements.
You can review the code below.
You can also view it from this link. Code:
function App() {
const [active, setActive] = useState(null);
const [showInput, setShowInput] = useState(false);
const handleInputChange = (e) => {
if (e.target.value < 1) {
setActive(null);
}
};
const handleTabClick = (e) => {
const index = parseInt(e.target.id, 0);
if (index !== active) {
setActive(index);
}
if (!showInput) {
setShowInput(!showInput);
}
};
return (
<div className="App">
<StyledShowButton
type="button"
id={1}
active={active === 1}
onClick={handleTabClick}
>
First
</StyledShowButton>
<StyledShowButton
type="button"
id={2}
active={active === 2}
onClick={handleTabClick}
>
Second
</StyledShowButton>
{/* CONTENT */}
{active &&
(active === 1 ? (
<>
<input placeholder="First input" onChange={handleInputChange} />
</>
) : (
<input placeholder="Second input" onChange={handleInputChange} />
))}
</div>
);
}