-
-
Notifications
You must be signed in to change notification settings - Fork 32.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Dialog] Add a hook to run a function when dialog is shown to the user #3618
Comments
@mbrookes excuse me for not knowing the conventions, why close this if it's still an open question? |
Because it's a question. Please read the issue template you deleted. 👍 |
@mbrookes is this not a feature request? |
@adrianmc There's an issue template he's referring to that we have to make issues easier to read as we get so many of them. Can you edit your original post to use the template or resubmit? It auto fills the box when you write an issue. |
@nathanmarks To be fair, the template does say to remove the headings for feature requests, but on first read, @adrianmc's issue appeared to be a question better suited to alternative forums, and the template says questions may be closed without comment. If there's a clear feature request here, let's reclassify. |
@mbrookes @nathanmarks |
Thanks for the detailed explanation. So my initial question is, given that the open prop is controlled by the parent component and so the state of that prop is already known externally, should the desired behavior not be driven from there, rather than reflecting the state of the |
@mbrookes You have a good point there, and no doubt what you say is entirely possible. However, my initial knee-jerk reaction is that it would create a lot more boilerplate for the parent component. I'll be honest, I am coming from the React-Bootstrap community and am quite used to the wealth of callbacks available there. See link here. The React-Bootstrap modal dialogs have an almost ridiculous number of callbacks. In fact, there are three callbacks just for the showing of a modal dialog ( Sure, I absolutely agree with you that it is possible to do it without these callbacks. But I think the primary benefit of what I am suggesting is developer convenience. It would be really nice to be able to pass in a function and not worry about managing changing state in the parent (after all, isn't that one of the main benefits of componentization?). |
The thing is, you have to manage that state in (or pass a prop through to or however you're communicating that application sate down to |
Yes, I understand that. But the less state the parent has to manage the better. Ideally (in terms of managing state) the developer ONLY has to worry about the opening and closing of the dialog. So perhaps what we disagree on is the marginal convenience gained from implementing a hook? |
Actually, I have just tried setting a text input focus in the parent on state change (using What's happening is that the prop in the parent changes long before the modal dialog is displayed (since there's an animation and thus a time delay between the parent prop change and the actual dialog content being displayed). The whole point of this feature request is to be able to do something AFTER the dialog has been displayed/shown. And since the transition animation is not controlled by the parent, this isn't something that can be easily done. Again, I urge you to reconsider the need for something like this. It appears to be more complex than it looked at first glance. |
I'll leave some code to illustrate what I have done. In the parent component: componentDidUpdate(prevProps, prevState) {
if (!prevState.modalOpen && this.state.modalOpen) {
$('#myInput').focus();
}
} Note that there is a text input element in the dialog with an This, however, works if I try to delay it a bit: componentDidUpdate(prevProps, prevState) {
if (!prevState.modalOpen && this.state.modalOpen) {
setTimeout(function(){$('#myInput').focus();},1000);
}
} I'm sure you'll agree with me that this is not a good solution. |
Based on your proposal to that point, this was all the developer had to worry about anyway. Reflecting the value of
Okay, now we're getting somewhere. 😄 In this case I can see the value in a I'd be interested to hear what the consensus is here. @callemall/material-ui ? |
I'm using the timeout for now, but I'm sure you'll agree that it would make a lot more sense to have an actual hook. If, for some reason, the animation lags or is longer than the expected time, the expected behaviour would fall apart. I'm not well-versed in how the transition animation is handled in the dialog component, but perhaps I'll take a deeper look this weekend. |
Can you make a demo repo with the issue? (using material-ui), or paste some code in here that I can try out Side note: I looked at the react-bootstrap modal and it has 6 callbacks dedicated to various stages of transition. This isn't something that is in the API design of this library right now. However, I'm really interested in seeing an example of the issue as I do understand how that could be frustrating. |
@nathanmarks I've put together a component that demonstrates the issue in its entirety: import React from 'react';
import Dialog from 'material-ui/lib/dialog';
import TextField from 'material-ui/lib/text-field';
import FlatButton from 'material-ui/lib/flat-button';
export default class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false
};
}
handleOpen() {
this.setState({open: true});
}
handleClose() {
this.setState({open: false});
}
componentDidUpdate(prevProps, prevState) {
if (!prevState.open && this.state.open) {
this._textField.focus(); // Doesn't work
setTimeout(() => {
this._textField.focus(); // Works
},250);
}
}
render() {
const actions = [
<FlatButton
label="Cancel"
secondary={true}
/>,
<FlatButton
label="Submit"
primary={true}
/>,
];
return (
<div>
<button onClick={this.handleOpen.bind(this)}>Click</button>
<Dialog
open={ this.state.open }
onRequestClose={ this.handleClose.bind(this) }
actions={ actions }
>
<TextField
floatingLabelText="Title"
ref={(x) => this._textField = x}
fullWidth
/>
</Dialog>
</div>
);
}
} Go ahead and render the component. The only dependencies are React and Material-UI. You'll see a button you can click on to display a modal dialog. You'll notice that, if you open the console, you'll see the following error:
This error halts execution and it won't ever focus onto the text field for you. The command fails because the text field isn't even rendered yet, that's why you can't focus onto it. The modal is still being animated into view. Comment out the line marked "Doesn't work" and you'll see that the command being executed inside the |
+1 for this feature, we are attempting exactly the same, a dialog with text field that we'd like to focus on show. |
I still think this is an important feature, but the community has been kind of silent around this use case. My best guess is that most everyone (including myself) has given up and just went back to using |
@AndrewLindsay221 The simplest answer I found is setting the |
@adrianmc We have a couple of issues with dialog that need addressing for I'm assuming that what you're looking for is the ability to call a function after the |
@nathanmarks yup, that's exactly right. Glad to hear it's in the books! |
@benderunit - we did try this solution as nobody likes to see magic timeout numbers, but we found that something else would grab the focus away from the text field almost immediately, so much like @adrianmc has said, we eventually gave up and accepted the setTimeout solution. Great to hear that a better solution is on the cards though. |
@adrianmc I'm not using an input but I would like this feature as well. I'm trying to have some images and text fade in when the modal is done animating in not just when the |
The callback will be available in |
+1, or dynamically update the input once the dialog opens |
Another use case is, I was trying to display the Facebook Page Plugin inside a dialog and I need something similar. If we are lazy loading the fb plugin, we need to call I would also like to suggest to pass a scope of dialog to the calling function as I had to pass that to the parse function above. There would be several other similar use cases as well. |
@nathanmarks There could be four type of callback events: |
@ankitduseja the callbacks are (in 0.16.0, which is under development): onEnter |
@nathanmarks: Sounds good. I hope you have passed the scope of the Dialog div as a parameter in the callbacks. |
@nathanmarks Thanks so much for tackling this! I am taking a look at the 0.16.0 version and I'm not seeing the new proptypes. Have these been pushed off to the 'next' branch? |
@TomMahle Yes, they should be there.
|
Really glad to see this come full circle! Good job everyone! |
Fantastic! Can't wait for this :D |
+1 waiting for this to be released :) |
when is this going to be released? any timeframe |
@usergit Those new hooks will be released with the |
Sorry, im pretty new here. I'm glad that I am not the only one with this problem. I checked the material-ui version on the front page and its V.0.16.5 on npm. Is this already here, and if so, whats the function to use? |
Whilst waiting on the new version to be out, here's a dirty solution to focus on an input element on dialog open: componentWillReceiveProps(nextProps)
{
//Check if dialog will be opened
if(this.props.open_dialog === false && nextProps.open_dialog === true)
{
//Wait for dialog to finish rendering, then focus on input
setTimeout(() => {
this.myInputReference.focus();
},300)
}
} |
To clarify @shift-keshav-pudaruth 's solution a little more, if your dialog component is set up as so: <Dialog open={this.props.open_dialog}>...</Dialog> You can implement componentWillReceiveProps(nextProps) {
if (this.props.open_dialog && !nextProps.open_dialog) {
// TODO: implement dialog close logic
}
if (!this.props.open_dialog && nextProps.open_dialog) {
// TODO: implement dialog open logic
}
} |
@shift-keshav-pudaruth / @jpdillingham - better approach for the old versions (if you want to avoid setTimeout) would be wrapping the Dialog content in custom component just to track it's <Dialog
{...others}
>
<MountTracker
onMount={this._onModalContentMount}
>
{ children }
</MountTracker>
</Dialog> @autobind
_onModalContentMount() {
if (this.props.onModalContentMount) {
this.props.onModalContentMount();
}
} class MountTracker extends Component {
componentDidMount() {
if (this.props.onMount) {
this.props.onMount();
}
}
render() {
return this.props.children;
}
} |
If anyone finds their way here in 2024, I found an easy way to trigger an <Dialog
open={open}
TransitionProps={{ onEntered: (node, isAppearing) => console.log(node, isAppearing) }}
>
{/* ... */}
</Dialog> See documentation of "under the hood" component here: http://reactcommunity.org/react-transition-group/transition#Transition-prop-onEntered |
This is a feature request discussion. The dialog component is supposed to be "focused on a specific task" as mentioned in the official material design spec here.
As a result, it makes sense to be able to set focus to certain elements or trigger some kind of Javascript the moment the dialog is shown to the user.
This is the material-ui component under discussion: https://www.google.com/design/spec/components/dialogs.html
The Problem
If your dialog contains a text field where the user is expected to enter in some text, it only makes logical sense to be able to set focus to that text field when the dialog is shown. However, as of now, there is no easy way to do this. There is no hook for when the modal is shown because the modal is shown whenever the
open
prop receives atrue
value.Potential Solutions
As an initial foray into this problem, I have evaluated two possible solutions: namely being able to call a function when the component is mounted and detecting state change to see when the modal dialog is shown. Both methods assume that we are going to have an
onDialogShown
prop that will receive a function to be run whenever the dialog is shown to the user. The first method does not work, but the second one seems promising.Component Mounting
Initially, my naive approach was to call the
this.props.onDialogShown
inside thecomponentDidMount()
lifecycle function, but I quickly realized that this wouldn't work because theDialog
component is mounted long before the actual dialog is shown to the user. As a result, by the time the dialog is actually shown to the user,componentDidMount()
would have been run a long long time ago.This method would not work unless the actual rendering of the dialog was a component in and of itself that we could control.
Prop Change Detection
Since the dialog is displayed whenever the
open
prop being passed in evaluates totrue
, my second idea was that we can try to detect when theopen
prop goes fromfalse
totrue
. This will involve looking at thecomponentDidUpdate()
life cycle function and essentially making it look something like this:Before forking and making a full pull-request though, I would like to ask the community whether or not this is the best way forward. Thoughts?
The text was updated successfully, but these errors were encountered: