Problem with useDisclouse() in Chakra UI in a forEach loop - reactjs

How can I use useDisclouse() in Chakra with a button in a forEach loop. When I clicked on onClick event a Button it trigger all Buttons.
const {isOpen:isOpenEdit,onOpen:onOpenEdit,onClose:onCloseEdit} = useDisclosure();
...
return (<Tbody>
{!isLoading &&
data.data?.map((row,i) => (
<Tr ref={rows.current[i]=createRef()} key={i}>
<Td>{`$${row.price}`}</Td>
<Td maxW={"10em"} overflow={"hidden"}>
{row.title}
</Td>
<Td maxW={"10em"} overflow={"hidden"}>
{row.description}
</Td>
<Td>{row.brand}</Td>
<Td maxW={"10em"} overflow={"hidden"}>{Date(row.date).toString()}</Td>
<Td>{row.location}</Td>
<Td>
{row.photo}
</Td>
<Td>{row.phones?.toString()}</Td>
<Td>
<ButtonGroup isAttached>
<Button key={row.id} onClick={()=>onOpenEdit()} >Edit</Button>
<EditModal key={i} isOpen={isOpenEdit} onClose={onCloseEdit} id={row.id}/>
</ButtonGroup>
</Td>
</Tr>
))}
</Tbody>)

useDisclosure only allows you to keep track of one boolean value. So for this we need to build something ourselfs.
First we need a state to keep track of the opened modals.
const [isOpenEdits, setIsOpenEdits] = useState({});
Now we need a function which helps us to set the state based on the id of the row.
const toggleIsOpen = (id, isOpen) => {
setIsOpenEdits((prevState) => {
return {
...prevState,
[id]: isOpen,
};
});
};
We can use this toggleIsOpen function in the ButtonGroup component and set the isOpen prop to the value of isOpenEdits[row.id] defaulting to false.
<ButtonGroup isAttached>
<Button key={row.id} onClick={() => toggleIsOpen(row.id, true)}>
Edit
</Button>
<EditModal
key={i}
isOpen={isOpenEdits[row.id] ?? false}
onClose={() => toggleIsOpen(row.id, false)}
id={row.id}
/>
</ButtonGroup>

Related

How to handle delete popup confirmation for an api in reactjs

Could anyone help me out here please, all I'm trying to do here is to show popup modal confirmation for delete action, but every time I clicked on **Yes **btn to confirm my delete action the last product on the list always get deleted instead. I need help from anyone please?
Here is my code for handling the delete popup
```
//OPEN DELETE MODALS
const [openDeleteModal, isOpenDeleteModal] = useState(false);
const closeDeleteModal = () => {
isOpenDeleteModal(false);
document.body.style.overflow = "unset";
};
const showDeleteModal = () => {
isOpenDeleteModal(true);
};
```
and here is the api
```
//DELETE PRODUCT
const deleteHandler = async (product) => {
try {
await axios.delete(`/api/products/${product._id}`, {
headers: { Authorization: `Bearer ${userInfo.token}` },
});
toast.success("product deleted successfully", {
position: "bottom-center",
});
dispatch({ type: "DELETE_SUCCESS" });
} catch (err) {
toast.error(getError(err), { position: "bottom-center" });
dispatch({ type: "DELETE_FAIL" });
}
};
```
down here is my modal for confirmation
```
{/* MODAL */}
{openDeleteModal && (
<div className="delete-modal">
<div className="delete-modal-box">
<div className="delete-modal-content">
<p className="delete-modal-content-p">
Are you sure to delete this product?
</p>
<div className="delete-modal-btn">
<button
onClick={closeDeleteModal}
className="delete-modal-btn-close"
>
Close
</button>
<button
onClick={() => {
deleteHandler(product);
closeDeleteModal();
}}
className="delete-modal-btn-yes"
>
{" "}
Yes
</button>
</div>
</div>
</div>
</div>
)}
```
All I'm trying to do is to be able to delete any product from the list not the last product every time.
here is the entirety of my productList map looks like
{products?.map((product, index) => (
<tr className="product-item-list" key={index}>
<tr>
<td className="product-item-id">{product._id}</td>
<td className="product-item-name">
{product.name}
</td>
<td className="product-item-price">
£{product.price}
</td>
<td className="product-item-category">
{product.category?.map((cat, index) => (
<span key={index}>{cat}</span>
))}
</td>
<td className="product-item-size">
{product.size?.map((s, index) => (
<span key={index}>{s} </span>
))}
</td>
<td className="product-btn-view">
<button
className="product-btn"
onClick={() =>
navigate(`/admin/productedit/${product._id}`)
}
>
Edit
</button>
<DeleteOutline
className="product-delete"
onClick={showDeleteModal}
/>
{/* MODAL */}
{openDeleteModal && (
<div className="delete-modal">
<div className="delete-modal-box">
<div className="delete-modal-content">
<p className="delete-modal-content-p">
Are you sure to delete this product?
</p>
<div className="delete-modal-btn">
<button
onClick={closeDeleteModal}
className="delete-modal-btn-close"
>
Close
</button>
<button
onClick={() => {
deleteHandler(product);
closeDeleteModal();
}}
className="delete-modal-btn-yes"
>
{" "}
Yes
</button>
</div>
</div>
</div>
</div>
)}
</td>
</tr>
<tr></tr>
</tr>
))}
I guess it happens because all of your modals open when you call showDeleteModal
And the last one is on the top, so when you click to delete the last closure works. Maybe its nessesary to pass id of product into the openDeleteModal. And than check if product.id equals to openDeleteModal.
Can you print to console the product.id when you click on the "Delete" button and check is it the correct id of the clicked product?

How to set default value of react-select using formik fieldarray?

I am using formik and fieldarray for creating dynamic form and I am using react-select. I am working on edit of items. On Edit if there exists previous values then those values must be shown and those values can be changed, but somehow I am unable to create. Here's my code.
here is the code for initial value of formik
const initialOrderItemData = orderItems && orderItems.map((orderItem) => {
return {
itemId: orderItem.itemId,
quantity: orderItem.quantity,
}
});
const initialOrderInfo = {
orderedItemDataArray: initialOrderItemData,
};
This is my form
<Formik
initialValues={initialOrderInfo}
onSubmit={handleSubmit}
validationSchema={HotelOrderSchema}
>
{({ errors, setFieldValue, touched, values, name, }) => (
<Form>
<Row>
<Table size="sm" hover>
<thead>
<tr>
<th>Item</th>
<th>Quantity</th>
<th>Operation</th>
</tr>
</thead>
<tbody>
<FieldArray
name="orderedItemDataArray"
render={({ remove, push }) => (
<>
{values.orderedItemDataArray.length > 0 &&
values.orderedItemDataArray.map(
(order, index) => (
<tr key={index}>
<td>
<Select
as="select"
name={`orderedItemDataArray.${index}.itemId`}
options={menuOptions}
placeholder="Type"
// value={selectedItems(order.itemId)}
value={menuOptions.find(op => {
return op.value === order.itemId
})}
/>
</td>
<td>
<Field
type="number"
className="form-control"
name={`orderedItemDataArray.${index}.quantity`}
min="0"
placeholder="Quantity"
/>
</td>
<td>
<ButtonGroup>
{values.orderedItemDataArray
.length !== 1 && (
<Button
color="primary"
size="sm"
onClick={() => remove(index)}
>
-
</Button>
)}
{values.orderedItemDataArray
.length -
1 ===
index && (
<Button
color="secondary"
size="sm"
onClick={() =>
push({
itemId: '',
quantity: '',
})
}
>
+
</Button>
)}
</ButtonGroup>
</td>
</tr>
)
)}
</>
)}
/>
</tbody>
</Table>
</Row>
</Form>
</Formik>
with this code I am able to get the value but I am unable to change the value and set the new value.
In your select component you must include an onChange event like so
//set a state for the changed selected option
const [selectedOption,setSelectedOption] = useState()
<Select
as="select"
name={`orderedItemDataArray.${index}.itemId`}
options={menuOptions}
placeholder="Type"
// value={selectedItems(order.itemId)}
value={menuOptions.find(op => {
return op.value === order.itemId
onChange={(e) => {
setSelectedOption(e.target.value)
}}
})}
/>
//then if you wanna access the selected option you can use the useState value.
<p>{selectedOption}</p>

How to handle Material UI inputs with React-hook-form?

I'm working on a dynamic form where you can add and remove fields at will using React-hook-form. For this I made a table component where the input will be held. This is how it looks.
import {useFieldArray, useFormContext, Controller} from "react-hook-form";
import {cloneElement} from "react";
import {IoMdAdd, IoMdRemoveCircle} from "react-icons/io";
interface TableData {
tableName:string,
inputFields: {
title: string,
name: string,
inputComponent: React.ReactElement, // Did this since input can be text, select, or entire component
}[],
inputBlueprint: object,
min?: number
};
const InputTable = ({tableName, inputFields, inputBlueprint, min}: TableData) => {
const {fields, remove, append} = useFieldArray({name: tableName});
const {register, formState: {errors}, control} = useFormContext();
return (
<table className="table-auto border-collapse block m-auto w-fit max-w-xs max-h-48 overflow-auto sm:max-w-none my-3">
<thead className="text-center">
<tr>
{inputFields.map((input) => (
<td className="border-2 border-gray-400 px-5" key={input.title}>{input.title}</td>
))}
</tr>
</thead>
<tbody>
{fields.map((field, index) => (
<tr key={field.id}>
{inputFields.map((input) => (
<td key={input.title} className="border-gray-400 border-2 p-0">
{input.inputComponent.type === "input" && cloneElement(input.inputComponent, {
className: "bg-transparent outline-none block w-full focus:bg-gray-400 dark:focus:bg-gray-500 p-1",
...register(`${tableName}.${index}.${input.name}` as const)
})
}
{input.inputComponent.type !== "input" && //This doesn't work at all
<Controller
name={`${tableName}.${index}.${input.name}`}
control={control}
defaultValue=""
render={({field: {onChange, value}}) => {return input.inputComponent}}
/>
}
{errors[tableName]?.[index]?.[input.name] &&
<p className="bg-red-400 p-1">
{errors[tableName][index][input.name]?.message}
</p>
}
</td>
))}
{(min === undefined || min <= index) &&
<td onClick={() => remove(index)}><IoMdRemoveCircle className="text-red-600 text-2xl"/></td>
}
</tr>
))}
<tr>
<td onClick={() => append(inputBlueprint)} className="bg-green-500 border-gray-400 border-2"
colSpan={inputFields.length}>
<IoMdAdd className="m-auto"/>
</td>
</tr>
{errors[tableName] &&
<tr>
<td className="max-w-fit text-center">
{errors[tableName].message}
</td>
</tr>}
</tbody>
</table>
)
}
export default InputTable
It works for the most part with regular inputs (html input and select) but I'm having problems since I'm using Material's UI Autocomplete component for suggestions in some fields and since React-hook-form uses unregistered components and MUI uses registered, they really clash. Is there a better way to do this? I have thought about using the children prop but I'm not entirely sure if this would better the situation.

Word is not getting added until the page is refreshed in TableItem component in React

TableItem component added without any data in UI. Could somebody help on this. On refereshing the UI, added data is shown with details in TableItem component.
Table Component Code
import TableItem from "./TableItem";
function Table({ searchWord }) {
const dispatch = useDispatch();
const dictData = useSelector((state) => state.dictionary);
useEffect(() => {
dispatch(getDictionaryAsync());
}, [dispatch]);
return (
<table className="table table-striped">
<thead>
<tr>
<th scope="col">Word</th>
<th scope="col">Description</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{dictData &&
dictData
.filter((e) =>
searchWord === ""
? e
: e.word &&
e.word.toLowerCase().includes(searchWord.toLowerCase())
)
.map((item) => (
<TableItem item={item} key={item.id} searchWord={searchWord} />
))}
</tbody>
</table>
);
}
export default Table;
Below is the TableItem Component Code which i am trying to update,
When i add a word to dictionary it will fetch the details from the server and display it in the React app.
function TableItem({ item }) {
const [modal, setModal] = useState(false);
const openModal = () => {
setModal(true);
};
return (
<>
<tr key={item.id}>
<td style={{ textTransform: "capitalize" }}>{item.word}</td>
<td>
<b style={{ textTransform: "capitalize" }}>
{item.items && item.items[0].category} -{" "}
</b>
{item.items && truncate(item.items[0].definitions[0])}
</td>
<td>
<button className="btn btn-danger btn-sm " onClick={openModal}>
View
</button>
</td>
</tr>
<Modal isOpen={modal} ariaHideApp={true}>
<div className="modal-header">
<h3 className="modal-word-header">
{item.word && item.word.toUpperCase()}
</h3>
<button
className="btn btn-danger btn-sm"
onClick={() => setModal(false)}
>
<i class="fa fa-times" aria-hidden="true"></i>
</button>
</div>
<div className="model-content">
<p>
{item.items &&
item.items.map((e) => {
return (
<>
<i>{e.category}</i>
<ul>
{e.definitions.map((def) => {
return <li>{def}</li>;
})}
</ul>
</>
);
})}
</p>
</div>
</Modal>
</>
);
}
Better add your TableItem component code!
Below code works fine and updated the UI on change in the Data in TableItem,
useEffect(() => {
dispatch(getDictionaryAsync());
}, [dispatch, dictData]); *<--updated code*

Select rows issue in pagination react table

I've made a table with one column as checkbox to select that row. So if the user checks this row's checkbox, I'll add isChecked : true property in state and on uncheck will change isChecked: false of that in state. Each page is having 10rows. The issue is when I checked the 1st row checkbox of 1st page and when I go to Next Page somehow the 1st row of next page checkbox also appears checked. However only 1st row is set to true in state. Whats the issue? What wrong I'm doing can anyone tell? Thanks in advance!
import React,{Component} from 'react';
import { Table,Button,Input } from 'reactstrap';
import SelectedUsers from './SelectedUsers';
import { yellow } from '#material-ui/core/colors';
import Icon from '#material-ui/core/Icon';
class Users extends Component {
constructor(props) {
super(props);
this.state = {
users : [],
pageSize: 10,
pageIndex: 0,
selectedUsers : [],
filterCandidate : '',
searchVal : ""
};
}
componentDidMount() {
const userLink = 'api';
fetch(userLink, {
method: 'GET'
})
.then(res => res.json())
.then(data => {
this.setState({
users : data
})
console.log(data)
})
}
onSelectUser = (e,i) => {
const copy_users = this.state.users.slice() ;
const checked = e.target.checked
copy_users[i].isChecked = checked
this.setState({ copy_users})
// console.log( e.target.value)
}
handlePrevPageClick = (event) => {
this.setState(prevState => ({
pageIndex: prevState.pageIndex > 0 ? prevState.pageIndex - 1 : 0
}));
}
handleNextPageClick = (event) => {
this.setState(prevState => ({
pageIndex:
prevState.pageIndex <
Math.floor(prevState.users.length / prevState.pageSize)
? prevState.pageIndex + 1
: prevState.pageIndex
}));
}
render() {
let profile = 'Profile Image';
return (
<div className="bets_page">
<Table striped responsive>
<thead>
<tr>
<th>Select</th>
<th>Player Name</th>
<th>Level<Icon style={{ color: yellow[800] }} fontSize="small">star</Icon></th>
<th>Avatar</th>
<th>BET</th>
<th>Wins<Icon style={{ color: yellow[800] }} fontSize="small">euro</Icon></th>
<th>Lost</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{this.state.users.slice(
this.state.pageIndex * this.state.pageSize,
this.state.pageIndex * this.state.pageSize + this.state.pageSize
).map((data,i) => (
<tr key={i}>
<td>
<label className="checkbox">
<input type="checkbox"
checked={data.isChecked}
key={i}
value={data.Name}
onChange={(e) => this.onSelectUser(e,i)}/>
</label>
</td>
<td>{data.Name}</td>
<td></td>
<td><img src={data[profile]} alt={data.Name}
className="avatar"/></td>
<td>{data.Bet}</td>
<td></td>
<td></td>
<td>{data.Price}</td>
</tr>
))}
</tbody>
</Table>
<div>
<Button onClick={event => this.handlePrevPageClick(event)} className="m-2">
{"<"}
</Button>Page {this.state.pageIndex+1}
<Button onClick={event => this.handleNextPageClick(event)} className="m-2">
{">"}
</Button>
</div>
</div>
}
}
export default Users;
when you slice the users and apply map on them the 'i' variable starts from 0 for each page. you should add 'this.state.pageIndex * this.state.pageSize' to 'i' variable whenever you set it for key and you send that to onSelectUser
render() {
return (
<div className="bets_page">
<Table striped responsive>
<thead>
<tr>
<th>Select</th>
<th>Player Name</th>
</tr>
</thead>
<tbody>
{this.state.users
.slice(
this.state.pageIndex * this.state.pageSize,
this.state.pageIndex * this.state.pageSize + this.state.pageSize
)
.map((data, i) => {
const index = i + this.state.pageIndex * this.state.pageSize;
return (
<tr key={index}>
<td>
<label className="checkbox">
<input
type="checkbox"
checked={data.isChecked}
key={i}
value={data.Name}
onChange={(e) => this.onSelectUser(e, index)}
/>
</label>
</td>
<td>{data.Name}</td>
</tr>
);
})}
</tbody>
</Table>
<div>
<Button
onClick={(event) => this.handlePrevPageClick(event)}
className="m-2"
>
{"<"}
</Button>
Page {this.state.pageIndex + 1}
<Button
onClick={(event) => this.handleNextPageClick(event)}
className="m-2"
>
{">"}
</Button>
</div>
</div>
);
}
I simplified your code and I created the online demo here

Resources