Goal
Dynamically create dropdown menus via hooks.
Challenge
When I change a hook value to my dropdown, the dropdown does not open or close. Its stays closed.
When I hard code the dropdown in the return, the open and close functions correctly.
Doesn't work Using hooks
///Various required imports are here..
export default function main(){
const [testb, state_set_testb] = useState(<div></div>);
function toggle_dropdown_function(toggle_request) {
console.log("fffffffffffffffffffffff",toggle_request)
state_set_dropdown_open(toggle_request)
}
state_set_testb(<div onMouseEnter={() => toggle_dropdown_function(true)} onMouseLeave={() => toggle_dropdown_function(false)}>
<Dropdown id="testa" isOpen={dropdownOpen} toggle={toggle_dropdown}>
<DropdownToggle style={{ backgroundColor: "transparent", border: "none", paddingTop: '8px', paddingBottom: '8px', paddingleft: '10px', paddingRight: '10px', color: "grey", fontSize: "14px " }}
color="light" >
othera
</DropdownToggle >
<DropdownMenu >
<DropdownItem style={{ fontSize: "14px " }}>Some Action</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>)
return <div>{testb}</div>
}
Works Not using hooks
///Various required imports are here..
export default function main(){
function toggle_dropdown_function(toggle_request) {
console.log("fffffffffffffffffffffff",toggle_request)
state_set_dropdown_open(toggle_request)
}
return <div onMouseEnter={() => toggle_dropdown_function(true)} onMouseLeave={() => toggle_dropdown_function(false)}>
<Dropdown id="testa" isOpen={dropdownOpen} toggle={toggle_dropdown}>
<DropdownToggle style={{ backgroundColor: "transparent", border: "none", paddingTop: '8px', paddingBottom: '8px', paddingleft: '10px', paddingRight: '10px', color: "grey", fontSize: "14px " }}
color="light" >
othera
</DropdownToggle >
<DropdownMenu >
<DropdownItem style={{ fontSize: "14px " }}>Some Action</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
}
Here is how you could accomplish it. Essentially you have a separate component called Dropdown and you push it to an array of dropdowns.
const { useState } = React;
const Dropdown = () => {
const [active, setActive] = useState('Select');
const [isOpen, setOpen] = useState(false);
const items = ["orange", "pear", "apple"];
return <div className={`dropdown`}>
<div onClick={()=> setOpen(!isOpen)} className={"dropdown__header"} >{active}</div >
{isOpen &&
<div className={"dropdown__body"}>
{items.map((item, index) => {
return <div key={index} onClick={(e) => {
setActive(item);
setOpen(false);
}}>{item}</div>
})}
</div>
}
</div>
}
const Main = () => {
const [dropdowns, setDropdowns] = useState([])
const addDropdowns = () => {
let updatedDropdowns = [...dropdowns];
updatedDropdowns.push(<Dropdown />)
setDropdowns(updatedDropdowns);
}
return (
<div className={"main"}>
<button onClick={addDropdowns}>
Add Dropdown
</button>
{dropdowns.map((dropdown, index) => {
return <div key={index}>{dropdown}</div>
})}
</div>
)
}
ReactDOM.render(<Main />, document.getElementById('app'))
Here is some codepen.
UPDATE
I managed to use reactstrap using the same approach and I did not notice any problems.
Here is a codepen
Related
I have made a table with a search bar functionality, it filter the data when press the search button and reset the filter function and show unfilter data when click the clear button but it's not clearing the current input value from the display. however it clear the filetr function and show unfilter data. I tired setting state to empty string but still not able to clear the input value, I'm new in react need assistance to understand the issue
1. App.js having search bar and all the state and function
function App() {
const [searchTerm, setSearchTerm] = useState("");
const classes = useStyles();
const [buttonSearch, setButtonSearch] = useState("");
const getSearchTerm = (event) => {
let searchWord = event.target.value;
setSearchTerm(searchWord);
console.log(searchWord);
}
const doSearch = () => {
console.log('this is from the doSearch func', searchTerm)
setButtonSearch(searchTerm);
}
const clearSearch = () => {
console.log('im working')
setSearchTerm("");
setButtonSearch("");
}
return (
<div className="App">
<div className="wrapper">
<div className="container-table">
<div className="head">
<h5 className='management'>MANAGEMENT</h5>
<div className="head-middle">
<h2>Clients</h2>
<div className="button-collection">
<Button style={{ backgroundColor: '#5900B4', color: '#FFFFFF', fontSize: '15px', fontWeight: '900', width: '206px', height: '42px' }}
variant="contained"
className='add-collection-btn'
startIcon={<AddIcon />}
>
New Collection
</Button>
</div>
</div>
<div className="head-bottom">
<div className="head-button">
<div className="search">
<div className={classes.search}>
<div className={classes.searchIcon}>
<SearchIcon />
</div>
<InputBase
placeholder="Search..."
classes={{
root: classes.inputRoot,
input: classes.inputInput,
}}
onChange={getSearchTerm}
/>
</div>
</div>
<Button onClick={doSearch}
style={{ backgroundColor: 'white', color: 'black', width: '100px', height: '40px', marginLeft: '20px', marginRight: '20px' }} variant="contained">
Search
</Button>
<Button onClick={clearSearch}
style={{ backgroundColor: 'white', color: 'black', width: '100px', height: '40px' }} variant="contained">
Clear
</Button>
</div>
<Button
style={{ backgroundColor: 'transparent', color: '#5900B4', width: '206px', height: '42px', borderColor: '#5900B4', fontSize: '15px', fontWeight: '900' }}
variant="outlined" color="primary"
startIcon={<FilterListIcon />}
>
SHOW FILTER
</Button>
</div>
<div className="table">
<EnhancedTable
searchTerm={buttonSearch}
/>
</div>
</div>
</div>
</div>
</div>
);
}
2. table.js having filter and map function
export default function EnhancedTable(props) {
console.log("these r props for table component", props);
const { searchTerm } = props;
console.log("table searchTerm value", searchTerm)
const classes = useStyles();
const [order, setOrder] = React.useState('asc');
const [orderBy, setOrderBy] = React.useState('calories');
const [selected, setSelected] = React.useState([]);
const [page, setPage] = React.useState(0);
const [dense, setDense] = React.useState(false);
const [rowsPerPage, setRowsPerPage] = React.useState(5);
const isSelected = (name) => selected.indexOf(name) !== -1;
const [data, setData] = useState([]);
const getData = async () => {
try {
const data = await axios.get("something");
console.log('This is data from axios', data.data);
setData(data.data);
} catch (e) {
console.log("this is error for fetching data", e)
}
};
useEffect(() => {
getData();
}, [])
const filtered = useMemo(() => {
if (!searchTerm) {
return data;
}
const term = searchTerm.toLowerCase()
return data.filter(({ clientName, clientEmail }) => clientName.toLowerCase().includes(term)
|| clientEmail.toLowerCase().includes(term)
)
}, [data, searchTerm])
return (
<div className={classes.root}>
<Paper className={classes.paper}>
<EnhancedTableToolbar numSelected={selected.length} />
<TableContainer>
<Table
className={classes.table}
aria-labelledby="tableTitle"
size={dense ? 'small' : 'medium'}
aria-label="enhanced table"
>
<EnhancedTableHead
classes={classes}
numSelected={selected.length}
order={order}
orderBy={orderBy}
/>
<TableBody>
{filtered
.map((item, index) => {
return (
<TableRow
hover
role="checkbox"
tabIndex={-1}
>
<TableCell padding="checkbox">
<Checkbox
/>
</TableCell>
<TableCell component="th" scope="row" padding="none">{item.clientName}</TableCell>
<TableCell align="right">{item.clientEmail}</TableCell>
<TableCell align="right">{item.clientWorkPhone}</TableCell>
<TableCell align="right">{item.clientIndustry}</TableCell>
<TableCell align="right">{item.tenantId}</TableCell>
<TableCell align="right">{item.clientWebsite}</TableCell>
<TableCell align="right">
<Button style={{ backgroundColor: 'transparent', color: '#5900B4' }}
variant="outlined" color="primary" href="#outlined-buttons" >
{<CreateIcon />}
</Button>
</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
</TableContainer>
</Paper>
</div>
);
}
Try this option
<InputBase value={searchTerm}
I have a multi-step form using kendo and basically what i want to do is to pass the values from the function Charging to one of the steps so i can use the user inputs in a fetch API GET request. Normally i would have done it with props, but the thing here is that i am passing data from a function. I tried useContext but i can't make it work and i went through the kendo form code many times in order to grasp its methodology but i still can't pass the values. I am getting the values from onStepSubmit() handler and i can get them outside this callback with useState, but even then i can't pass them.
Here is the code for the main function where i get the values
import * as React from "react";
import "./Main.css";
import { Form, FormElement } from "#progress/kendo-react-form";
import { Button } from "#progress/kendo-react-buttons";
import { Stepper } from "#progress/kendo-react-layout";
import { SelectVehicle } from "./components/SelectVehicle";
import { PaymentMethod } from "./components/PaymentMethod";
import chargeIcon from "../../../../img/svg-6.svg";
import { ChargingStep } from "./components/ChargingStep";
import { Payment } from "./components/Payment";
import axios from "axios";
import { AuthContext } from "../../../../shared/context/auth-context";
import Notification from "../../Vehicles/Vehicles1/components/Notification";
const stepPages = [SelectVehicle, PaymentMethod, ChargingStep, Payment];
export const Charging = () => {
const [step, setStep] = React.useState(0);
const [formState, setFormState] = React.useState({});
const [steps, setSteps] = React.useState([
{ label: "Select Vehicle", isValid: undefined },
{ label: "Method", isValid: undefined },
{ label: "Charging", isValid: undefined },
{ label: "Payment", isValid: undefined },
]);
const auth = React.useContext(AuthContext);
const [vehicleId, setVehicleId] = React.useState(false);
const [notify, setNotify] = React.useState({
isOpen: false,
message: "",
type: "",
});
const lastStepIndex = steps.length - 1;
const isLastStep = lastStepIndex === step;
const isPreviousStepsValid =
steps
.slice(0, step)
.findIndex((currentStep) => currentStep.isValid === false) === -1;
const onStepSubmit = React.useCallback(
//add fetch vehicle data based on ID
(event) => {
const { isValid, values } = event;
axios
.get(process.env.REACT_APP_BACKEND_URL + `/cars/user/${auth.userId}`)
.then((response) => {
for (var i = 0; i < response.data.vehicles.length; i++) {
if (values.vehicleID == response.data.vehicles[i]._id) {
setVehicleId(true);
return;
} else {
setVehicleId(false);
return;
}
}
});
const currentSteps = steps.map((currentStep, index) => ({
...currentStep,
isValid: index === step ? isValid : currentStep.isValid,
}));
setSteps(currentSteps);
setStep(() => Math.min(step + 1, lastStepIndex));
setFormState(values);
if (isLastStep && isPreviousStepsValid && isValid && vehicleId) {
// Send to api the data
//alert(JSON.stringify(values));
setNotify({
isOpen: true,
message: "Submitted Successfully",
type: "success",
});
} else if (isLastStep && isPreviousStepsValid && isValid && !vehicleId) {
setNotify({
isOpen: true,
message: "Wrong vehicle ID input",
type: "error",
});
}
},
[
step,
steps,
setSteps,
setStep,
setFormState,
lastStepIndex,
isLastStep,
isPreviousStepsValid,
]
);
const onPrevClick = React.useCallback(
(event) => {
event.preventDefault();
setStep(() => Math.max(step - 1, 0));
},
[step, setStep]
);
return (
<div>
<div className="vehicle__title">
<div className="main__title">
<img src={chargeIcon} alt="charging" />
<div className="main__greeting">
<h1>Charging Simulator</h1>
<p>Simulate a Charge</p>
</div>
</div>
</div>
<div className="wrapper__simulator">
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
}}
>
<Stepper value={step} items={steps} />
<Form
initialValues={formState}
onSubmitClick={onStepSubmit}
render={(formRenderProps) => (
<div style={{ alignSelf: "center" }}>
<FormElement style={{ width: 480 }}>
{stepPages[step]}
<span
style={{ marginTop: "40px" }}
className={"k-form-separator"}
/>
<div
style={{
justifyContent: "space-between",
alignContent: "center",
}}
className={"k-form-buttons k-buttons-end"}
>
<span style={{ alignSelf: "center" }}>
Step {step + 1} of 4
</span>
<div>
{step !== 0 ? (
<Button
style={{ marginRight: "16px" }}
onClick={onPrevClick}
>
Previous
</Button>
) : undefined}
<Button
primary={true}
disabled={
isLastStep
? !isPreviousStepsValid && !vehicleId
: false
}
onClick={formRenderProps.onSubmit}
>
{isLastStep ? "Submit" : "Next"}
</Button>
</div>
</div>
</FormElement>
</div>
)}
/>
</div>
</div>
<Notification notify={notify} setNotify={setNotify} />
</div>
);
};
export default Charging;
And here is the code for the component where i need these values. In the async componentDidMount function i want the url to be http://localhost:8765/evcharge/api/providers/${values.stationID}/${values.pointID} and get the params.
class OneStep extends React.Component {
data = [
{ text: "100%", id: 1 },
{ text: "75%", id: 2 },
{ text: "50%", id: 3 },
];
state = {
value: { text: "100%", id: 1 },
cost: {text: "", id: null}
};
providers = [];
async componentDidMount() {
const url = "http://localhost:8765/evcharge/api/providers";
const response = await fetch(url);
const data = await response.json();
for (var i = 0; i < data.providers.length; i++) {
this.providers.push({
text: "Provider: " +
data.providers[i]
.Title + " Cost: " + data.providers[i].kWhCost,
id: i + 1 ,
});
}
}
numberFrom = getRandomInt(30, 50, 0);
cost = getRandomInt(0.5, 2, 2);
handleChange = (event) => {
this.setState({
value: event.target.value,
});
console.log(this.data);
console.log(this.providers);
};
handleSecondChange = (event) => {
this.setState({
cost: event.target.value
})
}
render() {
return (
<div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<div style={{ width: "50%", marginRight: "25px" }}>
<Button
style={{
width: "50%",
marginRight: "25px",
marginTop: "35px",
textTransform: "capitalize",
color: "#0779e4",
fontWeight: "600",
fontSize: "18px",
right: "50px",
}}
disabled={true}
look="flat"
>
From: {this.numberFrom}
</Button>
</div>
<Button
style={{
width: "50%",
marginRight: "25px",
marginTop: "35px",
textTransform: "capitalize",
color: "#0779e4",
fontWeight: "600",
fontSize: "18px",
}}
disabled={true}
look="flat"
>
Cost per kWh: {this.cost}
</Button>
</div>
<br />
<div style={{ display: "flex", justifyContent: "space-between" }}>
<div style={{ width: "25%", marginRight: "25px" }}>
<DropDownList
data={this.data}
dataItemKey="id"
value={this.state.value}
onChange={this.handleChange}
textField="text"
defaultItem={{ text: "To" }}
/>
</div>
<div style={{ width: "75%", marginRight: "25px" }}>
<DropDownList
data={this.providers}
dataItemKey="id"
value={this.state.providers}
onChange={this.handleSecondChange}
textField="text"
defaultItem={{ text: "Select Provider..." }}
/>
</div>
</div>
<br />
<div
style={{
display: "flex",
justifyContent: "space-between",
height: "250px",
}}
>
<div style={{ width: "50%", marginLeft: "15px" }}>
<Button
style={{
width: "50%",
marginRight: "25px",
marginTop: "35px",
textTransform: "capitalize",
color: "#ff5349",
fontWeight: "600",
fontSize: "18px",
right: "30px",
}}
disabled={true}
look="flat"
>
<CountUp
start={0}
end={parseInt(
(parseFloat(this.state.value.text) - this.numberFrom) *
this.cost
)}
duration={15}
useEasing={true}
decimals={2}
prefix="Expected Cost: "
suffix=" €"
useGrouping={true}
delay={3}
/>
</Button>
</div>
<div
style={{ width: "50%", marginRight: "25px", marginBottom: "450px" }}
>
<div className="g-container">
<div className="g-number">
<CountUp
start={30}
end={parseInt(this.state.value.text)}
duration={15}
useEasing={true}
decimals={2}
suffix=" %"
useGrouping={true}
delay={3}
/>
</div>
<div className="g-contrast">
<div className="g-circle"></div>
<ul className="g-bubbles">
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
</div>
</div>
</div>
</div>
);
}
}
export const ChargingStep = <OneStep />;
Instead of exporting and using the rendered output of the OneStep component, just export and use the OneStep component itself, then you can pass whatever props you need to it.
Note there's no issue here with having a functional and class-based component as from the outside they're identical.
Start with the file that contains OneStep, change the export statement from export const ChargingStep = <OneStep />; to export const ChargingStep = OneStep; (or ideally just rename OneStep to ChargingStep and export it directly). Note you'll also have to do this with the other step components so they all work the same (but this is how React components should be exported and used anyway).
Then in the Charging component you can change the line in the return statement from {stepPages[step]} to something like:
const StepPage = stepPages[step];
return (
// ...
<StepPage relevantProp={value}/>
// ...
)
Or you can add special handling for just the ChargingStep step if you don't want to pass those same components to all the other steps, which I'd recommend here.
Further Refactoring
You might consider slightly changing the way you keep track of what step the user is on from a straight index lookup to using string names so you can tell which component is going to be rendered.
You could do something like this:
const stepPages = {
"SelectVehicle": SelectVehicle,
"PaymentMethod": PaymentMethod,
"ChargingStep": ChargingStep,
"Payment": Payment,
};
const stepPageNames = ["SelectVehicle", "PaymentMethod", "ChargingStep", "Payment"];
Then to get the step you're on:
const stepPageName = stepPageNames[step];
const StepPage = stepPages[stepPageName];
Then you can do things like:
let stepPage = <StepPage />
if (stepPageName === "ChargingStep") {
stepPage = <StepPage relevantProp={value}/>
}
And place stepPage in your return statement.
I have a dynamically generated set of dropdowns and accordions that populate at render client-side (validated user purchases from db).
I'm running into an error that I'm sure comes from my menu anchorEl not knowing 'which' menu to open using anchorEl. The MUI documentation doesn't really cover multiple dynamic menus, so I'm unsure of how to manage which menu is open
Here is a pic that illustrates my use-case:
As you can see, the menu that gets anchored is actually the last rendered element. Every download button shows the last rendered menu. I've done research and I think I've whittled it down to the anchorEl and open props.
Here is my code. Keep in mind, the data structure is working as intended, so I've omitted it to keep it brief, and because it's coming from firebase, I'd have to completely recreate it here (and I think it's redundant).
The component:
import { useAuth } from '../contexts/AuthContext'
import { Accordion, AccordionSummary, AccordionDetails, Button, ButtonGroup, CircularProgress, ClickAwayListener, Grid, Menu, MenuItem, Typography } from '#material-ui/core'
import { ExpandMore as ExpandMoreIcon } from '#material-ui/icons'
import LoginForm from '../components/LoginForm'
import { motion } from 'framer-motion'
import { useEffect, useState } from 'react'
import { db, functions } from '../firebase'
import styles from '../styles/Account.module.scss'
export default function Account() {
const { currentUser } = useAuth()
const [userPurchases, setUserPurchases] = useState([])
const [anchorEl, setAnchorEl] = useState(null)
const [generatingURL, setGeneratingURL] = useState(false)
function openDownloads(e) {
setAnchorEl(prevState => (e.currentTarget))
}
function handleClose(e) {
setAnchorEl(prevState => null)
}
function generateLink(prefix, variationChoice, pack) {
console.log("pack from generate func", pack)
setGeneratingURL(true)
const variation = variationChoice ? `${variationChoice}/` : ''
console.log('link: ', `edit-elements/${prefix}/${variation}${pack}.zip`)
setGeneratingURL(false)
return
if (pack.downloads_remaining === 0) {
console.error("No more Downloads remaining")
setGeneratingURL(false)
handleClose()
return
}
handleClose()
const genLink = functions.httpsCallable('generatePresignedURL')
genLink({
fileName: pack,
variation: variation,
prefix: prefix
})
.then(res => {
console.log(JSON.stringify(res))
setGeneratingURL(false)
})
.catch(err => {
console.log(JSON.stringify(err))
setGeneratingURL(false)
})
}
useEffect(() => {
if (currentUser !== null) {
const fetchData = async () => {
// Grab user products_owned from customers collection for user UID
const results = await db.collection('customers').doc(currentUser.uid).get()
.then((response) => {
return response.data().products_owned
})
.catch(err => console.log(err))
Object.entries(results).map(([product, fields]) => {
// Grabbing each product document to get meta (title, prefix, image location, etc [so it's always current])
const productDoc = db.collection('products').doc(product).get()
.then(doc => {
const data = doc.data()
const productMeta = {
uid: product,
title: data.title,
main_image: data.main_image,
product_prefix: data.product_prefix,
variations: data.variations
}
// This is where we merge the meta with the customer purchase data for each product
setUserPurchases({
...userPurchases,
[product]: {
...fields,
...productMeta
}
})
})
.catch(err => {
console.error('Error retrieving purchases. Please refresh page to try again. Full error: ', JSON.stringify(err))
})
})
}
return fetchData()
}
}, [currentUser])
if (userPurchases.length === 0) {
return (
<CircularProgress />
)
}
return(
currentUser !== null && userPurchases !== null ?
<>
<p>Welcome, { currentUser.displayName || currentUser.email }!</p>
<Typography variant="h3" style={{marginBottom: '1em'}}>Purchased Products:</Typography>
{ userPurchases && Object.values(userPurchases).map((product) => {
const purchase_date = new Date(product.purchase_date.seconds * 1000).toLocaleDateString()
return (
<motion.div key={product.uid}>
<Accordion style={{backgroundColor: '#efefef'}}>
<AccordionSummary expandIcon={<ExpandMoreIcon style={{fontSize: "calc(2vw + 10px)"}}/>} aria-controls={`${product.title} accordion panel`}>
<Grid container direction="row" alignItems="center">
<Grid item xs={3}><img src={product.main_image} style={{ height: '100%', maxHeight: "200px", width: '100%', maxWidth: '150px' }}/></Grid>
<Grid item xs={6}><Typography variant="h6">{product.title}</Typography></Grid>
<Grid item xs={3}><Typography variant="body2"><b>Purchase Date:</b><br />{purchase_date}</Typography></Grid>
</Grid>
</AccordionSummary>
<AccordionDetails style={{backgroundColor: "#e5e5e5", borderTop: 'solid 6px #5e5e5e', padding: '0px'}}>
<Grid container direction="column" className={styles[`product-grid`]}>
{Object.entries(product.packs).map(([pack, downloads]) => {
// The pack object right now
return (
<Grid key={ `${pack}-container` } container direction="row" alignItems="center" justify="space-between" style={{padding: '2em 1em'}}>
<Grid item xs={4} style={{ textTransform: 'uppercase', backgroundColor: 'transparent' }}><Typography align="left" variant="subtitle2" style={{fontSize: 'calc(.5vw + 10px)'}}>{pack}</Typography></Grid>
<Grid item xs={4} style={{ backgroundColor: 'transparent' }}><Typography variant="subtitle2" style={{fontSize: "calc(.4vw + 10px)"}}>{`Remaining: ${downloads.downloads_remaining}`}</Typography></Grid>
<Grid item xs={4} style={{ backgroundColor: 'transparent' }}>
<ButtonGroup variant="contained" fullWidth >
<Button id={`${pack}-btn`} disabled={generatingURL} onClick={openDownloads} color='primary'>
<Typography variant="button" style={{fontSize: "calc(.4vw + 10px)"}} >{!generatingURL ? 'Downloads' : 'Processing'}</Typography>
</Button>
</ButtonGroup>
<ClickAwayListener key={`${product.product_prefix}-${pack}`} mouseEvent='onMouseDown' onClickAway={handleClose}>
<Menu anchorOrigin={{ vertical: 'top', horizontal: 'right' }} transformOrigin={{ vertical: 'top', horizontal: 'right' }} id={`${product}-variations`} open={Boolean(anchorEl)} anchorEl={anchorEl}>
{product.variations && <MenuItem onClick={() => generateLink(product.product_prefix, null, pack) }>{`Pack - ${pack}`}</MenuItem>}
{product.variations && Object.entries(product.variations).map(([variation, link]) => {
return (
<MenuItem key={`${product.product_prefix}-${variation}-${pack}`} onClick={() => generateLink(product.product_prefix, link, pack)}>{ variation }</MenuItem>
)
})}
</Menu>
</ClickAwayListener>
</Grid>
</Grid>
)}
)}
</Grid>
</AccordionDetails>
</Accordion>
</motion.div>
)
})
}
</>
:
<>
<p>No user Signed in</p>
<LoginForm />
</>
)
}
I think it also bears mentioning that I did check the rendered HTML, and the correct lists are there in order - It's just the last one assuming the state. Thanks in advance, and please let me know if I've missed something, or if I can clarify in any way. :)
i couldn't manage to have a menu dynamic,
instead i used the Collapse Panel example and there i manipulated with a property isOpen on every item of the array.
Check Cards Collapse Example
On the setIsOpen method you can change this bool prop:
const setIsOpen = (argNodeId: string) => {
const founded = tree.find(item => item.nodeId === argNodeId);
const items = [...tree];
if (founded) {
const index = tree.indexOf(founded);
founded.isOpen = !founded.isOpen;
items[index]=founded;
setTree(items);
}
};
<IconButton className={clsx(classes.expand, {
[classes.expandOpen]: node.isOpen,
})}
onClick={()=>setIsOpen(node.nodeId)}
aria-expanded={node.isOpen}
aria-label="show more"
>
<MoreVertIcon />
</IconButton>
</CardActions>
<Collapse in={node.isOpen} timeout="auto" unmountOnExit>
<CardContent>
<MenuItem onClick={handleClose}>{t("print")}</MenuItem>
<MenuItem onClick={handleClose}>{t("commodities_management.linkContainers")}</MenuItem>
<MenuItem onClick={handleClose}>{t("commodities_management.linkDetails")}</MenuItem>
</CardContent>
</Collapse>
I think this is the right solution for this: https://stackoverflow.com/a/59531513, change the anchorEl for every Menu element that you render. :D
This code belongs to TS react if you are using plain JS. Then remove the type.
import Menu from '#mui/material/Menu';
import MenuItem from '#mui/material/MenuItem';
import { useState } from 'react';
import { month } from '../../helper/Utilities';
function Company() {
const [anchorEl, setAnchorEl] = useState<HTMLElement[]>([]);
const handleClose = (event: any, idx: number) => {
let array = [...anchorEl];
array.splice(idx, 1);
setAnchorEl(array);
};
<div>
{month &&
month.map((val: any, ind: number) => {
return (
<div
key={val.id + 'w9348w344ndf allBankAndCardAccountOfClient'}
style={{ borderColor: ind === 0 ? '#007B55' : '#919EAB52' }}
>
<Menu
id='demo-positioned-menu'
aria-labelledby='demo-positioned-button'
anchorEl={anchorEl[ind]}
open={anchorEl[ind] ? true : false}
key={val.id + 'w9348w344ndf allBankAndCardAccountOfClient' + ind}
onClick={(event) => handleClose(event, ind)}
anchorOrigin={{
vertical: 'top',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
<MenuItem
key={val.id + 'w9348w344ndf allBankAndCardAccountOfClient' + ind}
onClick={(event) => handleClose(event, ind)}
style={{
display: ind === 0 ? 'none' : 'inline-block',
}}
>
<span
style={{
marginLeft: '.5em',
color: 'black',
background: 'inherit',
}}
>
Make Primary
</span>
</MenuItem>
<MenuItem onClick={(event) => handleClose(event, ind)}>
<span style={{ marginLeft: '.5em', color: 'black' }}>Edit</span>
</MenuItem>
<MenuItem
onClick={(event) => handleClose(event, ind)}
style={{
display: ind === 0 ? 'none' : 'inline-block',
}}
>
<span style={{ marginLeft: '.5em', color: 'red' }}>Delete</span>
</MenuItem>
</Menu>
</div>
);
})}
</div>;
}
export default Company;
I have a tab component in Material-UI and I want to implement a tooltip on it.
My problem is that when I click the tab component, the tooltip is not disappearing. It must disappear after I click on that tab.
Currently, it continues to be visible even after I click on the tab.
How do I rectify that?
<Tabs
className="navbar-routes"
value={value}
style={{ color: 'green'}}
indicatorColor="secondary"
onChange={handleChange}
>
{
tabsData.map(({id,title,description}) => {
return(
<ToolTip description={description}>
<Tab
style={{
minWidth: 10,
fontSize: '80%',
fontWeight: 'bold',
marginLeft: '-4px',
marginRight: 4
}}
key={id}
component={Link}
to={`/${title}`}
label={`${title}`}
/>
</ToolTip>
);
}
)}
</Tabs>
If you look at the document of Material-UI tooltip API
You would find a props named disableHoverListener
bool
default: false
Do not respond to hover events.
Set it as True would turn off the tooltip onMouseOver event trigger.
Update
Or you can simply make it totally under control.
By binding the onClick, onMouseOver, onMouseLeave, open to related component.
import React, { useState } from "react";
import "./styles.css";
import { Tooltip, Tab } from "#material-ui/core";
export default function App() {
const [flg, setFlg] = useState(false);
const [isHover, setIsHover] = useState(false);
return (
<div className="App">
<Tooltip
title={"message"}
aria-label="add"
placement="bottom"
open={!flg && isHover}
>
<Tab
label={`Click: ${!flg ? "enabled" : "disabled"}`}
onClick={() => setFlg(!flg)}
onMouseOver={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
/>
</Tooltip>
</div>
);
}
Try it online:
You can also implement a generic tooltip with a managed state when to open/close the tooltip.
import Tooltip, { TooltipProps } from "#mui/material/Tooltip";
import { useState } from "react";
/**
* MUI Tooltip wrapper with adaption to the move away once focuses left.
*/
export function ManagedTooltip(props: TooltipProps) {
const [open, setOpen] = useState<boolean>(false);
// Wrap Tooltip with div to capture mouse events
return <div style={{ display: 'flex' }}
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
onClick={() => setOpen(false)}
>
{/* Show the original MUI Tooltip with all props. */}
{/* Just override the open attribute to be fully managed, and disable internal listeners */}
<Tooltip {...props} open={open} disableHoverListener disableFocusListener />
</div>;
}
Once it's ready, you can use it anywhere exactly like the original MUI tooltip.
<Tabs
className="navbar-routes"
value={value}
style={{ color: 'green'}}
indicatorColor="secondary"
onChange={handleChange}
>
{
tabsData.map(({id,title,description}) => {
return(
<ManagedTooltip description={description}>
<Tab
style={{
minWidth: 10,
fontSize: '80%',
fontWeight: 'bold',
marginLeft: '-4px',
marginRight: 4
}}
key={id}
component={Link}
to={`/${title}`}
label={`${title}`}
/>
</ManagedTooltip>
);
}
)}
</Tabs>
The way I solved this was by rendering the tooltip conditionally. In your case I suppose you want the tooltip not to render for the tab of the current active route:
function ConditionalTooltip({renderTooltip, children, ...props}) {
return renderTooltip ? <Tooltip {...props}>{children}</Tooltip> : children;
}
function Tabs() {
const location = useLocation();
return (
<Tabs
className="navbar-routes"
value={value}
style={{ color: 'green'}}
indicatorColor="secondary"
onChange={handleChange}
>
{
tabsData.map(({id,title,description}) => {
return(
<ConditionalTooltip
renderTooltip={location.pathname.indexOf(title) === -1} /* only render tooltip on not active urls */
title={description}
>
<Tab
style={{
minWidth: 10,
fontSize: '80%',
fontWeight: 'bold',
marginLeft: '-4px',
marginRight: 4
}}
key={id}
component={Link}
to={`/${title}`}
label={`${title}`}
/>
</ConditionalTooltip>
);
}
)}
</Tabs>
)
}
I am creating Badges Or Chips like these
Using the code:
<View style = {{flexDirection: 'row', marginLeft: 10, marginTop: 5}}>
{this.state.eduModes.map((v, i) => {
return (
<Badge
key={i}
onPress = {(i) => {
console.log(i);
}}
value={v}
containerStyle= {{marginRight: 5}}
textStyle={{ color: 'orange' }}
/>
)
})}
</View>
The user chooses the value from a picker which creates the badge, now what I want is when the user clicks on the badge the badge should be removed. So how can I access the specific badge on which the user clicked so it would disappear on re-rendering?
You could create a new inline function that sends the index of the badge that should be removed to the remove function.
Example
class App extends React.Component {
handlePress = index => {
this.setState(previousState => {
const eduModes = [...previousState.eduModes];
eduModes.splice(index, 1);
return { eduModes };
});
};
render() {
return (
<View style={{ flexDirection: "row", marginLeft: 10, marginTop: 5 }}>
{this.state.eduModes.map((v, i) => {
return (
<Badge
key={i}
onPress={() => this.handlePress(i)}
value={v}
containerStyle={{ marginRight: 5 }}
textStyle={{ color: "orange" }}
/>
);
})}
</View>
);
}
}