最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Set initial state for material ui dialog - Stack Overflow

programmeradmin0浏览0评论

I have a MaterialUI dialog that has a few text fields, drop downs, and other things on it. Some of these elements need to be set to some value every time the dialog opens or re-opens. Others elements cannot be loaded until certain conditions exist (for example, user data is loaded).

For the 'resetting', I'm using the onEnter function. But the onEnter function doesn't run until entering (duh!)... but the render function, itself, still does - meaning any logic or accessing javascript variables in the JSX will still occur. This leaves the 'onEnter' function ill-equipped to be the place I set up and initialize my dialog.

I also can't use the constructor for setting/resetting this initial state, as the data I need to construct the state might not be available at the time the constructor loads (upon app starting up). Now, I could super-plicate my JSX in my render function and make conditionals for every data point... but that's a lot of overhead for something that gets re-rendered every time the app changes anything. (the material UI dialogs appear run the entire render function even when the 'open' parameter is set to false).

What is the best way to deal with initializing values for a material ui dialog?

Here is a super-dumbed-down example (in real life, imagine getInitialState is a much more plex, slow, and potentially async/network, function) - let's pretend that the user object is not available at app inception and is actually some data pulled or entered long after the app has started. This code fails because "user" is undefined on the first render (which occurs BEFORE the onEnter runs).

constructor(props) {
    super(props);
}

getInitialState = () => {
    return {
        user: {username: "John Doe"}
    }
}

onEnter = () => {
    this.setState(this.getInitialState())
}


render() {
    const { dialogVisibility } = this.props;

    return (
        <Dialog  open={dialogVisibility}  onEnter={this.onEnter}>
            <DialogTitle>
                Hi, {this.state.user.username}
            </DialogTitle>
        </Dialog> );
}

My first instinct was to put in an "isInitialized" variable in state and only let the render return the Dialog if "isInitialized" is true, like so:

constructor(props) {
    super(props);
    this.state = {
        isInitialized: false
    };
}

getInitialState = () => {
    return {
        user: {username: "John Doe"}
    }
}

onEnter = () => {
    this.setState(this.getInitialState(), 
        () => this.setState({isInitialized:true})
    );
}


render() {
    const { dialogVisibility } = this.props;

    if(!this.state.isInitialized) {
        return null;
    }

    return (
        <Dialog  open={dialogVisibility}  onEnter={this.onEnter}>
            <DialogTitle>
                Hi, {this.state.user.username}
            </DialogTitle>
        </Dialog> );
}

As I'm sure you are aware... this didn't work, as we never return the Dialog in order to fire the onEnter event that, in turn, fires the onEnter function and actually initializes the data. I tried changing the !this.state.inInitialized conditional to this:

    if(!this.state.isInitialized) {
        this.onEnter();
        return null;
    }

and that works... but it's gives me a run-time warning: Warning: Cannot update during an existing state transition (such as withinrender). Render methods should be a pure function of props and state.

That brought me to a lot of reading, specifically, this question: Calling setState in render is not avoidable which has really driven home that I shouldn't be just ignoring this warning. Further, this method results in all the logic contained in the return JSX to still occur... even when the dialog isn't "open". Add a bunch of plex dialogs and it kills performance.

Surely there is a 'correct' way to do this. Help? Thoughts?

I have a MaterialUI dialog that has a few text fields, drop downs, and other things on it. Some of these elements need to be set to some value every time the dialog opens or re-opens. Others elements cannot be loaded until certain conditions exist (for example, user data is loaded).

For the 'resetting', I'm using the onEnter function. But the onEnter function doesn't run until entering (duh!)... but the render function, itself, still does - meaning any logic or accessing javascript variables in the JSX will still occur. This leaves the 'onEnter' function ill-equipped to be the place I set up and initialize my dialog.

I also can't use the constructor for setting/resetting this initial state, as the data I need to construct the state might not be available at the time the constructor loads (upon app starting up). Now, I could super-plicate my JSX in my render function and make conditionals for every data point... but that's a lot of overhead for something that gets re-rendered every time the app changes anything. (the material UI dialogs appear run the entire render function even when the 'open' parameter is set to false).

What is the best way to deal with initializing values for a material ui dialog?

Here is a super-dumbed-down example (in real life, imagine getInitialState is a much more plex, slow, and potentially async/network, function) - let's pretend that the user object is not available at app inception and is actually some data pulled or entered long after the app has started. This code fails because "user" is undefined on the first render (which occurs BEFORE the onEnter runs).

constructor(props) {
    super(props);
}

getInitialState = () => {
    return {
        user: {username: "John Doe"}
    }
}

onEnter = () => {
    this.setState(this.getInitialState())
}


render() {
    const { dialogVisibility } = this.props;

    return (
        <Dialog  open={dialogVisibility}  onEnter={this.onEnter}>
            <DialogTitle>
                Hi, {this.state.user.username}
            </DialogTitle>
        </Dialog> );
}

My first instinct was to put in an "isInitialized" variable in state and only let the render return the Dialog if "isInitialized" is true, like so:

constructor(props) {
    super(props);
    this.state = {
        isInitialized: false
    };
}

getInitialState = () => {
    return {
        user: {username: "John Doe"}
    }
}

onEnter = () => {
    this.setState(this.getInitialState(), 
        () => this.setState({isInitialized:true})
    );
}


render() {
    const { dialogVisibility } = this.props;

    if(!this.state.isInitialized) {
        return null;
    }

    return (
        <Dialog  open={dialogVisibility}  onEnter={this.onEnter}>
            <DialogTitle>
                Hi, {this.state.user.username}
            </DialogTitle>
        </Dialog> );
}

As I'm sure you are aware... this didn't work, as we never return the Dialog in order to fire the onEnter event that, in turn, fires the onEnter function and actually initializes the data. I tried changing the !this.state.inInitialized conditional to this:

    if(!this.state.isInitialized) {
        this.onEnter();
        return null;
    }

and that works... but it's gives me a run-time warning: Warning: Cannot update during an existing state transition (such as withinrender). Render methods should be a pure function of props and state.

That brought me to a lot of reading, specifically, this question: Calling setState in render is not avoidable which has really driven home that I shouldn't be just ignoring this warning. Further, this method results in all the logic contained in the return JSX to still occur... even when the dialog isn't "open". Add a bunch of plex dialogs and it kills performance.

Surely there is a 'correct' way to do this. Help? Thoughts?

Share Improve this question edited Oct 2, 2019 at 23:40 R.J. Dunnill 2,0893 gold badges11 silver badges23 bronze badges asked Oct 2, 2019 at 21:48 lowcrawlerlowcrawler 7,59910 gold badges58 silver badges94 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 5

What you need conceptually is that when you are freshly opening the dialog, you want to reset some items. So you want to be able to listen for when the value of open changes from false to true.

For hooks, the react guide provides an example for keeping the "old" value of a given item with a usePrevious hook. It is then simply a matter of using useEffect.

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

function MyDialog({ dialogVisibility }) {
  const prevVisibility = usePrevious(dialogVisibility);

  useEffect(() => {
    // If it is now open, but was previously not open
    if (dialogVisibility && !prevVisibility) {
      // Reset items here
    }
  }, [dialogVisibility, prevVisibility]);

  return <Dialog open={dialogVisibility}></Dialog>;
}

The same thing can be achieved with classes if you use ponentDidUpdate and the previousProps parameter it receives.

export class MyDialog extends Component {
  public ponentDidUpdate({ dialogVisibility : prevVisibility }) {
    const { dialogVisibility } = this.props;

    if (dialogVisibility && !prevVisibility) {
      // Reset state here
    }
  }

  public render() {
    const { dialogVisibility } = this.props;

    return <Dialog open={dialogVisibility}></Dialog>;
  }
}

You should use ponentDidUpdate()

  • This method is not called for the initial render
  • Use this as an opportunity to operate on the DOM when the ponent has been updated

If you need data preloaded before the dialog is opened, you can use ponentDidMount():

  • is invoked immediately after a ponent is mounted (inserted into the tree)
  • if you need to load data from a remote endpoint, this is a good place to instantiate the network request

React guys added the useEffect hook exactly for cases like the one you are describing, but you would need to refactor to a functional ponent. Source: https://reactjs/docs/hooks-effect.html

This can be solved by doing leaving the constructor, getInitialState, and onEnter functions as written and making the following addition of a ternary conditional in the render function :

render() {
    const { dialogVisibility } = this.props;

return (
    <Dialog  open={dialogVisibility}  onEnter={this.onEnter}>
        {this.state.isInitialized && dialogVisibility ? 
        <DialogTitle>
            Hi, {this.state.user.username}
        </DialogTitle> : 'Dialog Not Initialized'}
    </Dialog> );
)}

It actually allows the dialog to use it's "onEnter" appropriately, get the right transitions, and avoid running any extended plex logic in the JSX when rendering while not visible. It also doesn't require a refactor or added programming plexity.

...But, I admit, it feels super 'wrong'.

发布评论

评论列表(0)

  1. 暂无评论