0

Summary

I want to add a quick action button on a record show view that will display a dialog with a form field.

When the form is submitted, I want the data provided to dispatch an update action with the form field data plus a few option.

I know how to create action button that will dispatch update action on data provider, but I am stuck about how to handle a form on a dialog.

Tries (and failures)

So I started by creating a button displaying a dialog with a TextInput (from react-admin) on it:

const ValidateButton: FC<ButtonProps> = ({ record }) => {
  const [open, setOpen] = useState<boolean>(false);

  const handleInit = () => {
    setOpen(true)
  }

  const handleCancel = () => {
    setOpen(false);
  }

  const handleSubmit = () => {
    setOpen(false);
  }

  return (
    <>
      <Button
        label="Validate"
        onClick={handleInit}
      >
        <ValidateIcon />
      </Button>
      <Dialog
        open={open}
      >
        <DialogTitle>Validate</DialogTitle>
        <DialogContent>
          <TextInput
            source="comment"
            label="Validation comment"
          />
        </DialogContent>
        <DialogActions>
          <Button
            label="Cancel"
            onClick={handleCancel}
          />
          <Button
            label="Validate"
            onClick={handleSubmit}
          />
        </DialogActions>
      </Dialog>
    </>
  );
}

This gave me the following error:

Error: useField must be used inside of a <Form> component

Well. Pretty clear. So I tried wrapping it on a SimpleForm component:

<DialogContent>
  <SimpleForm
    form={createForm({ onSubmit: handleSubmit })}
    resource="rides"
    record={record}
  >
    <TextInput
      source="comment"
      label="Validation comment"
    />
  </SimpleForm>
</DialogContent>

Then I got:

TypeError: can't access property "save", context is undefined
useSaveContext
node_modules/ra-core/esm/controller/details/SaveContext.js:23

  20 |  */
  21 | export var useSaveContext = function (props) {
  22 |     var context = useContext(SaveContext);
> 23 |     if (!context.save || !context.setOnFailure) {
  24 |         /**
  25 |          * The element isn't inside a <SaveContextProvider>
  26 |          * To avoid breakage in that case, fallback to props

The ValidateButton is implemented on the Show view toolbar:

const RideShowActions: FC<ShowActionsProps> = ({ basePath, data }) => {
  if (!data) {
    return null;
  }

  return (
    <TopToolbar>
      <ValidateButton />
      {[
        'created',
        'validated',
        'confirmed',
      ].includes(data?.status) && <CancelButton basePath={basePath} record={data} />}
      <EditButton basePath={basePath} record={data} />
    </TopToolbar>
  );
};

export const RideShow: FC<ShowProps> = (props) => (
  <Show
    title={<RideTitle />}
    actions={<RideShowActions />}
    {...props}
  >
    <SimpleShowLayout>
      // Show classic stuff.
    </SimpleShowLayout>
  </Show>
);

Question

I suppose the last error is because the SimpleForm needs to be placed on <Edit> or similar.

My goal is just to have an input with the same functionnality and UX/UI design than the edit form on a dialog and do custom actions.

What I am missing? How can I achieve that?

Note: I tried to directly use the TextField component of @material-ui/core. It works, but I loose all the react-admin functionnalities that will be useful to me like for ReferenceInput.

1 Answer 1

1

I finally found the a solution by creating a custom DialogForm component, combining the common logic like the form itself and the action buttons:

import { FC, MouseEventHandler } from 'react';
import { Button } from 'react-admin';
import { Dialog, DialogProps, DialogTitle, DialogContent, DialogActions } from '@material-ui/core'
import { Form } from 'react-final-form';
import { Config } from 'final-form';

export interface DialogFormProps {
  open: DialogProps['open'],
  loading?: boolean;
  onSubmit: Config['onSubmit'],
  onCancel: MouseEventHandler,
  title?: string,
  submitLabel?: string;
  cancelLabel?: string;
}

export const DialogForm: FC<DialogFormProps> = ({
  open,
  loading,
  onSubmit,
  onCancel,
  title,
  cancelLabel,
  submitLabel,
  children,
}) => {
  return (
    <Dialog
      open={open}
    >
        <Form
          onSubmit={onSubmit}
          render={({handleSubmit}) => (
            <form onSubmit={handleSubmit}>
              {title && (
                <DialogTitle>
                  {title}
                </DialogTitle>
              )}
              <DialogContent>
                {children}
              </DialogContent>
              <DialogActions>
                <Button
                  label={cancelLabel || 'Cancel'}
                  onClick={onCancel}
                  disabled={loading}
                />
                <Button
                  label={submitLabel || 'Validate'}
                  type="submit"
                  disabled={loading}
                />
              </DialogActions>
            </form>
          )}
        />

    </Dialog>
  )
};

export default null;

You can copy/paste the example above to have it working out of the box.

Here is an implementation example with a button:

const ValidateButton: FC<ButtonProps> = ({ record }) => {
  const [open, setOpen] = useState<boolean>(false);
(undefined);
  const [mutate, { loading }] = useMutation();
  const notify = useNotify();
  const refresh = useRefresh();

  const handleInit = () => {
    setOpen(true)
  }

  const handleCancel = () => {
    setOpen(false);
  }

  const handleSubmit: DialogFormProps['onSubmit'] = (values) => {
    console.log(values);
    mutate(
      // Your mutation logic.
    );
  }

  return (
    <>
      <Button
        label="Validate"
        onClick={handleInit}
      >
        <ValidateIcon />
      </Button>
      <DialogForm
        open={open}
        loading={loading}
        onSubmit={handleSubmit}
        onCancel={handleCancel}
        title="Validate that thing"
        submitLabel="Let's go!"
      >
        <DialogContentText>
          Some text.
        </DialogContentText>
        <ReferenceInput
          source="referenceId"
          label="Reference"
          reference="service"
          filter={{ userId: record?.customerId }}
        >
          <SelectInput
            optionText="name"
            resettable
            fullWidth
          />
        </ReferenceInput>
      </DialogForm>
    </>
  );
}

Hope it will help somehow!

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.