How to handle an event of a child element, on a parent element?

61 Views Asked by At

I have a component that contains a Checkbox component from the @fluentui/react-components library, and I am using an onChange event on the Checkbox component.

const ContactText = ({ contact, contactKey, selectedContacts, setSelectedContacts }) => {

  return (
    <div>
      <img src={contactIcon} alt="Contact Icon" />
      <div>
        <Checkbox
          labelPosition="before"
          label={`${contact.firstname} ${contact.surname}`}
          checked={selectedContacts.filter((contact) => contact.key == contactKey).checked}
          //@ts-ignore - Ignore event parameter on onChange
          onChange={(event, data) => {
            setSelectedContacts((prevState: SelectedContactModel[]) =>
              data.checked
                ? [
                    ...prevState,
                    {
                      key: contactKey,
                      checked: data.checked,
                      companyKey: contact.companyKey,
                      companyName: contact.companyName,
                      individual: contact.individual,
                      title: contact.title,
                      firstname: contact.firstname,
                      surname: contact.surname,
                      telephone: contact.telephone,
                      mobile: contact.mobile,
                      retired: contact.retired,
                      address: contact.address,
                      postcode: contact.postcode,
                      email: contact.email,
                    },
                  ]
                : prevState.filter((contact) => contact.key !== contactKey)
            );
          }}
        />
        {contact.email != "" ? <SecondaryText text={contact.email} /> : <></>}
        {contact.telephone != "" ? <TertiaryText text={contact.telephone} /> : <></>}
        {contact.mobile != "" ? <TertiaryText text={contact.mobile} /> : <></>}
      </div>
    </div>
  );
};

I want to have the onChange to happen when I click the outermost div of the ContactText component. I have seen people using useRef although I am wondering if there are different ways of doing this.

3

There are 3 best solutions below

0
tomtomdam On BEST ANSWER

Based on @Mike K's answer and another answer I found, this is what I came up with. This lets me call onChange from the div element outside of the Checkbox element.

const ContactText = React.forwardRef(
  ({ contact, contactKey, selectedContacts, setSelectedContacts }: any, ref: any) => {
    const [checked, setChecked] = useState(false);

    React.useImperativeHandle(
      ref,
      () => {
        return {
          onChange: handleChange,
        };
      },
      []
    );

    // @ts-ignore: next-line - Ignore event parameter on onChange
    const handleChange = (event, data) => {
      setChecked(!checked);

      setSelectedContacts((prevState: SelectedContactModel[]) =>
        data.checked
          ? [
              ...prevState,
              {
                key: contactKey,
                checked: data.checked,
                companyKey: contact.companyKey,
                companyName: contact.companyName,
                individual: contact.individual,
                title: contact.title,
                firstname: contact.firstname,
                surname: contact.surname,
                telephone: contact.telephone,
                mobile: contact.mobile,
                retired: contact.retired,
                address: contact.address,
                postcode: contact.postcode,
                email: contact.email,
              },
            ]
          : prevState.filter((contact) => contact.key !== contactKey)
      );
    };

    return (
      <div ref={ref}>
        <img src={contactIcon} alt="Contact Icon" />
        <div>
          <Checkbox
            labelPosition="after"
            label={`${contact.firstname} ${contact.surname}`}
            checked={selectedContacts.filter((contact: SelectedContactModel) => contact.key === contactKey).checked}
            onChange={handleChange}
          />
          {contact.email != "" ? <SecondaryText text={contact.email} /> : <></>}
          {contact.telephone != "" ? <TertiaryText text={contact.telephone} /> : <></>}
          {contact.mobile != "" ? <TertiaryText text={contact.mobile} /> : <></>}
        </div>
      </div>
    );
  }
);
0
cbr On

You can either add a onClick etc on the div element and do what you do inside the onChange in the Checkbox (although you will need to compute checked like you do in the render), or you can just change the <div> into a <label> as clicking on a label will delegate the click to whichever <input> element it wraps (or has a for with a matching ID).

 const ContactText = ({ contact, contactKey, selectedContacts, setSelectedContacts }) => {
  return (
    <label> {/* <------------ */}
      <img src={contactIcon} alt="Contact Icon" />
      <div>
        <Checkbox
          labelPosition="before"
          label={`${contact.firstname} ${contact.surname}`}
          checked={selectedContacts.filter((contact) => contact.key == contactKey).checked}
          //@ts-ignore - Ignore event parameter on onChange
          onChange={(event, data) => {
            setSelectedContacts((prevState: SelectedContactModel[]) =>
              data.checked
                ? [
                    ...prevState,
                    {
                      key: contactKey,
                      checked: data.checked,
                      companyKey: contact.companyKey,
                      companyName: contact.companyName,
                      individual: contact.individual,
                      title: contact.title,
                      firstname: contact.firstname,
                      surname: contact.surname,
                      telephone: contact.telephone,
                      mobile: contact.mobile,
                      retired: contact.retired,
                      address: contact.address,
                      postcode: contact.postcode,
                      email: contact.email,
                    },
                  ]
                : prevState.filter((contact) => contact.key !== contactKey)
            );
          }}
        />
        {contact.email != "" ? <SecondaryText text={contact.email} /> : <></>}
        {contact.telephone != "" ? <TertiaryText text={contact.telephone} /> : <></>}
        {contact.mobile != "" ? <TertiaryText text={contact.mobile} /> : <></>}
      </div>
    </label>
  );
};
0
Mike K On

Try this

const ContactText = React.forwardRef<{ onChange: (event: any, data: any) => void; scrollIntoViewOnClick: () => void }, HTMLDivElement>(({ contact, contactKey, selectedContacts, setSelectedContacts }, ref) => {
  React.useImperativeHandle((ref) => ({
    // You could name this whatever you want, we'll call it 'onChange'
    onChange: handleChange,
    // Just for the sake of this example and what 'useImperativeHandle' is used for
    scrollIntoViewOnClick: () => {
      // Logic to query for your div on the page and then scroll into view
      console.log('should scroll into view');
    }
  }));

  const handleChange = (event, data) => {
            setSelectedContacts((prevState: SelectedContactModel[]) =>
              data.checked
                ? [
                    ...prevState,
                    {
                      key: contactKey,
                      checked: data.checked,
                      companyKey: contact.companyKey,
                      companyName: contact.companyName,
                      individual: contact.individual,
                      title: contact.title,
                      firstname: contact.firstname,
                      surname: contact.surname,
                      telephone: contact.telephone,
                      mobile: contact.mobile,
                      retired: contact.retired,
                      address: contact.address,
                      postcode: contact.postcode,
                      email: contact.email,
                    },
                  ]
                : prevState.filter((contact) => contact.key !== contactKey)
            );
          };

  return (
    <div ref={ref}>
      <img src={contactIcon} alt="Contact Icon" />
      <div>
        <Checkbox
          labelPosition="before"
          label={`${contact.firstname} ${contact.surname}`}
          checked={selectedContacts.filter((contact) => contact.key == contactKey).checked}
          //@ts-ignore - Ignore event parameter on onChange
          onChange={handleChange}
        />
        {contact.email != "" ? <SecondaryText text={contact.email} /> : <></>}
        {contact.telephone != "" ? <TertiaryText text={contact.telephone} /> : <></>}
        {contact.mobile != "" ? <TertiaryText text={contact.mobile} /> : <></>}
      </div>
    </div>
  );
});

useImperativeHandle enables you to expose methods that only you want to be available outside of your component.