Skip to content
Advertisement

Multiple buttons triggering the same modal component

I have an videos array, which in turn has objects of type Video (typing below).

I need that when clicking on the button corresponding to a specific video, I can open only one modal with the information of the clicked video.

interface VideosInfo {
  id: number;
  title: string;
  url: string;
  quiz: boolean;
}

interface PagePros {
  videos: VideosInfo[]
}

Below is the component that renders the array of videos through a map, notice that inside the map, I have an onClick function that calls the modal.

import { VideoModal } from '../index';
import { useVideos } from '../../../../hooks/Videos';

export const Videos: React.FC<VideoProps> = ({ module_id }) => {
  const [modalOpen, setModalOpen] = useState<boolean>(false);

  const { getVideos, videos, loadingVideos } = useVideos();

  const handleCloseModal = () => {
    setModalOpen(false);
  };

  const VideosData = () => {
    if (videos.length) {
      return (
        <List dense>
          {videos?.map(video => (
            <div key={video.id}>
              <ListItem onClick={() => setModalOpen(true)} button>
                <ListItemText primary={video.title} />
              </ListItem>
              <Divider />

              <VideoModal
                open={modalOpen}
                handleClose={() => handleCloseModal()}
                video={video}
                video_id={video.id}
              />
            </div>
          ))}
        </List>
      );
    }

    if (!videos.length && !loadingVideos) {
      return (
        <Typography variant="body1">
          Não existem vídeos cadastrados neste módulo.
        </Typography>
      );
    }

    return <LoadingScreen text="Carregando vídeos..." />;
  };

  useEffect(() => {
    getVideos(module_id);
  }, [module_id, getVideos]);

  return (
    <Grid container spacing={2}>
      <Grid item xs={12} md={12}>
        <VideosData />
      </Grid>

      <Grid item xs={12} md={12}>
        <Button variant="text" color="primary">
          Novo Vídeo
        </Button>
      </Grid>
    </Grid>
  );
};

And below the VideoModal component:

export const VideoModal: React.FC<ModalProps> = ({
  video,
  open,
  handleClose,
  video_id,
}) => {
  console.log('videos modal', video);

  return (
    <Dialog
      open={open}
      aria-labelledby="form-dialog-title"
      onClose={handleClose}
    >
      <DialogTitle id="form-dialog-title">Subscribe</DialogTitle>
      <DialogContent>
        <h2>test</h2>
      </DialogContent>
    </Dialog>
  );
};

I understand that the modal uses the “open” property to define whether it is open or not, but when I click the button and perform the setModalOpen, it renders a modal for each object in the array. I don’t understand how I could assemble this correctly.

Advertisement

Answer

I solved it as follows, created a state called videoToModal of type VideosInfo and a function called handleModalOpen, passed the video parameter to the function, and in the function stored this video in the videoToModal state.

I instantiated the VideoModal component outside the map (obviously should have done this before) and passed the state to the VideoModal component’s video parameter.

Below is the complete code for the component.

import React, { useEffect, useState } from 'react';
import {
  Button,
  Divider,
  Grid,
  IconButton,
  List,
  ListItem,
  ListItemSecondaryAction,
  ListItemText,
  Tooltip,
  Typography,
} from '@material-ui/core';
import { Delete, QuestionAnswer } from '@material-ui/icons';

import { useVideos } from '../../../../hooks/Videos';
import { useStyles } from './styles';

import { LoadingScreen } from '../../../../components/CustomizedComponents';
import { VideoModal } from '../index';

import { VideosInfo } from '../../../../hooks/Videos/types';
import { VideoProps } from './types';

export const Videos: React.FC<VideoProps> = ({ module_id }) => {
  const [openModal, setOpenModal] = useState<boolean>(false);
  const [videoToModal, setVideoToModal] = useState<VideosInfo>();

  const classes = useStyles();
  const { getVideos, videos, loadingVideos } = useVideos();

  const handleCloseModal = () => {
    setOpenModal(false);
  };

  const handleOpenModal = (video: VideosInfo) => {
    setVideoToModal(video);
    setOpenModal(true);
  };

  const VideosData = () => {
    if (videos.length) {
      return (
        <List dense>
          {videos?.map(video => (
            <div key={video.id}>
              <ListItem
                className={classes.listItem}
                onClick={() => handleOpenModal(video)}
                button
              >
                <ListItemText
                  primary={video.title}
                  className={classes.listItemText}
                />
                <ListItemSecondaryAction>
                  <Tooltip
                    placement="top"
                    title={
                      video.Quizzes?.length
                        ? 'Clique para ver as perguntas'
                        : 'Clique para iniciar o cadastro de perguntas'
                    }
                  >
                    <IconButton edge="end" aria-label="delete">
                      <QuestionAnswer
                        color={video.Quizzes?.length ? 'primary' : 'action'}
                      />
                    </IconButton>
                  </Tooltip>

                  <Tooltip placement="top" title="Deletar Vídeo">
                    <IconButton edge="end" aria-label="delete">
                      <Delete color="secondary" />
                    </IconButton>
                  </Tooltip>
                </ListItemSecondaryAction>
              </ListItem>
              <Divider />
            </div>
          ))}
          <VideoModal
            open={openModal}
            handleClose={() => handleCloseModal()}
            video={videoToModal}
          />
        </List>
      );
    }

    if (!videos.length && !loadingVideos) {
      return (
        <Typography variant="body1">
          Não existem vídeos cadastrados neste módulo.
        </Typography>
      );
    }

    return <LoadingScreen text="Carregando vídeos..." />;
  };

  useEffect(() => {
    getVideos(module_id);
  }, [module_id, getVideos]);

  return (
    <Grid container spacing={2} className={classes.container}>
      <Grid item xs={12} md={12}>
        <VideosData />
      </Grid>

      <Grid item xs={12} md={12}>
        <Button variant="text" color="primary">
          Novo Vídeo
        </Button>
      </Grid>
    </Grid>
  );
};
Advertisement