Home > Software engineering >  How to not change the state at the first render of React component?
How to not change the state at the first render of React component?

Time:10-22

I'm trying to create a MUI dialog component with React and manage it from wrap component. Here is my example:

import { Dialog, DialogActions, DialogTitle, DialogContent, Button } from "@material-ui/core";
import React, {useState, useEffect} from "react";

export default function MultiNote(props) {
    
    const [useDialogOpen, setDialogOpen] = useState(false);

    useEffect(() => {
        setDialogOpen(!useDialogOpen);
    }, [props.open]);

    const handleDialogToggle = () => {
        setDialogOpen(!useDialogOpen);
    }

    return (
        <Dialog
            open={useDialogOpen}
            onClose={handleDialogToggle}
            aria-labelledby="form-multinote-dialog"
        >
            <DialogTitle id="form-multinote-dialog">{props.title}</DialogTitle>
            <DialogContent>Test</DialogContent>
            <DialogActions>
                <Button onClick={handleDialogToggle} color="primary">
                    Close
                </Button>
            </DialogActions>
        </Dialog>
    );
}

At here, I'm trying change the useDialogOpen state accourding to component's prop's open. It works great except of the first render. At the first render, it opens the dialog. But I want it to be closed.

I couldn't understand why it changes.

CodePudding user response:

I used a sandbox to test out your code. Your useEffect is toggling the useDialogOpen to be true.

I used an if statement inside your useEffect to get it to get the dialog to not show:

useEffect(() => {
    if (Object.keys(props).length !== 0) {
      setDialogOpen(!useDialogOpen);
    }
  }, [props.open]);

This works because it is confirming that a prop is actually being passed into the component and not just an empty object.

Here is a sandbox with all the code together for you to play with:

https://codesandbox.io/s/stoic-shaw-3dszx?file=/src/App.js:259-279

The reason this was happening was because you were watching for a change from props.open which because it was being passed in during the render phase always happened. This then toggled your useDialog to be true and the dialog box would show.

CodePudding user response:

To answer your first question - why does the dialog open on first render? What's happening is this:

  1. The component renders the first time, and useDialogOpen is false (because it was created with useState(false)), and the dialog is closed).
  2. Immediately afterwards (probably so fast it can't be perceived by the user), your useEffect runs, and calls setDialogOpen(!useDialogOpen). Since useDialogOpen is currently false, this is the same as setDialogOpen(true)
  3. The component re-renders with useDialogOpen as true.

If you want the dialog open state to be controlled by the open prop, you might want to consider lifting up state. E.g. get rid of all the state inside this component and add an onClose event to the props. So it would look like this:

function MultiNote(props) {
  return (
    <Dialog
      open={props.open}
      onClose={props.onClose}
      aria-labelledby="form-multinote-dialog"
    >
      <DialogTitle id="form-multinote-dialog">{props.title}</DialogTitle>
      <DialogContent>Test</DialogContent>
      <DialogActions>
        <Button onClick={props.onClose} color="primary">
          Close
        </Button>
      </DialogActions>
    </Dialog>
  );
}

And it could be used like this:

function App() {
  const [isOpen, setIsOpen] = useState(false);
  return <MultiNote open={isOpen} onClose={() => setIsOpen(false)} />;
}

See this codesandbox

  • Related