MUI popover's prop transformOrigin is not working

Viewed 32

There is a button which opens a popover when clicked. The popover contains table. transformOrigin prop of popover is not working. In initial reload, the popover shows in correct position.(Also when I resize window while popover is opened, it again shifts to correct position)enter image description here

But after popover is close and opened again, it moves to the undesired position(even outside the screen due to position of button). enter image description here

Here is the code for button component. Also contains popover

import Button from "@mui/material/Button";
import Popover from "@mui/material/Popover";
import { useState } from "react";
import ChooseRacesPanel from "./ChooseRacesPanel";

export default function ChooseRacesBtn({ date, setIndex }) {
    const [anchorEl, setAnchorEl] = useState(null);

    const handleClick = (event) => {
        setAnchorEl(event.currentTarget);
    };

    const handleClose = () => {
        setAnchorEl(null);
    };

    const open = Boolean(anchorEl);

    return (
        <div>
            <Button sx={{ m: 2 }} variant="contained" onClick={handleClick}>
                Choose Races
            </Button>
            <Popover
                open={open}
                anchorEl={anchorEl}
                onClose={handleClose}
                anchorOrigin={{
                    vertical: "bottom",
                    horizontal: "right",
                }}
                transformOrigin={{
                    vertical: "top",
                    horizontal: "right",
                }}
            >
                <ChooseRacesPanel date={date} setIndex={setIndex} />
            </Popover>
        </div>
    );
}

Here is the table component(ChooseRacesPanel), used inside popover:

import Table from "@mui/material/Table";
import TableCell from "@mui/material/TableCell";
import TableBody from "@mui/material/TableBody";
import TableContainer from "@mui/material/TableContainer";
import TableRow from "@mui/material/TableRow";
import Paper from "@mui/material/Paper";
import { useState, useEffect } from "react";
import { NavLink } from "react-router-dom";

export default function ChooseRacesPanel({ date, setIndex }) {
    const [meetingNames, setMeetingNames] = useState([]);
    const [meetingsRaces, setMeetingsRaces] = useState([]);
    useEffect(() => {
        const fetchData = async () => {
            const response = await fetch(
                `http://localhost:3000/meeting/${date}`,
                {
                    method: "GET",
                    headers: { "Content-Type": "application/json" },
                }
            );
            const json = await response.json();
            console.log(json);
            setMeetingNames(json.meetingNames);
            setMeetingsRaces(json.meetingRaces);
        };
        fetchData();
    }, [date]);
    return (
        <TableContainer component={Paper}>
            <Table>
                <TableBody>
                    {meetingNames.map((mName, index) => (
                        <TableRow
                            key={index}
                            sx={{
                                "&:last-child td, &:last-child th": {
                                    border: 0,
                                },
                            }}
                        >
                            <TableCell align="center">{mName}</TableCell>
                            {meetingsRaces[index].map((race, i) => (
                                <TableCell key={i} align="center" onClick={() => setIndex(parseInt(race.index))}>
                                    <NavLink
                                        to={race.link}
                                        style={({ isActive }) => ({
                                            color: isActive ? '#fff' : '#545e6f',
                                            background: isActive ? '#ee9154' : '#f0f0f0',
                                          })}
                                    >
                                        {race.time}
                                    </NavLink>
                                </TableCell>
                            ))}
                        </TableRow>
                    ))}
                </TableBody>
            </Table>
        </TableContainer>
    );
}
1 Answers

I've reproduced the problematic case in this sandbox.

The problem is related to the change in width of your component after it is rendered inside Popover. Popover should be rerendered after the table data is retrieved in useEffect in ChooseRacesPanel component.

In order to do that, either:

  1. You can move fetchData to the ChooseRacesBtn component (or even further) and pass meetingNames down to the ChooseRacesPanel all the way down.
  2. You can call a callback function from ChooseRacesPanel and update the state of ChooseRacesBtn by calling this callback function after the data is fetched like this:

ChooseRacesPanel.js:

export default function ChooseRacesPanel({ date, setIndex, onItemsRetrieved }) {
    const [meetingNames, setMeetingNames] = useState([]);
    const [meetingsRaces, setMeetingsRaces] = useState([]);
    useEffect(() => {
        const fetchData = async () => {
            const response = await fetch(
                `http://localhost:3000/meeting/${date}`,
                {
                    method: "GET",
                    headers: { "Content-Type": "application/json" },
                }
            );
            const json = await response.json();
            console.log(json);
            setMeetingNames(json.meetingNames);
            setMeetingsRaces(json.meetingRaces);
            onItemsRetrieved(); // this is callback after fetch
        };
        fetchData();
    }, [date]);


export default function ChooseRacesBtn({ date, setIndex }) {
    const [anchorEl, setAnchorEl] = useState(null);
    const [itemsRetrieved, setItemsRetrieved] = useState(false);

    const handleClick = (event) => {
        setAnchorEl(event.currentTarget);
    };

    const handleClose = () => {
        setItemsRetrieved(false);
        setAnchorEl(null);
    };

    const onItemsRetrieved = () => {
        setItemsRetrieved(true);
    };

    const open = Boolean(anchorEl);

    return (
        <div>
            <Button sx={{ m: 2 }} variant="contained" onClick={handleClick}>
                Choose Races
            </Button>
            <Popover
                open={open}
                anchorEl={anchorEl}
                onClose={handleClose}
                anchorOrigin={{
                    vertical: "bottom",
                    horizontal: "right",
                }}
                transformOrigin={{
                    vertical: "top",
                    horizontal: "right",
                }}
            >
                <ChooseRacesPanel date={date} setIndex={setIndex} itemsRetrieved={itemsRetrieved}
                    onItemsRetrieved={onItemsRetrieved} />
            </Popover>
        </div>
    );
}

You can take a look at this sandbox for a live working example of the second alternative solution.

Related