javascript - ReactJs-modal i loop


Jag har följt denna React-modalhandledning på ett löst sätt.
När jag lägger modalen inuti en loop med flera länkar och öppnar modalen, öppnas alla modaler (naturligtvis). Jag försöker lista ut hur man öppnar rätt modal (för id:t)

Alert-komponenten med Modal i loop

export default function Alert({}: Props) {
  const { alerts, loading } = useGetAlerts();
  const { isShown, toggle } = useModal();

  return (
    <div className={styles.alertHeader}>
      <div className={styles.title}>Alerts</div>
      <div className={styles.alertFrame}>
        <div className={styles.counter}>{`${alerts?.items.length}`}</div>
        <div className={styles.titleFrame}>
          <div className={styles.summaryTitle}>
            Outstanding <br></br>Alerts
          </div>
        </div>
      </div>
      {loading ? (
        <SnakeLoader />
      ) : (
        alerts?.items.slice(0, 5).map((a) => (
          <div key={`id_${a}`} className={styles.alertFrame}>
            <div className={styles.titleFrame} id="titleFrame">
              <div className={styles.alertTitle} id="alertTitle">
                {' '}
                {`${a.message}`}{' '}
              </div>
              <div className={styles.alertLink} id="alertLink">
                <button onClick={toggle}>Click for More</button>
                <Modal
                  isShown={isShown}
                  hide={toggle}
                  modalContent={a.message}
                  headerText={a.id}
                />
              </div>
            </div>
            <div className={styles.alertClose} id="alertClose">
              X
            </div>
          </div>
        ))
      )}
    </div>
  );
}

Modal-komponenten

import React, { FunctionComponent, useEffect } from 'react';
import ReactDOM from 'react-dom';
import styles from './Modal.module.scss';
//https://nainacodes.com/blog/create-an-accessible-and-reusable-react-modal

export interface ModalProps {
  isShown: boolean;
  hide: () => void;
  modalContent: string;
  headerText: string;
}

export const Modal: FunctionComponent<ModalProps> = ({
  isShown,
  hide,
  modalContent,
  headerText,
}) => {
  const modal = (
    <div className={styles.modalContainer}>
      <div className={styles.styledModal}>
        <div className={styles.header}>
          <div className={styles.headerText}>{headerText}</div>
          <button className={styles.closeButton} onClick={hide}>
            X
          </button>
        </div>
        <div className={styles.content}>{modalContent}</div>
      </div>
    </div>
  );

  return isShown ? ReactDOM.createPortal(modal, document.body) : null;
};

Metoden useModal

export const useModal = () => {
  const [isShown, setIsShown] = useState<boolean>(false);
  const toggle = () => setIsShown(!isShown);
  const id = '';
  return {
    isShown,
    toggle,
    id
  };
};

Mycket ny på React, all hjälp uppskattas mycket

Svar #1

För närvarande använder varje av dina modaler samma del av state för att växla dess synlighet, isShown. Och toggle är funktionen som ändrar det state. För att kunna styra modalerna individuellt behöver vi en separat del av state och en separat funktion för att växla det state för varje individuell modal du har skapat. Ett alternativ kan vara att ta bort useModal-hooken och flytta både isShown och toggle in i Modal-komponenten själv. På så sätt när varje Modal-komponent skapas, har den sitt eget state istället för att dela det från den hooken.

Svar #2

I princip finns det ingen anledning att rendera så många Modal-komponenter, rendera bara en av dem.

Lägg till 1 extra tillståndsvariabel som kommer att hålla activeAlertItem för modal. Denna variabel kommer att sättas av Click for More-knappens klick för angiven alert.

När det väl är inställt - är du fri att använda antingen villkorsstyrd rendering dvs

{activeAlertItem && (
  <Modal
    isShown={true}
    hide={() => setActiveAlertItem(undefined)}
    modalContent={activeAlertItem.message}
    headerText={activeAlertItem.id}
  />
)}

eller vad annat du föredrar.

Här är ett exempel (med manuella dummydata, förlåt):

interface IAlertItem {
  id: string;
  message: string;
}

const alertsItems: IAlertItem[] = [
  { id: "1", message: "test 1" },
  { id: "2", message: "test 2" },
  { id: "3", message: "test 3" }
];
const alerts = {
  items: alertsItems
};

export default () => {
  const [loading, setLoading] = useState(false);
  const [activeAlertItem, setActiveAlertItem] = useState<IAlertItem | undefined>(undefined);

  return (
    <div className={styles.alertHeader}>
      <div className={styles.title}>Alerts</div>
      <div className={styles.alertFrame}>
        <div className={styles.counter}>{`${alerts?.items.length}`}</div>
        <div className={styles.titleFrame}>
          <div className={styles.summaryTitle}>
            Outstanding <br></br>Alerts
          </div>
        </div>
      </div>
      {loading ? (
        <SnakeLoader />
      ) : (
        alerts?.items.slice(0, 5).map((a) => (
          <div key={`id_${a}`} className={styles.alertFrame}>
            <div className={styles.titleFrame} id="titleFrame">
              <div className={styles.alertTitle} id="alertTitle">
                {" "}
                {`${a.message}`}{" "}
              </div>
              <div className={styles.alertLink} id="alertLink">
                <button onClick={() => setActiveAlertItem(a)}>
                  Click for More
                </button>
              </div>
            </div>
            <div className={styles.alertClose} id="alertClose">
              X
            </div>
          </div>
        ))
      )}

      {activeAlertItem && (
        <Modal
          isShown={true}
          hide={() => setActiveAlertItem(undefined)}
          modalContent={activeAlertItem.message}
          headerText={activeAlertItem.id}
        />
      )}
    </div>
  );
};