I'm trying to read the id of a <Button /> component when it's clicked:
const FlagsDialog = (props) => {
const {
classes,
handleClose,
showFlagsDialog,
} = props;
const selectLang = (evt) => {
console.log('CLICKED', evt.target.id);
};
return (
<Dialog
open={showFlagsDialog}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<Button onClick={selectLang} id="en">English</Button>
<Button onClick={selectLang} id="es">Spanish</Button>
<Button onClick={selectLang} id="fr">French</Button>
<Button onClick={selectLang} id="de">German</Button>
</Dialog>
);
};
But when run, the click only returns the text CLICKED, and no value for the clicked button's id. What am I doing wrong?
I don't know what you're trying to do, but that code works.
I'm assiming Button and Dialog are your custom components (if not, you need to change them to button and dialog). I changed them to button and dialog in the code that I used and it works fine.
Here, check it out:
use a method instead of event for passing id as parameter.
const FlagsDialog = (props) => {
const {
classes,
handleClose,
showFlagsDialog,
} = props;
const selectLang = (id) => {
console.log(id);
//OR e.target.getAttribute("id")
};
return (
<Dialog
open={showFlagsDialog}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<Button onClick={() => this.selectLang(id)} id="en">English</Button>
<Button onClick={selectLang} id="en">English</Button>
</Dialog>
);
};
Alternatively you can use the fancy-pant ref function:
class App extends React.Component {
btns = [];
handleClick = index => console.log(this.btns[index].id);
render() {
return (
<div>
<button
ref={ref => (this.btns[0] = ref)}
onClick={() => this.handleClick(0)}
id="no1"
>
First
</button>
<button
ref={ref => (this.btns[1] = ref)}
onClick={() => this.handleClick(1)}
id="no2"
>
Second
</button>
</div>
);
}
}
Turns out this ref implementation is simpler than I expected.
Related
I have a MUI custom dialog, with 1 input text and 2 action buttons (Cancel, Done).
I can pass onClick function etc.. but
I have problem in passing data from this dialog to the actual parent widget, because when I click on "Done" i need to:
save the text
close the dialog
My Dialog
const FormDialog = ({ open, onClose, onSave }) => {
const [value, setValue] = useState("");
const parentToChild = () => {};
if (!open) return null;
return (
<div>
<Dialog open={open} onClose={onClose}>
<DialogTitle>Edit</DialogTitle>
<DialogContent>
<DialogContentText>Add name</DialogContentText>
<TextField
value={value}
onChange={setValue(value)} />
</DialogContent>
<DialogActions>
<Button onClick={onClose}>Cancel</Button>
<Button onClick={() => onSave(value)}>Salva</Button>
</DialogActions>
</Dialog>
</div>
);
and the dialog inside my class:
This is where I should receive the text value
<FormDialog open={open} onClose={() => setOpen(false)} onSave={() => editCategory()} />
const editCategory = (value) => () => {
console.log(value);
setOpen(false);
};
You need to receive the parameter in the onSave parent function
//Before
... onSave={()=>editCategory()}
//After
... onSave={(value)=>editCategory(value)}
But that is not needed since you can pass the function editCategory directly to onSave (editCategory already receives "value"):
... onSave={editCategory}
IMPORTANT
// wrong
const editCategory = (value) => () => {...}
// correct
const editCategory = (value) => {...}
So, finally:
<FormDialog open={open} onClose={() => setOpen(false)} onSave={editCategory} />
const editCategory = (value) => {
console.log(value);
setOpen(false);
};
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'm trying to call a function from another component, with the old fashion react Class style I was able to do it easily, since I'm trying to hooked everything I'm facing this kind of issue
This code doesn't work when we call setText() using the reference :
export function MyComp(props, ref) {
const [theText, setText] = useState(props.theText);
return (
<div>
<h1>{theText}</h1>
<button
onClick={e => {
setText("clicked with inside button");
}}
>
inside button
</button>
<button
onClick={e => {
setText("not clicked");
}}
>
reinit
</button>
</div>
);
}
export const MyRefComp = React.forwardRef((props, ref) => (
<MyComp ref={ref} {...props}>
{props.children}
</MyComp>
));
function App() {
const compref = useRef();
return (
<div>
<MyRefComp ref={compref} theText="not clicked" />
<button
onClick={e => {
compref.current.setText("clicked with outside button");
}}
>
outside button
</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
here is the editable code : https://codesandbox.io/s/reactforwardrefproblem-ublk0
Thanks for helping
Here is the answer to your question, but i don't think it's a good pattern to do like this.
You need explain what are you trying to do, so we can help you. I assume context or HOC is what you needed.
Working example.
Thanks #RTW,
It's incredible how many combinaisons I tried and I didn't manange to do it.
Context or HOC won't fit in my case.
I've also simplified it to avoid the intermediaite component, and allow multiple calls with an object that contains the func.
here is it :
const MyComp = React.forwardRef((props, ref) => {
const [theText, setText] = useState(props.theText);
ref.current = { setText: setText };
return (
<div>
<h1>{theText}</h1>
<button
onClick={e => {
setText("clicked with inside button");
}}
>
inside button
</button>
<button
onClick={e => {
setText("not clicked");
}}
>
reinit
</button>
</div>
);
});
function App() {
let compref = useRef();
return (
<div>
<MyComp ref={compref} theText="not clicked" />
<button
onClick={e => {
compref.current.setText("clicked with outside button");
}}
>
outside button
</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
https://codesandbox.io/s/react-example-x194f
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.
In my App.jsx I have an event handler and a returned component:
handleSell = (price) => (event) => {
console.log(price);
}
render() {
return(
<SellCatalogLine
key = {item.ORDERID}
price = {item.PRICE}
title = {item.TITLE}
handleSell = {this.handleSell}/>
)}
My component looks like this:
function SellCatalogLine(props){
const currentprice = props.price
const cheaperprice = currentprice - 100;
return (
<div>
<h3>{props.title}</h3>
<p>Lowest Price: ${currentprice}</p>
<button type="submit" onClick = {() => props.handleSell(currentprice)}>List item at ${currentprice} </button>
<button type="submit" onClick = {() => props.handleSell(cheaperprice)}>List item at ${cheaperprice} </button>
</div>
)
};
I'm trying to make it so that the console will log cheaperprice or currentprice depending on which button I click. How do I do that?
Since handleSell method when called returns another function so you need to call props.handleSell(currentprice) in SellCatalogLine component.
i.e.
handleSell = (price) => (event) => {
console.log(price);
}
And use it as
<button type="submit" onClick = {props.handleSell(currentprice)}>List item at ${currentprice} </button>
If handleSell method did not return a function then you could use an anonymous function. In which you could call props.handleSell(currentprice)
i.e
handleSell = (event, price) => {
console.log(price);
}
And use it as
<button type="submit" onClick = {(e) => props.handleSell(e, currentprice)}>List item at ${currentprice} </button>