Reputation: 123
I have this RenamePopover component with an input field, which I'm using inside a map function located in the Board componenent. As I loop over the array, I'm passing "board.name" into the RenamePopover as a value prop, so that in the end, every rendered element would have its own popover with its input field prepopulated with the name. Unfortunately, that's not the case, since after rendering every input field is set to the name of the last element in the array. What I am missing?
Board.js
import React from 'react'
import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { getActiveBoard } from '../../state/action-creators/activeBoardActions';
import RenamePopover from '../popovers/RenamePopover';
const Board = () => {
const dispatch = useDispatch();
const [anchorEl, setAnchorEl] = React.useState(null);
const boards = useSelector((state) => state.board.items);
const open = Boolean(anchorEl);
useEffect(() => {
dispatch(getActiveBoard());
}, [boards])
const handleClick = (id) => {
const anchor = document.getElementById(`board-anchor${id}`);
setAnchorEl(anchor);
};
const handleClose = () => {
setAnchorEl(null);
};
const single_board = boards && boards.map((board) => {
return (
<li key={board.id} className={`row hovered-nav-item board-item ${active_board == board.id ? "item-selected" : ""}`}>
<span className="d-flex justify-content-between">
<div onClick={() => onBoardClick(board.id)} className="fs-5 text-white board-name" id={`board-anchor${board.id}`}>
{board.name}
</div>
<svg onClick={() => handleClick(board.id)} xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
className="bi bi-pen-fill rename-add-icon rename-add-icon-sidebar"
viewBox="0 0 16 16">
<path
d="m13.498.795.149-.149a1.207 1.207 0 1 1 1.707 1.708l-.149.148a1.5 1.5 0 0 1-.059 2.059L4.854 14.854a.5.5 0 0 1-.233.131l-4 1a.5.5 0 0 1-.606-.606l1-4a.5.5 0 0 1 .131-.232l9.642-9.642a.5.5 0 0 0-.642.056L6.854 4.854a.5.5 0 1 1-.708-.708L9.44.854A1.5 1.5 0 0 1 11.5.796a1.5 1.5 0 0 1 1.998-.001z" />
</svg>
<RenamePopover
open={open}
anchorEl={anchorEl}
onClose={handleClose}
placeholder="Enter new name"
value={board.name}
/>
</span>
</li>
)
})
return(
<div className="container" id="sidebar-boards">
{single_board}
</div>
)
}
export default Board
RenamePopover.js
import { Popover } from '@material-ui/core'
import React from 'react'
const RenamePopover = (props) => {
const [value, setValue] = React.useState(props.value);
const handleChange = (e) => {
setValue(e.target.value);
}
return (
<Popover
open={props.open}
anchorEl={props.anchorEl}
onClose={props.onClose}
anchorOrigin={{
vertical: 'center',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'center',
horizontal: 'left',
}}
>
<input autoFocus={true} className="card bg-dark text-light add-input" type="text"
placeholder={props.placeholder}
value={value}
onChange={handleChange}
/>
</Popover>
)
}
export default RenamePopove
r
Upvotes: 1
Views: 2256
Reputation: 5497
You have one common anchorElement state and it is being used for all the Popovers. Create a new component called BoardItem which is responsible for rendering each board item and move the anchorEl state inside it. This guarantees that each BoardItem will have its own state for the anchorEl .
const BoardItem = ({ board }) => {
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
return (
<li>
<span className="d-flex justify-content-between">
<div>{board.name}</div>
<svg
onClick={handleClick}
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
className="bi bi-pen-fill rename-add-icon rename-add-icon-sidebar"
viewBox="0 0 16 16"
>
<path d="m13.498.795.149-.149a1.207 1.207 0 1 1 1.707 1.708l-.149.148a1.5 1.5 0 0 1-.059 2.059L4.854 14.854a.5.5 0 0 1-.233.131l-4 1a.5.5 0 0 1-.606-.606l1-4a.5.5 0 0 1 .131-.232l9.642-9.642a.5.5 0 0 0-.642.056L6.854 4.854a.5.5 0 1 1-.708-.708L9.44.854A1.5 1.5 0 0 1 11.5.796a1.5 1.5 0 0 1 1.998-.001z" />
</svg>
<RenamePopover
open={open}
anchorEl={anchorEl}
onClose={handleClose}
placeholder="Enter new name"
value={board.name}
/>
</span>
</li>
);
};
Now in the Board component render the BoardItem
const Board = () => {
// your useSelector code goes here ....
const single_board =
boards &&
boards.map((board) => {
return <BoardItem board={board} key={board.id} />;
});
return (
<div className="container" id="sidebar-boards">
{single_board}
</div>
);
};
Upvotes: 1
Reputation: 202618
This issue is that you are using a single open
"state" for the popover. You set the anchorEl
to the element being interacted with, but then open all popovers.
Add an open
state to track which specific board element you want to open.
const Board = () => {
...
const [anchorEl, setAnchorEl] = React.useState(null);
const [open, setOpen] = React.useState(null); // <-- add state to hold id
...
const handleClick = (id) => {
const anchor = document.getElementById(`board-anchor${id}`);
setAnchorEl(anchor);
setOpen(id);
};
const handleClose = () => {
setAnchorEl(null);
setOpen(null);
};
const single_board =
boards &&
boards.map((board) => {
return (
<li
key={board.id}
className={`row hovered-nav-item board-item ${active_board == board.id ? "item-selected" : ""}`}
>
<span className="d-flex justify-content-between">
<div
onClick={() => handleClick(board.id)}
className="fs-5 text-white board-name"
id={`board-anchor${board.id}`}
>
{board.name}
</div>
...
<RenamePopover
open={open === board.id} // <-- match board id
anchorEl={anchorEl}
onClose={handleClose}
placeholder="Enter new name"
value={board.name}
/>
</span>
</li>
);
});
...
Use the open
state to hold the entire board
data you want to open/render and render a single popover. You'll need to add an useEffect
hook to the popover component to handle resetting the local state.
const RenamePopover = (props) => {
const [value, setValue] = React.useState(props.value);
React.useEffect(() => {
setValue(props.value);
}, [props.value]);
const handleChange = (e) => {
setValue(e.target.value);
};
return (
<Popover
open={props.open}
anchorEl={props.anchorEl}
onClose={props.onClose}
anchorOrigin={{
vertical: "center",
horizontal: "right"
}}
transformOrigin={{
vertical: "center",
horizontal: "left"
}}
>
<input
autoFocus={true}
className="card bg-dark text-light add-input"
type="text"
placeholder={props.placeholder}
value={value}
onChange={handleChange}
/>
</Popover>
);
};
const Board = () => {
...
const [anchorEl, setAnchorEl] = React.useState(null);
const [open, setOpen] = React.useState(null);
...
const handleClick = (board) => {
const anchor = document.getElementById(`board-anchor${board.id}`);
setAnchorEl(anchor);
setOpen(board);
};
const handleClose = () => {
setAnchorEl(null);
setOpen(null);
};
const single_board =
boards &&
boards.map((board) => {
return (
<li
key={board.id}
className={`row hovered-nav-item board-item ${active_board == board.id ? "item-selected" : ""}`}
>
<span className="d-flex justify-content-between">
<div
onClick={() => handleClick(board)}
className="fs-5 text-white board-name"
id={`board-anchor${board.id}`}
>
{board.name}
</div>
...
</span>
</li>
);
});
return (
<div className="container" id="sidebar-boards">
{single_board}
<RenamePopover // <-- render 1 popover
open={open}
anchorEl={anchorEl}
onClose={handleClose}
placeholder="Enter new name"
value={open?.name}
/>
</div>
);
};
Upvotes: 1