Is there any way to retrieve the dropdown items value? when the on-click handler gets called it doesn't the value, Instead, it returns the text and IMG component.
And also the on onchange handler attached to dropdown never gets triggered
import React from "react";
import { Dropdown, Item } from "semantic-ui-react";
import { connect } from "react-redux";
import "./currency.style.css";
const CurrencyConverter = ({ currencyDetails, initialData }) => {
const handleSelection = (e) => {
alert();
};
return (
<div className="currency-form">
<div className="selected-Country">
<img src={initialData.flag} width="30px" height="30px" />
</div>
<Dropdown
inline
text={initialData.currency}
>
<Dropdown.Menu
>
{currencyDetails.map((item) => (
<Dropdown.Item
key={Math.random()}
text={item.currencyName}
image={item.flag}
style={{ height: "70px", fontSize: "17px" }}
onClick={(event, item) => {
console.log(item);
}}
/>
))}
</Dropdown.Menu>
</Dropdown>
</div>
);
};
const mapStateToProps = ({ currency: { currencyDetails } }) => ({
currencyDetails,
});
export default connect(mapStateToProps, null)(CurrencyConverter);
First step is to assign the 'value' as part of the Dropdown.Item:
<Dropdown.Item
key={Math.random()}
text={item.currencyName}
image={item.flag}
value={item.value}
style={{ height: "70px", fontSize: "17px" }}
onClick={(event, item) => {
console.log(item);
}}
/>
Then you can grab the 'value' from item passed by the onClick event.
onClick={(event, item) => console.log(item.value)}
So long as you set the 'value' and not the 'text' in the initial Dropdown this will also let the Dropdown handle displaying the correct value when it isn't active
Here's a slightly simplified version of your code:
const currencyDetails = [
{currencyName: 'United States Dollars', value: 'USD'},
{currencyName: 'Canadian Dollars', value: 'CDN'}
]
const initialData = {currency: 'USD'}
const CurrencyConverter = () => {
const handleSelection = (e, value) => {
console.log(value)
alert(`"I Chose the value ${value.value}`)
}
return (
<div className="currency-form">
<Dropdown
inline
value={initialData.currency}
>
<Dropdown.Menu
>
{currencyDetails.map((item) => (
<Dropdown.Item
key={item.currencyName}
value={item.value}
text={item.currencyName}
style={{ height: "70px", fontSize: "17px" }}
onClick={handleSelection}
/>
))}
</Dropdown.Menu>
</Dropdown>
</div>
)
}
To read more check out the documentation:
https://react.semantic-ui.com/modules/dropdown/#usage-controlled
Related
I'm trying to update value in react functional compoenent through input element but after the first value I'm unable to type
My Code:
import React from "react";
import "./App.css";
const { useState } = React;
function App() {
const [items, setItems] = useState([
{ value: "" },
{ value: "" },
{ value: "" },
]);
const [count, setCount] = useState(0);
const Item = React.memo(({ id, value, onChange }) => {
return (
<div className="item">Item
<input
onChange={(e) => onChange(id, e.target.value)}
value={value}
/>
</div>
);
});
return (
<div className="app">
<h1>Parent</h1>
<p style={{marginTop: '20px'}}>Holds state: {count}, Does't passes to it items</p>
<p style={{marginTop: '20px'}}>{JSON.stringify(items)}</p>
<button onClick={() => setCount((prev) => prev + 1)} style={{marginTop: '20px'}}>
Update Parent
</button>
<ul className="items" style={{marginTop: '20px'}}>
{items.map((item, index) => {
return (
<Item
key={index}
id={index}
value={item.value}
onChange={(id, value) =>
setItems(
items.map((item, index) => {
return index !== id
? item
: { value: value };
})
)
}
/>
);
})}
</ul>
</div>
);
}
export default App;
You should move the Item declaration outside the App component. Having a component declaration inside another one is almost always a bad idea. Explanation below.
import React, { useState } from "react";
const Item = React.memo(({ id, value, onChange }) => {
return (
<div className="item">
Item
<input onChange={(e) => onChange(id, e.target.value)} value={value} />
</div>
);
});
function App() {
const [items, setItems] = useState([
{ value: "" },
{ value: "" },
{ value: "" }
]);
const [count, setCount] = useState(0);
return (
<div className="app">
<h1>Parent</h1>
<p style={{ marginTop: "20px" }}>
Holds state: {count}, Does't passes to it items
</p>
<p style={{ marginTop: "20px" }}>{JSON.stringify(items)}</p>
<button
onClick={() => setCount((prev) => prev + 1)}
style={{ marginTop: "20px" }}
>
Update Parent
</button>
<ul className="items" style={{ marginTop: "20px" }}>
{items.map((item, index) => {
return (
<Item
key={index}
id={index}
value={item.value}
onChange={(id, value) =>
setItems(
items.map((item, index) => {
return index !== id ? item : { value: value };
})
)
}
/>
);
})}
</ul>
</div>
);
}
export default App;
When a component definition is inside another component, React will re-declare the inner component every time the parent re-renders. This means, that any state held by the inner component will be lost.
In your case, since every time there is an entirely new component, the input was not the same input as in the previous render. This means that the input that was in focus in the previous render is not present anymore, and the new input is not focused anymore.
You should also probably change
setItems(
items.map((item, index) => {
return index !== id ? item : { value: value };
})
)
to
prev.map((item, index) => {
return index !== id ? item : { value: value };
})
)
It's a good idea to use the function notation for set state when the new state depends on the old state value.
I have child component and it has a button to dispatch redux a variable to use in parent component.
But I want to still open child component when I click on the button.
Child Component
const ServoActionFill = (e, jammerSelected) => {
dispatch({
type: "HIGHLIGHT_TARGET",
data: {
uniqueId: "546546644",
},
});
}
return <div>
{showPopup == true && target != null && subPopupServo == true &&
<div className="position-fixed d-flex" style={{ top: info.containerPoint.y - 130, left: info.containerPoint.x - 130, zIndex: 1000 }} onMouseLeave={() => disp("servo")}>
<PieMenu
radius="130px"
centerRadius="70px"
centerX={info.containerPoint.x}
centerY={info.containerPoint.y}
>
{/* Contents */}
{jammerList.activeJammers.map(x =>
//StatusName.forEach(y =>
// y == x.latinName ?
<Slice onMouseOver={(e) => ServoActionFill(e, x)} backgroundColor="#6b22f3"><div style={{ fontSize: "11px", fontWeight: "bold" }}>{x.latinName}</div></Slice>
)}
</PieMenu>
</div>
}
Parent Component
const handlePropChange = (prevProps, nextProps) => {
let returnState = true;
// filter targets based on types
if (nextProps.HIGHLIGHT_TARGET) {
filterTargetsBasedOnTypes(nextProps.targetTypeHiddenInfo);
}
};
const LeafletMap = React.memo(({ highlightedTarget }) => {
const [viewport, setViewport] = React.useState(DEFAULT_VIEWPORT)
const { props: { mapUrl } } = useTheme();
return (
<>
<TextField
classes={{ root: "target-search" }}
placeholder="Search Target"
title="searching target based on its id"
variant="filled"
type="search"
onChange={searchTargetHandler}
onKeyPress={(e) => {
if (e.key === 'Enter')
searchTargetBtnHandler()
}}
InputProps={{
classes: {
input: "py-2 text-black non-opacity",
},
endAdornment: <IconButton
className="p-0"
onClick={searchTargetBtnHandler}
>
<AiOutlineSearch />
</IconButton>
}}
/>
</>
);
}, handlePropChange);
const mapStateToProps = state => {
return {
highlightedTarget: state.BaseReducers.highlightedTarget,
};
};
export default connect(mapStateToProps)(LeafletMap);
When dispatch fire in Child component (Highlight_Target), Parent re-render by redux. But I want hold open Child component. Any solution?
I'm trying to implement this feature in react:
Default state: All list items are blue;
If one specific item is clicked: that same item text color becomes red;
When any other item is clicked: that last item text color revert back to blue;
In my code the item text color stays red, even when other items are clicked.
I searched here and it seems that I should use useRef and the item.id but I'm stuck on how to implement this particular feature.
Thanks
App.js
import { useState } from 'react'
import Item from './components/Item'
function App() {
const [items, setItems] = useState([
{
id: 1,
name: 'Item 1',
},
{
id: 2,
name: 'Item 2',
},
])
return (
<>
{items.map((item) => (
<Item key={item.id} id={item.id} name={item.name} />
))}
</>
)
}
export default App
Item.jsx
import { useState } from 'react'
function Item({ id, name }) {
const [clicked, setClicked] = useState(false)
const handleClick = () => {
setClicked(!clicked)
}
return (
<>
<button
onClick={handleClick}
style={{ color: clicked ? 'red' : 'blue' }}
key={id}
>
{name}
</button>
</>
)
}
export default Item
You need to maintain the selected id in the parent component and based on the selection we can change the color of the button text.
App.jsx
import { useState } from 'react'
import Item from './components/Item'
function App() {
const [items, setItems] = useState([
{
id: 1,
name: 'Item 1',
},
{
id: 2,
name: 'Item 2',
},
])
const [selectedId,setSelectedId] = useState(null)
return (
<>
{items.map((item) => (
<Item key={item.id} id={item.id} name={item.name} handleClick={() => setSelectedId(item.id)} clicked={selectedId === item.id} />
))}
</>
)
}
export default App
Item.jsx
function Item({ id, name, clicked, handleClick }) {
return (
<button
onClick={handleClick}
style={{ color: clicked ? 'red' : 'blue' }}
key={id}
>
{name}
</button>
)
}
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 am trying to put a button of edit for each item with the help of simple hook. i have used map to see the elements of an item.... but somehow it's not working...while pressing the button nothing is showing.....
Codesandbox link: https://codesandbox.io/s/condescending-worker-s0igh
app.js:
import React, { useState } from "react";
import TodoList from "./TodoFiles/TodoList";
const defaultItems = [
{ id: 1, title: "Write React Todo Project", completed: true },
{ id: 2, title: "Upload it to github", completed: false }
];
const App = () => {
const [items,setItems]=useState(defaultItems)
const editItem=({id,title})=>{
setItems(items.map(p=>p.id===id?{...p,title}:p))
}
return (
<div style={{ width: 400 }}>
<hr />
<TodoList
items={items}
editItem={editItem}/>
<hr />
</div>
);
};
export default App;
TodoList.js:
import React from "react";
const TodoItem = ({ title, completed, editItem }) => {
return (
<div style={{ width: 400, height: 25 }}>
<input type="checkbox" checked={completed} />
{title}
<button style={{ float: "right" }} onClick={() => editItem(title)}>
Edit
</button>
</div>
);
};
const TodoList = ({ items = [], index,editItem }) => {
return items.map((p, index) => (
<TodoItem
{...p}
key={p.id}
index={index}
editItem={editItem}
/>
));
};
export default TodoList;
i don't want to use useEffect or useReducer fro custom hook ... because i want to practice with basics. sorry for my frequent questions, i'm trying so hard to learn this reactjs and i dun want to give up... thanx in advance and if it is possible, will you put some explanations in a plain text, why it's not working.
You forgot to pass id to editItem function on button onClick in TodoItem component.
Also you shouldn't use old state in setState function like that (in editItem function). You should use an updater function argument. Thanks to that you always modify current state.
const editItem = ({id,title}) => {
setItems(oldItems => (olditems.map(p=>p.id===id?{...p,title}:p)))
}
import React from "react";
const TodoItem = ({id, title, completed, editItem }) => {
return (
<div style={{ width: 400, height: 25 }}>
<input type="checkbox" checked={completed} />
{title}
<button style={{ float: "right" }} onClick={() => editItem({id,title})}>
Edit
</button>
</div>
);
};
const TodoList = ({ items = [], index,editItem }) => {
return items.map((p, index) => (
<TodoItem
{...p}
key={p.id}
index={index}
editItem={editItem}
/>
));
};
export default TodoList;