I'm trying to add a white outline via css box-shadow, but whenever I click on any of the buttons, they all get the outline instead of just the actual button I clicked.
Is there a way so only the button component I click on gets the outline and then toggles off if I click it again?
Here is my current code:
const [selectState, setSelectState] = useState(false);
const Button = ({ selected, text }) => {
function handleClick() {
setSelectState(true);
}
return (
<span
onClick={handleClick}
className={`btn-style ${selected ? "selected" : ""}`}
>
{text}
</span>
);
};
export default function Hello() {
return (
<Button selected={selectState} text='Blue'/>
<Button selected={selectState} text='Red'/>
<Button selected={selectState} text='Green'/>
);
}
.selected css:
.selected {
box-shadow: rgb(17 206 101) 0px 0px 0px 2px inset !important;
}
If you want to track the selected state of individual elements, you'd need to handle the onClick method and make corresponding state change in parent element.
const Button = ({ selected, text, onClick }) => {
return (
<span
onClick={onClick}
className={`btn-style ${selected ? "selected" : ""}`}
>
{text}
</span>
);
};
export default function Hello() {
const [selectState, setSelectState] = React.useState(0);
return (
<React.Fragment>
<Button
onClick={() => setSelectState(1)}
selected={selectState === 1}
text="Blue"
/>
<Button
onClick={() => setSelectState(2)}
selected={selectState === 2}
text="Red"
/>
<Button
onClick={() => setSelectState(3)}
selected={selectState === 3}
text="Green"
/>
</React.Fragment>
);
}
You can have the click handler tell the parent component to save the clicked button index in state, and pass that state down to determine whether the selected class is needed:
const Button = ({ selected, text, onClick }) => {
return (
<span
onClick={onClick}
className={`btn-style ${selected ? "selected" : ""}`}
>
{text}
</span>
);
};
export default function Hello({ texts }) {
const [selectedIndex, setSelectedIndex] = useState(-1);
return (<>
{
texts.map((text, i) => <Button selected={i === selectedIndex} text={text} onClick={() => setSelectedIndex(i)}} />)
}
</>);
}
ReactDOM.render(<Hello texts={['Blue', 'Red', 'Green']} />, document.querySelector('.react'));
Related
So, I am creating a dropdown menu in React and if I click outside the dropdown menu, it should close. For that, I am currently using click eventListeners. Is there any other way that can be used instead of using eventListeners? I tried with onFocus and onBlur, but that doesn't seem to work.
Here's the code snippet:
const [showMenu, setShowMenu] = useState(false);
const dropdownRef = useRef(null);
useEffect(() => {
//hiding the dropdown if clicked outside
const pageClickEvent = (e: { target: unknown }) => {
if (dropdownRef.current !== null && !dropdownRef.current.contains(e.target)) {
setShowMenu(!showMenu);
}
};
//if the dropdown is active then listens for click
if (showMenu) {
document.addEventListener("click", pageClickEvent);
}
//unsetting the listener
return () => {
document.removeEventListener("click", pageClickEvent);
};
}, [showMenu]);
<Button onClick = {() => setShowMenu(!showMenu)} />
{showMenu ? (
<div className="dropdown-content" ref={dropdownRef} >
<a>
...
<a>
</div>
) : null}
Yes there is. Use an overlay under the menu.
function MyComponent() {
const [menuVisible, setMenuVisible] = useState(false);
return (
<div>
<button className='dropdown-button' onClick={() => setMenuVisible(true)}>Click me</button>
{menuVisible ? (
<ul className='dropdown-menu'>
{/* items go here */ }
</ul>
) : null}
{/* now the important part */}
{menuVisible ? (<div className='overlay' onClick={() => setMenuVisible(false)} />) : null}
</div>
)
}
CSS
.overlay {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0, 0, 0, 0.01);
}
import React ,{useState} from 'react'
const LessMoreData = ({data}) => {
const [readMore , setReadMore] = useState(false);
return (
<div>
<p>
{readMore ? data : `${data.substring(0,100)}`}
<button onClick = {() => setReadMore(!readMore)}
style = {{backgroundColor: "black", color: "yellow"}}>
{readMore ? 'LESS' : 'MORE'}
</button>
</p>
</div>
)
}
export default LessMoreData
Use ClassName.
className={readMore?'LESS':'MORE'}
In React I created a component that holds a local state of popup. With a little help from onClick handler I change the local state to make the popup show up. The Popup component in turn contains confirm button. I would like to call a function deleteItem ONLY after the confirm button is clicked. But I don't get how to do it. In the code below the item gets deleted right after the popup shows up but it has to be deleted only if I press the button in the Popup component. If I understand correctely, the state of the components changes when the popup shows up and I have to get it khow to the MainComponent and only in this case the function deleteItem will be called.
import {deleteItem} from './item-reducer';
const MainComponent = ({items}) => {
const [visiblePopup, setVisiblePopup] = useState(false);
return(
{items.map(item => <li key={item.id}></li>
<img onClick={() => {
setVisiblePopup(true);
deleteList(item.id) // I have to call this function after the button
in the Popup component is pressed
}}
/>
)}
<Popup setVisiblePopup={setVisiblePopup}
)
}
Popup.jsx
<div onClick={() => setVisiblePopup(false)} />
Confirm
</div>
What I have to accomplish 1) I click img and popup shows up 2) I press
'Confirm' 3) function deleteItem is invoked 4)popup dissapeares.
If I understand you correctly this is what your looking for ?
const MainComponent = ({items}) => {
const [modalState, setModalState] = useState({
display: false,
deleteItemId: undefined
});
const modalCallback = useCallback((deleteItemId)=>{
deleteItem(deleteItemId)
setModalState({ display: false, deleteItemId: undefined })
},[])
return(
<Fragment>
{
items.map(item => (
<Fragment>
<li key={item.id}></li>
<img onClick={() => setModalState({ display: true, deleteItemId: item.id })} />
</Fragment>
))
}
<PopupModal
visible={modalState.display}
deleteItemId={modalState.deleteItemId}
callback={modalCallback}
/>
</Fragment>
)
}
const PopupModal = ({ visible, deleteItemId, callback }) => {
return (visible ? <div onClick={ () => callback(deleteItemId)}>Confirm</div> : null)
}
--- OR ----
const MainComponent = ({items}) => {
const [modalState, setModalState] = useState({
display: false,
deleteItemId: undefined
});
return(
<Fragment>
{
items.map(item => (
<Fragment>
<li key={item.id}></li>
<img onClick={() => setModalState({ display: true, deleteItemId: item.id })} />
</Fragment>
))
}
modalState.display ? <div onClick={() => [deleteList(modalState.deleteItemId), setModalState({display: false, deleteItemId: undefined}) ]}>Confirm</div> : null
</Fragment>
)
}
When I click on the + every list is displayed and if I click on the - everything is hidden. How can I click on specific button and it displays the content or hide as the case may be. Right now a click on any of the button either hides or shows.
{ showing ? <button onClick={(e) => setShowing(false)}>-</button> : <button onClick={(e) => setShowing({showing: showing})}>+</button>
}
{ showing
? student.grades.map((grade, index) => (
<span className="grade" key={index}>Test {index}: {grade}</span>
)) : <span></span>
}
Your plus function seems to be invalid, try something like this:
For the minus (-):
<button onClick={(e) => setShowing(false)}>-</button>
For the plus (+):
<button onClick={(e) => setShowing(true)}>+</button>
You either need to keep track of a showing state variable for each row, or a more "React" style solution would be to create another component for each row, and let them each have one state variable:
function myComponent(props) {
const { students } = props.students; // or wherever these come from - you didn't specify
return students.map((student) => <StudentRow {...student} />});
}
function StudentRow (props) {
const [showing, setShowing] = useState(false); // or true if they default visible
return (<>
{ showing ? <button onClick={(e) => setShowing(false)}>-</button> : <button onClick={(e) => setShowing(true)}>+</button>}
{ showing ? student.grades.map((grade, index) => (
<span className="grade" key={index}>Test {index}: {grade}</span>
)) : <span></span>}
</>);
}
You could refactor this StudentRow component to be more easily readable as:
function StudentRow (props) {
const [showing, setShowing] = useState(false); // or true if they default visible
if (!showing) {
return <button onClick={(e) => setShowing(true)}>+</button>;
} else {
return (<>
<button onClick={(e) => setShowing(false)}>-</button>
{student.grades.map((grade, index) => (
<span className="grade" key={index}>Test {index}: {grade}</span>
))}
</>);
}
I have a nav menu built with material-ui/core in Navbar.
I use useRef to track the position of clicked button on toggle menu close.
anchorRef.current.contains(event.target)
And I am getting 'Uncaught TypeError: anchorRef.current.contains is not a function' .
I tried 'Object.values(anchorRef.current).includes(event.target)' instead, it always returns false.
-- update --
anchorRef.current.props Object.
withStyles {
props:{
aria-haspopup: "true"
aria-owns: undefined
children: "계정"
className: "nav-menu--btn"
onClic: f onClick()
get ref: f()
isReactWarning: true
arguments: (...)
caller: (...)
length: 0
name: "warnAboutAccessingRef"
...
}, context{...}, refs{...}, ...}
ToggleMenuList
const ToggleMenuList = ({ navAdminList, navAdminItems, classes }) => {
const [activeId, setActiveId] = useState(null);
const anchorRef = useRef(null);
const handleToggle = id => {
setActiveId(id);
};
const handleClose = event => {
if (anchorRef.current && anchorRef.current.contains(event.target)) {
return;
}
setActiveId(null);
};
return (
<React.Fragment>
<div className={`nav-menu--admin ${classes.root}`}>
{navAdminList.map(e => (
<div key={e.id}>
<Button
ref={anchorRef}
aria-owns={activeId === e.id ? 'menu-list-grow' : undefined}
aria-haspopup="true"
onClick={() => handleToggle(e.id)}
>
{e.name}
</Button>
{activeId === e.id && (
<ToggleMenuItems
id={e.id}
activeId={activeId}
handleClose={handleClose}
anchorRef={anchorRef}
items={navAdminItems[e.id]}
/>
)}
</div>
))}
</div>
</React.Fragment>
);
};
export default withStyles(styles)(ToggleMenuList);
ToggleMenuItems
const ToggleMenuItems = ({
listId,
activeId,
handleClose,
anchorRef,
items,
}) => {
const isOpen = activeId === listId;
const leftSideMenu = activeId === 3 || activeId === 4 ? 'leftSideMenu' : '';
return (
<Popper
open={isOpen}
anchorEl={anchorRef.current}
keepMounted
transition
disablePortal
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin:
placement === 'bottom' ? 'center top' : 'center bottom',
}}
className={`toggle-menu ${leftSideMenu}`}
>
<Paper id="menu-list-grow">
<ClickAwayListener
onClickAway={handleClose}
>
<MenuList className="toggle-menu--list">
{items.map(e => (
<MenuItem
key={e.id}
className="toggle-menu--item"
onClick={handleClose}
>
<Link
to={e.to}
className="anchor td-none c-text1 toggle-menu--link"
>
{e.name}
</Link>
</MenuItem>
))}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
);
};
export default ToggleMenuItems;
react: ^16.8.6
react-dom: ^16.8.6
react-router-dom: ^4.3.1
#material-ui/core: ^3.1.2
I assume your ToggleMenuItems sets up global(document-level?) event listener on click to collapse Menu on clicking somewhere outside.
And you have a sibling button element. Clicking on that you want to keep menu expanded, right? So that was the point to use .contains in onClick to check if we are clicked outside of ToggleMenuItems but in scope of specific Button. The reason why it does not work: <Button> is custom class-based React component so it returns React component instance in ref. And it does not have any DOM-specific methods like .contains
You can rework you current approach: just stop bubbling event in case Button has been clicked. It would stop global event handler set by ToggleMenuItems to react.
const stopPropagation = (event) => event.stopPropagation();
const ToggleMenuList = ({ navAdminList, navAdminItems, classes }) => {
const [activeId, setActiveId] = useState(null);
const anchorRef = useRef(null);
const handleToggle = id => {
setActiveId(id);
};
const handleClose = event => {
setActiveId(null);
};
return (
<React.Fragment>
<div className={`nav-menu--admin ${classes.root}`}>
{navAdminList.map(e => (
<div key={e.id}>
<div onClick={stopPropagation}>
<Button
aria-owns={activeId === e.id ? 'menu-list-grow' : undefined}
aria-haspopup="true"
onClick={() => handleToggle(e.id)}
>
{e.name}
</Button>
</div>
{activeId === e.id && (
<ToggleMenuItems
id={e.id}
activeId={activeId}
handleClose={handleClose}
anchorRef={anchorRef}
items={navAdminItems[e.id]}
/>
)}
</div>
))}
</div>
</React.Fragment>
);
};
export default withStyles(styles)(ToggleMenuList);
I've put stopPropagation handler outside since it does not depend on any internal variable.