How do i change the internal state of the Popover with a function outside the component: Like the openPopover function i created below.
const openPopover = () => {
setPopoverOpen(true) // Example..
}
<Popover>
{({open}: {open: boolean}) => {
// How do i change the open state here from outside the Popover.
return (
<>
<Popover.Button>
Test
</Popover.Button>
{open && (
<Popover.Panel static>
Test
</Popover.Panel>
)}
</>
)
}}
</Popover>
Popover manages open state internally and doesn't expose a way to change this.
To keep track of the open state however, you could use a useEffect inside the render prop.
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
<Popover>
{({open}: {open: boolean}) => {
useEffect(() => {
setIsPopoverOpen(open)
}, [open]);
return (
<>
<Popover.Button>
Test
</Popover.Button>,
{open && (
<Popover.Panel static>
Test
</Popover.Panel>
)}
</>
)
}}
</Popover>
The toggling behavior is being handled by <Popover.Button> component. Alternatively, Popover and Popover.Panel exposes a close() method to close the popover. You could always use Portals to make the component available in parent for handling either toggling or executing the close() method.
import { createPortal } from 'react-dom';
<Popover>
{({ open, close }) => {
return (
<>
{createPortal(
<Popover.Button>
Toggle popover
</Popover.Button>,
// html node where we will position this button
)}
{open && (
<Popover.Panel static>
Test
</Popover.Panel>
)}
{createPortal(
<button onClick={close()}>
Close Popover
</button>,
// html node where we will position this close button
)}
</>
)
}}
</Popover>
Related
I have the following component:
const ModalWrapper = props => {
return (
<Modal
isOpen={true}
ariaHideApp={false}
>
{props.children}
</Modal>
)
};
I'm using this modal in other file:
import ModalWrapper from '../components/CModal/ModalWrapper';
return (
<ModalWrapper
isOpen={this.state.showModal}
onRequestClose={this.handleCloseModal}
>
so, as in the component: isOpen will be always true, and I'm passing the showModal state, which will change according with the actions on my file.. there is some way in which I can change or override this isOpen method through my file?
i want to display a dialog on clicking additem and addbooks button using react and typescript.
what i am trying to do?
I want to display a dialog on clicking additem button or addbooks button . this dialog will have hide button. on clicking this hide button the dialog should never appear again for the session.
Below is the code,
function MainComponent () {
const [showDialog, setShowDialog] = React.useState(false);
const openDialog = () => {
setShowDialog(true);
};
const hideDialog = () => {
setShowDialog(false);
};
return (
<Route
path="/items"
render={routeProps => (
<Layout>
<Home
showDialog={showDialog}
openDialog={openDialog}
hideDialog={hideDialog}
{...routeProps}
/>
{showDialog && (
<Dialog
hideDialog={hideDialog}
/>
)}
</Layout>
)}
/>
<Route
path="/items/:Id/books/:bookId"
render={routeProps => (
<Layout>
<Books
openDialog={openDialog}
{...routeProps}
/>
{showDialog && (
<Dialog
hideDialog={hideDialog}
/>
)}
</Layout>
)}
</>
)
function Home ({openDialog}: Props) {
return (
<button Onclick={openDialog}>AddItem</Button>
)
}
function Books ({openDialog}: Props){
return (
<button onClick={openDialog}>AddBooks</Button>
)
}
function MessageDialog({hideDialog}: Props) {
return (
<button onClick={hideDialog}>hide</button>
)
}
Now the question is as you see i am rendering MessageDialog in two places based on showDialog value. if users clicks additems button the dialog is displayed and when user to navigates to other view and clicks addbooks button the dialog is displayed.
somehow i feel this is not the right approach or something is missing...
How can i create a global dialog component that is accessible anywhere from my app or using toastify or some better approach. could someone help me with this. thanks.
I think your code is pretty good, but I can think of two minor things that could potentially improve it.
Firstly, you have some duplicate code in your main component. You should be able to move the Dialog outside of the routes. Like so:
return (<>
{showDialog && (
<Dialog
hideDialog={hideDialog}
/>
)}
<Route ... />
<Route ... />
</>)
This will render your dialog no matter which route is matched.
Secondly, instead of passing openDialog and hideDialog callbacks as props, you could create a React context. This is optional, and dependning on the case this might not be desired. A React context is slightly more complex than just a callback, so it adds complexity to your code. The benefit is that the callbacks doesn't need to be passed down through the props of every child component, which makes the component structure cleaner if you have a large component tree.
To create a React context for this use case, you could do the following:
// Create a context with a default value of false
let DialogContext = React.createContext()
function MainComponent () {
...
return (<DialogContext.Provider value={{ setDialogOpen: (open) => setShowDialog(open) }}>
{showDialog && (
<Dialog />
)}
<Route ... />
<Route ... />
</ DialogContext.Provider>)
}
function Home ({openDialog}: Props) {
let dialogContext= useContext(DialogContext)
return (
<button onclick={() => dialogContext.setDialogOpen(true)}>AddItem</Button>
)
}
function Books ({openDialog}: Props){
let contextValue = useContext(DialogContext)
return (
<button onClick={() => dialogContext.setDialogOpen(true)}>AddBooks</Button>
)
}
function MessageDialog({hideDialog}: Props) {
let dialogContext= useContext(DialogContext)
return (
<button onClick={() => dialogContext.setDialogOpen(false)}>hide</button>
)
}
I am opening Model (child Component) on Button Click from Parent Component, it opens very well but its not closing and it shows some error:
Uncaught TypeError: setOpen is not a function from Child Component
Here is My Parent Component
<TableCell>
<Button
variant="contained"
size="small"
color="primary"
onClick={() => deleteHandler(index)}
>
Delete Me
</Button>
</TableCell>
{console.log(open)}
{open && <AddList open={open} setOpen={open} />}
My Child Component
export default function TransitionsModal(open, setOpen) {
const classes = useStyles();
// const [openL, setOpenL] = React.useState(null);
// const handleOpen = () => {
// setOpen(true);
// };
const handleClose = () => {
setOpen(!open);
};
return (
<div>
<Modal
aria-labelledby="transition-modal-title"
aria-describedby="transition-modal-description"
className={classes.modal}
open={open}
onClose={handleClose}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500
}}
>
<Fade in={open}>
<div className={classes.paper}>
<h2 id="transition-modal-title">Transition modal</h2>
<p id="transition-modal-description">
react-transition-group animates me.
</p>
</div>
</Fade>
</Modal>
</div>
);
}
Your first issue is that you are passing a Boolean for the setOpen prop rather than the setOpen function itself, so change it to setOpen={setOpen}.
// RenderList.js
const RenderList = props => {
// ...
return (
...
{open && <AddList open={open} setOpen={setOpen} />}
)
}
Your second issue is that you're not destructing props properly in the TransitionsModal component. Use {} to destruct the props object and grab what you need.
// AddList.js
export default function TransitionsModal({ open, setOpen }) {
// ...
}
Here's the fixed example:
CodeSandbox
Hope this helps.
Hi take a look at this
https://codesandbox.io/s/frosty-bird-5yh5g
in RenderList.js you didn't pass setOpen
{open && <AddList open={open} setOpen={setOpen} />}
also export default function TransitionsModal({ open, setOpen }) {
I have a React app which uses Material UI for it's interface. I've created a custom button component which styles the default Material UI button and also uses redux.
The render() function of my button component looks like this:
return (
<div className={classes.buttonWrapper}>
<Button
ref={this.props.innerRef}
disabled={loading || disabled}
onClick={this.handleClick}
{...other}>
<React.Fragment>
{children}
{this.buildLoader(loading, classes)}
</React.Fragment>
</Button>
</div>
);
What I want is to be able to include this button on a page and have the UI trigger its click event by other means other than clicking on it. For example, on a login form I want a user who currently has focus on the password textbox to be able to trigger the button click by hitting the Return/Enter key.
I'm sure I need to use the concept of forwarding refs in React, but I'm fairly new to React and can't get it working. You can see on my button I've defined a ref set to this.props.innerRef. My button component (called WaitingButton) is exported like this:
const withInnerRef = React.forwardRef((props, ref) => <WaitingButton
innerRef={ref} {...props}
/>);
var component = withStyles(styles)(withInnerRef);
export default connect(mapStateToProps, mapDispatchToProps)(component);
I've then added this button to a form like this:
<Paper>
<TextField
style={{marginBottom: '8px'}}
label="A textbox!"
fullWidth
onKeyPress={(e) => { if (e.key === "Enter") this.triggerClick(); }} />
<WaitingButton
ref={this.submitButton}
variant="contained"
color="primary"
onClick={(e) => {
console.log('Button clicked :)', e.target);
}}>
Press enter in textbox!
</WaitingButton>
</Paper>
See I've assigned the button's ref and in this page's constructor I've initialised the ref in the constructor using this.submitButton = React.createRef();
Finally the triggerClick looks like this:
triggerClick() {
console.log('CLICK', this.submitButton.current);
this.submitButton.current.click();
}
When I hit enter in the textbox, I can inspect the value assigned to this.submitButton.current and can see it is the Redux connect object that I've wrapped my button with. However, I also get the error this.submitButton.current.click is not a function so clearly the ref isn't getting forwarded all the way to the button itself.
I'm afraid I'm a bit lost so appealing for your help!
Just want to ensure, what you want is: when user press Enter while typing on the textfield, the button will show a loading visual, right?
I think you don't have to pass ref to the button component, you could just pass state isLoadingShown into your WaitingButton
WaitingButton.js
return (
<div className={classes.buttonWrapper}>
<Button
ref={this.props.innerRef}
disabled={loading || disabled}
onClick={this.handleClick}
{...other}>
<React.Fragment>
{children}
{this.props.isLoadingShown && this.buildLoader(loading, classes)}
</React.Fragment>
</Button>
</div>
);
Then in the form component
state = {
isLoadingShown: false,
}
triggerClick() {
this.setState({ isLoadingShown: true })
}
render(){
...
<Paper>
<TextField
style={{marginBottom: '8px'}}
label="A textbox!"
fullWidth
onKeyPress={(e) => { if (e.key === "Enter") this.triggerClick(); }} />
<WaitingButton
variant="contained"
color="primary"
isLoadingShown={this.state.isLoadingShown}
onClick={(e) => {
console.log('Button clicked :)', e.target);
}}>
Press enter in textbox!
</WaitingButton>
</Paper>
...
}
don't forget to set isLoadingShown to false again in componentWillUnmount
I just tried to reproduce your case. And I created a codesandbox for it. I think I found the problem. It seems React.forwardRef only works with prop name forwardedRef so try to rename the innerRef property to forwardedRef in your code.
const withInnerRef = React.forwardRef((props, ref) => <WaitingButton
forwardedRef={ref} {...props}
/>);
and also in your render() function
<Button
ref={this.props.forwardedRef}
disabled={loading || disabled}
onClick={this.handleClick}
...
You can try it with my simplified codesandbox https://codesandbox.io/s/intelligent-cori-rb5ce
With my menu I encounter different problem.
For starters, in the first RETURN, I have a TREEITEM with a LISTITEM and a LISTITETEXT.
I put an OnClick in the LISTITETEXT so that if the id of my menu is equal to a value I authorize a redirection.
However, the redirection reloads the page and this is not the purpose of my menu when I use react.
Second, I have my other RETURN which contains my submenu.
it is displayed correctly, when I click on the TREEITEM, I am redirected to the right page.
However, I have the error in the console:
Warning: You tried to redirect to the same route you're currently on:
"/extranetV1/prospect"
{stoMenu && (
<TreeView
style={layout.menu}
defaultCollapseIcon={<ArrowDropDownIcon />}
defaultExpandIcon={<ArrowRightIcon />}
>
{stoMenu.root.children.map(menu => {
return (
<TreeItem
key={menu.nodeId}
nodeId={menu.nodeId}
label={
<ListItem style={layout.menuListItem} className={menu.iconCls}>
<ListItemText style={layout.menuText} primary={menu.text} onClick={() => {
if (menu.id === '/accueil') {
window.location.assign(menu.id);
}
}} />
</ListItem>
}
>
{menu.children.map(child => {
return (
<TreeItem
key={child.nodeId}
nodeId={child.nodeId}
label={child.text}
>
<Redirect to={child.id}/>
</TreeItem>
);
})}
</TreeItem>
);
})}
</TreeView>
)}
For both scenarios you use redirection in react wrong.
You need to use react router for redirection inside react app and instead of window.location.assign(menu.id) it will be props.history.push(menu.id).
I see in the second one you use Redirect component. The problem is that you trigger redirect each time in the loop. You should set some state instead and trigger render with condition earlier in the code.
const MyComponent = (props) => {
const [state, setState] = useState({ pathToRedirect: null });
const handleRedirect = (to) => {
setState({ pathToRedirect: to});
}
render () {
if (state.pathToRedirect) {
return <Redirect to={state.pathToRedirect} />;
}
//render list with handleRedirect onClick
return (...);
}
To correct my problem of reloading the page, I integrated this:
<TreeItem
key={menu.nodeId}
nodeId={menu.nodeId}
label={
<ListItem style={layout.menuListItem} className={menu.iconCls}>
<ListItemText style={layout.menuText} primary={menu.text} onClick={() => {
if (menu.id === '/accueil') {
this.props.history.push(menu.id);
}
}} />
</ListItem>
}
>
I removed the REDIRECT by this in my sub menu :
<TreeItem
key={child.nodeId}
nodeId={child.nodeId}
label={child.text}
onClick={() => {
if (child.id) {
this.props.history.push(child.id);
}
}}
/>