Johnny Foxville
Johnny Foxville

Reputation: 103

Modal in table is opened for every item in table

Issue as stated in topic. Goal is to open a modal, and use item.imdbID to fetch data. As it is now, a modal is opened for every entry in the table, and also the id prop is sent for every entry.

const { toggle, visible } = useModal();


<tbody>
    {items.map((item, i) => (
        <tr key={i}>
            <MTd>{item.Title}</MTd>
            <MTd>{item.Year}</MTd>
            <MTd>{item.imdbID}</MTd>
            <MTd>{item.Type}</MTd>
            <MTd><button onClick={toggle}>Detaljer</button>
                <DetailedViewModal visible={visible} toggle={toggle} id={item.imdbID} />
            </MTd>
        </tr>
    ))}
</tbody>

I know one can do <button onClick={e => testClick(item.imdbID)}>Details</button> when the function resides in the same file, but unsure on how to handle this with a modal.

The modal is custom made; useModal.js:

const useModal = () => {
  const [visible, setVisible] = useState(false);
  function toggle() {
    setVisible(!visible);    
  }
  return {toggle, visible}
};

Modal.js

const Modal = ({ visible, toggle, body }) => visible ? ReactDOM.createPortal(
    <div className="modal">
        <div className="modal-pop" role="dialog" aria-modal="true">
            <div>{body}</div>
            <button type="button" onClick={toggle}>Close</button>
        </div>
        <div className="modal-overlay"></div>
    </div>, document.body
) : null;

DetailedViewModal.js

return (
    <>
    <Modal visible={visible} toggle={toggle} body={modalBody()} /> 
    </>
)

Any good suggestions to make this work is much appreciated.

Upvotes: 1

Views: 182

Answers (1)

Drew Reese
Drew Reese

Reputation: 202676

Issue

You are using a single toggle state to toggle visibility of all the modals.

Solution

Store a passed id property for the modal you want to toggle visibility of.

useModal:

Update to either toggle back to null if the id matches to close the modal, or to toggle on a new id. Note: toggle is a curried function so you don't need to create anonymous callback functions to pass the id when attaching callback.

const useModal = () => {
  const [visibleId, setVisible] = useState(null);
  const toggle = id => () => setVisible(visibleId => visibleId === Id ? null : id);
  return {toggle, visibleId}
};

Component Code:

Pass item.imdbID or null to the toggle callback handler, check the current item.imdbID equals the current visibleId value to set the visibility of the modal.

const { toggle, visibleId } = useModal();

...

<tbody>
  {items.map((item, i) => (
    <tr key={i}>
      <MTd>{item.Title}</MTd>
      <MTd>{item.Year}</MTd>
      <MTd>{item.imdbID}</MTd>
      <MTd>{item.Type}</MTd>
      <MTd>
        <button
          onClick={toggle(item.imdbID)} // <-- pass id to handler to open (or close)
        >
          Detaljer
        </button>
        <DetailedViewModal
          visible={visibleId === item.imdbID} // <-- check id match for opening modal
          toggle={toggle(null)} // <-- pass null to close
          body={{ /* ... whatever the body is ... */ }}
        />
      </MTd>
    </tr>
  ))}
</tbody>

Upvotes: 1

Related Questions