reactjs

Pre-filled dialog input


I am making a "Rename Folder" dialog. I want it to be pre-filled with the current name and have the Ok button disabled if the input is less than 3 characters long.

The dialog component is added to the parent, and the parent has a state determining if we are renaming a folder. The code below does not pre-fill the input, because at the time of creating the dialog component, we are not renaming a folder, and thus the props.currentName is empty.

If i add

if(props.currentName !== newName){
    setNewName(props.currentName);
}

I can pre-fill the input, but then every call to setName, resets the name to the default value. And I need to call setName on every input, to check if the Ok button should be enabled or not.

type RenameItemDialogProps = {
    itemId: string;
    currentName: string;
    onDone: () => void;
    open: boolean;
}

export const RenameDialog = (props: RenameDialogProps) => {   
    const storageContext = useContext(StorageContext);

    const styles = useStyles();
    const [newName, setNewName] = useState<string>(props.currentName);
    const [isUpdating, setIsUpdating] = useState(false);
    const [nameLength, setNameLength] = useState<number>(props.currentName.length);

    async function onOk() {
        //Update name through API
    }

    return (
        <Dialog open={props.open}>
            <DialogSurface aria-describedby={undefined}>
                <DialogBody>
                    <DialogTitle>Rename</DialogTitle>
                    <DialogContent className={styles.content}>
                        <Label required htmlFor={"name-input"}>
                            Name
                        </Label>
                        <Input value={newName} autoComplete="off" minLength={3} required type="text" id={"name-input"} onInput={(event: React.FormEvent) => {
                            const target = event.target as HTMLInputElement;
                            setNameLength(target.value.length);
                        }} />
                    </DialogContent>
                    <DialogActions>
                        <Button appearance="secondary" onClick={() => {
                            props.onDone();
                        }}>Cancel</Button>
                        <Button disabled={newName.length < 3 || isUpdating} appearance="primary" onClick={async () => {
                            setIsUpdating(true);
                            await onOk();
                            setIsUpdating(false);
                            props.onDone();
                        }}>
                            Update
                        </Button>
                    </DialogActions>
                </DialogBody>
            </DialogSurface>
        </Dialog>
    );
};

Solution

  • You may use the useEffect hook to set the initial value of newName only when props.currentName changes, which will solve the problems of pre-filling the input field with the current name and guaranteeing the input is updated appropriately without resetting. In this manner, when the dialog box opens, the input field will already be filled in, and any modifications you make later won't cause it to revert to its original value.

    export const RenameDialog = (props: RenameItemDialogProps) => {   
      const storageContext = useContext(StorageContext);
    
      const styles = useStyles();
      const [newName, setNewName] = useState<string>(props.currentName);
      const [isUpdating, setIsUpdating] = useState(false);
    
      useEffect(() => {
        if (props.open) {
          setNewName(props.currentName);
        }
      }, [props.currentName, props.open]);
    
      async function onOk() {
        // Update name through API
        storageContext.updateName(props.itemId, newName);
      }
    
      return (
        <Dialog open={props.open}>
          <DialogSurface aria-describedby={undefined}>
            <DialogBody>
              <DialogTitle>Rename</DialogTitle>
              <DialogContent className={styles.content}>
                <Label required htmlFor={"name-input"}>
                  Name
                </Label>
                <Input
                  value={newName}
                  autoComplete="off"
                  minLength={3}
                  required
                  type="text"
                  id={"name-input"}
                  onInput={(event: React.FormEvent) => {
                    const target = event.target as HTMLInputElement;
                    setNewName(target.value);
                  }}
                />
              </DialogContent>
              <DialogActions>
                <Button appearance="secondary" onClick={props.onDone}>
                  Cancel
                </Button>
                <Button
                  disabled={newName.length < 3 || isUpdating}
                  appearance="primary"
                  onClick={async () => {
                    setIsUpdating(true);
                    await onOk();
                    setIsUpdating(false);
                    props.onDone();
                  }}
                >
                  Update
                </Button>
              </DialogActions>
            </DialogBody>
          </DialogSurface>
        </Dialog>
      );
    };