Mike Ross
Mike Ross

Reputation: 165

React. Passing Functions For A Modal

I am trying to Close a modal from a child component of the modal component, my problem is, I have to declare a function on the grand parent component for the gradchild component and my code to update it isn't working. My test app uses react-bootstrap and currently has no store lib. What I am trying todo is nest a "form" component within a "modal" component and pass the closing function to the form component within the modal component all on a grand parent. the code is as follows:

This is the main component which contains my Modal Component and Form Component

import { useEffect } from "react";
import { GetAllGrades } from "../../../Services/GradeApi";
import FormModal from "../../Layout/Modal/FormModal";
import AddGradeModal from "./AddGradeModal";
import EditGradeModal from "./EditGradeModal";
import GradeForm from "./GradeForm";

const AllGrades = () => {
  const [grades, setGrades] = GetAllGrades();

  useEffect(() => {
    setGrades();
  }, [grades, setGrades]);

  return (
    <table className="table .table-striped ">
      <thead>
        <tr>
          <th scope="col">Name</th>
          <th scope="col">KYU</th>
          <th scope="col">Required Session Time</th>
          <th>
           
          </th>
          <th></th>
        </tr>
      </thead>
      <tbody>
        {grades?.map((grade) => (
          <tr key={grade.id}>
            <th>{grade.name}</th>
            <td>{grade.kyu}</td>
            <td>{grade.requiredLessionTime}</td>
            <td>
             
            </td>
            <td>
              <FormModal ModalTitle="Form Modal" TriggerButtonText="Test">
                <GradeForm Grade={grade} ViewOnly={false}>
                   closeModal={????} </GradeForm>  //not sure how not to set this property for the callback function here
              </FormModal>
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
};

export default AllGrades;

Modal Component - I am trying to add its "handlClose" function to the child component so it can call and close the modal once the for is submitted.

import React, { useEffect, useState } from "react";
import { Button, Modal } from "react-bootstrap";

type FormModalProps = {
  ModalTitle: string;
  TriggerButtonText: string;
  children?: React.ReactNode;
};

const FormModal = (props: FormModalProps) => {
  const { ModalTitle, TriggerButtonText, children } = props;
  const [show, setShow] = useState(false);
  const handleClose = () => setShow(false);
  const handleShow = () => setShow(true);

  const childrenWithProps = React.Children.map(children, (child) => {
    if (React.isValidElement(child)) {
      return React.cloneElement(child, {
        closeModal: { handleClose },
      });
    }
    return child;
  }); // update the children with the correct function
  return (
    <>
      <Button variant="primary" onClick={handleShow}>
        {TriggerButtonText}
      </Button>

      <Modal show={show} onHide={handleClose}>
        <Modal.Header closeButton>
          <Modal.Title>{ModalTitle}</Modal.Title>
        </Modal.Header>
        <Modal.Body>{childrenWithProps}</Modal.Body>
        <Modal.Footer>
          <Button variant="secondary" onClick={handleClose}>
            Close
          </Button>
        </Modal.Footer>
      </Modal>
    </>
  );
};

export default FormModal;

this is the form - on the submit function I want to close the modal, so I pass the "handleClose" function from the parent.

import { useForm, Controller } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";

import { Grade, GradeFormProps } from "../../../Interface";
import { useEffect, useState } from "react";
import { Button, Form } from "react-bootstrap";
import { GradeValidationSchema } from "../../../Services/ValidationSchemas/GradeValidation";

const GradeForm = (props: GradeFormProps) => {
  const [grade, setGrade] = useState<Grade>(props.Grade);
  const { Grade, ViewOnly, closeModal } = props;

  const {
    handleSubmit,
    control,
    formState: { errors },
  } = useForm<Grade>({
    resolver: yupResolver(GradeValidationSchema),
    defaultValues: grade,
  });
  const onSubmit = (data: Grade) => {
    if (data.id === undefined) {
      // AddMember({ Grade: data });
    } else {
      // UpdateMember({ Grade: data });
    }
    console.log("close Form");

    closeModal && closeModal(null);

    // closeModal.setShow(true);
  };

  useEffect(() => {
    setGrade(Grade);
  }, [setGrade, grade, Grade]);

  return (
    <Form onSubmit={handleSubmit(onSubmit)}>
      <fieldset disabled={ViewOnly}>
        <Form.Group className="mb-3" controlId="name">
          <Form.Label>Licence Number</Form.Label>
          <Controller
            control={control}
            name="name"
            defaultValue=""
            render={({ field: { onChange, onBlur, value, ref } }) => (
              <Form.Control
                onChange={onChange}
                value={value}
                ref={ref}
                placeholder="Enter Name"
              />
            )}
          />
          {errors.name && (
            <div className="text-danger">{errors.name?.message}</div>
          )}
        </Form.Group>

        <Form.Group className="mb-3" controlId="kyu">
          <Form.Label>KYU</Form.Label>
          <Controller
            control={control}
            name="kyu"
            render={({ field: { onChange, onBlur, value, ref } }) => (
              <Form.Control
                onChange={onChange}
                value={value}
                ref={ref}
                placeholder="Enter Kyu"
              />
            )}
          />
          {errors.kyu && (
            <div className="text-danger">{errors.kyu?.message}</div>
          )}
        </Form.Group>

        <Form.Group className="mb-3" controlId="requiredLessionTime">
          <Form.Label>Last Name</Form.Label>
          <Controller
            control={control}
            name="requiredLessionTime"
            render={({ field: { onChange, onBlur, value, ref } }) => (
              <Form.Control
                onChange={onChange}
                value={value}
                ref={ref}
                placeholder="Enter required Session Time"
              />
            )}
          />
          {errors.requiredLessionTime && (
            <div className="text-danger">
              {errors.requiredLessionTime?.message}
            </div>
          )}
        </Form.Group>
        {!props.ViewOnly && (
          <Button variant="primary" type="submit">
            Submit
          </Button>
        )}
      </fieldset>
    </Form>
  );
};

export default GradeForm;

Props for the Form

export interface GradeFormProps{
    Grade: Grade,
    ViewOnly: boolean,
    closeModal: (params:any)=>any ;
}

any help would be appreciated.

Upvotes: 0

Views: 498

Answers (1)

Stefan Schmidt
Stefan Schmidt

Reputation: 76

I think, you just have a syntax issue in your FormModal component. handleClose shouldn't be wrapped in an object.

import React, { useEffect, useState } from "react";
import { Button, Modal } from "react-bootstrap";

type FormModalProps = {
  ModalTitle: string;
  TriggerButtonText: string;
  children?: React.ReactNode;
};

const FormModal = (props: FormModalProps) => {
  const { ModalTitle, TriggerButtonText, children } = props;
  const [show, setShow] = useState(false);
  const handleClose = () => setShow(false);
  const handleShow = () => setShow(true);

  const childrenWithProps = React.Children.map(children, (child) => {
    if (React.isValidElement(child)) {
      return React.cloneElement(child, {
        closeModal: handleClose
      });
    }
    return child;
  }); // update the children with the correct function
  return (
    <>
      <Button variant="primary" onClick={handleShow}>
        {TriggerButtonText}
      </Button>

      <Modal show={show} onHide={handleClose}>
        <Modal.Header closeButton>
          <Modal.Title>{ModalTitle}</Modal.Title>
        </Modal.Header>
        <Modal.Body>{childrenWithProps}</Modal.Body>
        <Modal.Footer>
          <Button variant="secondary" onClick={handleClose}>
            Close
          </Button>
        </Modal.Footer>
      </Modal>
    </>
  );
};

export default FormModal;

In the AllGrades component, you don't need to have closeModal in the JSX but this could be an old code fragment.

Additionally, I would recommend to rename the closeModal prop of GradeForm into something like onSuccess or onFinish, as the form doesn't know that it is rendered within a modal or not.

Upvotes: 1

Related Questions