When a user deletes a client, I have created a modal that asks the user(Are you sure you want to delete it).
The problem is as soon as the modal is open, the client is deleted, even though the user has not clicked yes to confirm the delete.
ClientList.js
export default function ListClients() {
const [showModal, setShowModal] = useState();
const [userlist, setUserlist] = useState([]);
function deleteClient() {
const userParams = {
clientName:
clientName,
country: country,
clientid: selectedID,
};
axios
.delete(process.env + "client", {
data: clientParams,
})
.then((response) => {
setClientlist(clientlist.filter((client) => client.id !== clientId));
})
.catch((error) => {
console.log(error);
});
// const openModal = () => {
// setShowModal(prev => !prev);
// };
}
return(
<div>
<tbody>
{userlist.length > 0 ? (
userlist.map((userlist) => (
<tr key={userlist.id}>
<td>
<div">
{userlist.id}
</div>
</td>
<td>
<button type="button" onClick={() => {deleteClient(clientlist.id); setShowModal(true)}}>
Delete
</button>
</td
</tr>
</tbody>
//the idea is to pass the state for modal to show
<ModalDelete showModal={showModal} setShowModal={setShowModal} onDel={() => deleteClient(clientlist.id)}/>
</div>
);
ModalDelete.js
export default function ModalDelete({ showModal, setShowModal,onDel }) {
return(
<div>
{ showModal ? <Transition.Root show={showModal}>
<div>
<p> Are you sure you want to delete the client?</p>
</div>
<div>
<button type="button" onClick={() => {onDel(); setShowModal(false);}>Yes</button>
<button type="button" onClick={() => {setShowModal(false);}} >
Go Back
</button>
</div>
</Transition.Root> : null }
</div>
);
}
When the user clicks yes in the modal, I want the client to be deleted.
How can I make it?
You are calling the deleteClient function in the event handler assigned to the delete button, before setting showModal to true. This causes the client to be deleted first and the modal to be displayed afterwards.
To fix this, you should update the event handler to not call deleteClient. Instead, define a function in the ListClient component which will close the modal and call the deleteClient function. Pass this as the onDel prop to your ModalDelete component.
(Edit: Fixed grammar)
Related
I have some inputs in every row(PreviewItemRow.js) of Table component. I get the data from Redux store. I keep PreviewItemRow changes as internal state. There is also a save button in every button whose onClick event makes an api call to server.
Problem is I want user to save(make api call) his changes as batch requests and also use should be able to save as individual row.
If I reflect changes directly to redux store changes state in redux whenever user presses a button in keyboard, I wont be able to be sure if changes reflected to server.
If I keep the name as component internal state, I can not track changes from SaveAll button.
So how can I Implement to save changes from a button individual row and a button in parent component ?
Parent Table Component
const ParentTableComp = (props) => {
const cases = useSelector(store => store.taskAppReducer.Case.cases);
const handleSaveAllClick = () => {
dispatch(previewBulkSave({
taskId: selectedTask.taskId,
caseData: cases.map(item => ({
name: item.caseName,
}))
}))
.then(() => {
saveSuccess("All saved.");
})
.catch((err) => {
saveError(err);
});
};
return (
<div>
<Button
type='button'
color='primary'
onClick={handleSaveAllClick}
>
Save All
</Button>
<Table>
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
{cases.map((item, index) => (
<tr key={item.caseId}>
<PreviewCaseItem
case={item}
/>
</tr>
))}
</tbody>
</Table>
</div>
);
};
This is the Row component.
const PreviewItemRow = (props) => {
const [name, setName] = useState(props.case.name)
const dispatch = useDispatch();
const handleSaveButtonClick = () => {
dispatch(saveCase({
taskType: taskType,
updatedCase: {
...props.case,
name
},
}))
.then(() => {
saveSuccess("Case Updated");
})
.catch((err) => {
saveError(err);
});
};
const handleNameChange = (event) => {
setName(event.target.value)
}
return (
<div>
<td style={{ width: 100 }}>
<Input
type={"text"}
id={`name-${props.case.caseId}`}
value={name}
onChange={handleNameChange}
/>
</td>
</div>
);
};
refreshing react component after navigation
I wanted to refresh my component, which has a table . After navigating to that page but nothing happened since the useeffect doesn't work.
Actually, there is a delete button ,once click on it it should remove some data and redirect to a component and refresh the component.
CrudDemo function:
function CrudDemo(props) {
const navigate=useNavigate();
const location = useLocation();
//it runs for firsttime ,due to having second parameter as empthy array
useEffect(()=>{
debugger;
axios.get('http://localhost:60359/api/person/getpersons').then((res)=>{
const ResponseData=res.data;
setPersonList(ResponseData);
})
},[])
const [PersonList, setPersonList] = useState();
const [ShouldRefresh, setShouldRefresh] = useState(false);
return (
<div className="app">
<h2>All students <span role="img" aria-labelledby="love">📫</span>
</h2>
<button type="button" className="btn btn-info"onClick={() => navigate('/SavePerson/')}>Add Person</button>
<table className="table" >
<TableHeader></TableHeader>
<tbody>
{PersonList&&PersonList.map((student,i) => {
return (
<TableRow key={student.id} obj={student}></TableRow>
);
})}
</tbody>
</table>
</div>
);
}
export default CrudDemo;
and navigation code:
navigate('/CrudDemo/');
and inside crudCompnent there is a TableRow component:
function TableRow(props) {
return (
<tr >
<th>{props.obj.id}</th>
<th>{props.obj.personName}</th>
<th>
{ //Check if message failed
(props.obj.city!=null&&props.obj.city.name.length >0)
? <div> {props.obj.city.name} </div>
: <div> </div>
}
</th>
<th>{props.obj.personPhoneNumber}</th>
<th>
<TableAction id={props.obj.id}></TableAction>
</th>
</tr>
);
}
export default TableRow
and inside it there is a tableAction which responsible for redirect after delete action:
function TableAction(props) {
const navigate=useNavigate();
const handleClick = (e) => {
axios.get('http://localhost:60359/api/person/DeletePerson/'+props.id).then((res)=>{
const ResponseData=res.data;
console.log('person deleted message :',ResponseData);
navigate('/CrudDemo/');
//navigate('/home');
})
};
return (
<div>
<button type="button" className="btn btn-info"onClick={() => navigate('/PersonDetail/'+props.id,{state:{id:props.id}})}>Details</button>
<button type="button" className="btn btn-danger"onClick={handleClick}>Delete</button>
</div>
);
}
export default TableAction
to sum up ,there is a crudComponent which present data Table and inside it there is a tableRow component which responsible for showing each row and inside it there is a tableaction component which responsible for delete each row and redirect to crudComponent .Beside problem is that after redirection crudComponent isn't refreshed.
I handle it this way.Added location.state as a dependency in useEffect and send a state to it.
function CrudDemo(props) {
const navigate=useNavigate();
const location = useLocation();
//it runs for firsttime ,due to having second parameter as empthy array
useEffect(()=>{
debugger;
axios.get('http://localhost:60359/api/person/getpersons')
.then((res)=>{
const responseData=res.data;
setPersonList(responseData);
});
},[location.state]);
and in another component:
const handleClick = (e) => {
axios.get('http://localhost:60359/api/person/DeletePerson/'+props.id)
.then((res)=>{
const ResponseData=res.data;
console.log('person deleted message :',ResponseData);
navigate('/CrudDemo/',{state:{refresh:true}});
//navigate('/home');
});
};
I am displaying a todo list, every todo have an edit button that triggers a modal. From the modal (child component), you can update the description and send the new description to the database.
In the parent component there is update and setUpdate hook. I am sending both through props so that I can change the update value to the opposite when click save in the modal.
I was expecting the parent component to re render and make the axios call again when using setUpdate in the child component, this way when I click save button in modal, the list of todos will show the updated todos. But is not working.
I do not understand why the parent component do not re render if I am changing update state using setUpdate in the child component.
Thanks for your help.
Parent component to display todos
import axios from "axios";
import { Fragment, useState, useEffect } from "react";
import EditTodo from "./EditTodo";
const ListTodo = () => {
const [todos, setTodos] = useState(null);
const [isPending, setIsPending] = useState(false);
const [error, setError] = useState(false);
const [update, setUpdate] = useState(false);
useEffect(() => {
async function getData() {
try {
setIsPending(true);
const response = await axios.get("http://localhost:5000/todos");
setTodos(response.data);
setIsPending(false); // We have to changed back to false because the response finished and the data is in todos state
} catch (err) {
setError(err.message); // I am changing the error state to the message that comes from the backend if there is an error
setIsPending(false); // I am changing back to false because the call and response ended and throw an error
}
}
getData();
}, []);
const handleDelete = async (id) => {
try {
const response = await axios.delete(`http://localhost:5000/todos/${id}`);
console.log(response.data);
setTodos(todos.filter((todo) => todo.todo_id !== id)); //This is the way to delete, so that the component can rerender once the todos state is changed
} catch (err) {
setError(err.message);
}
};
console.log(todos);
return (
<Fragment>
{isPending && <p>Loading...</p>}
{error && <p>{error}</p>}
{todos && (
<table className="table mt-5">
<thead>
<tr>
<th scope="col">Description</th>
<th scope="col">Edit</th>
<th scope="col">Delete</th>
</tr>
</thead>
<tbody>
{todos.map((todo) => {
return (
<Fragment key={todo.todo_id}>
<tr>
<th scope="row">{todo.description}</th>
<td>
<EditTodo
todo={todo}
update={update}
setUpdate={setUpdate}
/>
</td>
<td>
<button
className="btn btn-primary"
onClick={() => handleDelete(todo.todo_id)}
>
Delete
</button>
</td>
</tr>
</Fragment>
);
})}
</tbody>
</table>
)}
</Fragment>
);
};
export default ListTodo;
Child component to update todo
import axios from "axios";
import React, { Fragment, useState } from "react";
import "./EditTodo.css";
const EditTodo = ({ todo, update, setUpdate }) => {
const [showModal, setShowModal] = useState(false);
const [description, setDescription] = useState(todo.description);
const handleSubmit = async (e) => {
e.preventDefault();
try {
const updatedTodo = { description };
console.log(updatedTodo);
const response = await axios.put(
`http://localhost:5000/todos/${todo.todo_id}`,
updatedTodo
);
setUpdate(!update);
setShowModal(false);
// window.location = "/";
} catch (err) {
console.error(err.message);
}
};
return (
<Fragment>
<button
type="button"
className="btn btn-primary"
data-bs-toggle="modal"
data-bs-target="#exampleModal"
onClick={() => setShowModal(true)}
>
Edit
</button>
{showModal && (
<div className="modal-background">
<div className="modal-content">
<div
style={{
display: "flex",
justifyContent: "flex-end",
}}
onClick={() => setShowModal(false)}
>
<p
style={{
cursor: "pointer",
}}
>
x
</p>
</div>
<div>
<h2>Input Todo</h2>
<form className="d-flex flex-column" onSubmit={handleSubmit}>
<input
type="text"
placeholder="edit todo"
className="form-control"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<br />
<button className="btn btn-success">Save</button>
</form>
</div>
</div>
</div>
)}
</Fragment>
);
};
export default EditTodo;
Your component is rerendering, but the effect that gets the list of todos is not dependent on the update state, so it is not be ran again when update changes. You might want to read more into how the dependency array works with the useEffect hook for more information about why that is, but shortly, an empty dependency array means that an effect will be called only twice, on mount and dismount.
Moving on to how to fix your problem, lift the logic that retrieves the whole list of todos and sets the state into its own function. Call that function in the effect, and pass the function to the child, call it after the update to the child is complete.
I am creating a Todo App, and trying to create a confirmation delete popup which is going to be visible when the user wants to delete a todo.
In my todo.js component I have created an onClick callback, handleDelete, in my delete button, that callback will set the popup to true making it visible, the problem is that in my handleDelete I pass the Id as argument, so I can track which todo has been clicked and filter it to show the new data updating the todos state, but I only want to do update the data when the user have clicked in the confirm button that is in the popup.
App Component:
function App() {
const [inputValue, setInputValue] = useState("");
const [todos, setToDos] = useState([]);
const [noToDo, setNoToDo] = useState(false);
const [popup, setPopup] = useState(false);
const handleOnSubmit = (e) => {
e.preventDefault();
setNoToDo(false);
const ide = nanoid();
const date = new Date().toISOString().slice(0, 10);
const newToDo = { task: inputValue, id: ide, date: date };
setToDos([...todos, newToDo]);
setInputValue("");
};
const handleDelete = (id) => {
setPopup(true);
let filteredData = todos.filter((todo) => todo.id !== id);
{
/*
filteredData is the new data, but I only want to update
todos with filteredData when the user has clicked on the confirm
button in the modal component, which execute(handleDeleteTrue)*/
}
};
const handleDeleteTrue = () => {
setPopup(false);
setToDos(filteredData);
};
const handleEdit = (id, task) => {
setInputValue(task);
const EditedData = todos.filter((edited) => edited.id !== id);
setToDos(EditedData);
};
return (
<div className="App">
<div className="app_one">
<h1>To do app</h1>
<form action="" className="form" onSubmit={handleOnSubmit}>
<input
type="text"
placeholder="Go to the park..."
onChange={(e) => setInputValue(e.target.value)}
value={inputValue}
/>
<button type="submit">ADD TO DO</button>
</form>
</div>
{noToDo && <FirstLoad />}
{todos.map((todo) => {
return (
<div key={todo.id} className="result">
<Todo
{...todo}
handleDelete={handleDelete}
handleEdit={handleEdit}
/>
</div>
);
})}
{popup && <Popup handleDeleteTrue={handleDeleteTrue} />}
</div>
);
}
export default App;
Todo Component:
const Todo = ({ handleDelete, handleEdit, task, id, date }) => {
return (
<>
<div className="result_text">
<h3>{task}</h3>
<p className="result_textP">{date}</p>
</div>
<div>
<button onClick={() => handleEdit(id, task)} className="button green">
Edit
</button>
<button onClick={() => handleDelete(id)} className="button">
delete
</button>
</div>
</>
);
};
export default Todo;
Modal Component:
function Popup({ handleDeleteTrue }) {
return (
<div className="modal">
<div className="modal_box">
<p>You sure you wanna delete?</p>
<button className="modal_buttonCancel">Cancel</button>
<button onClick={handleDeleteTrue} className="modal_buttoDelete">
Confirm
</button>
</div>
</div>
);
}
export default Popup;
I tried to declare filteredData as global variable, outside my App component, so when I execute handleDelete it initializes that variable with the filtered data, and only when the user click the confirm button on the popup it executes a new function, handleDeleteTrue, which updates the data to filteredData.
It works, but declaring variables outside my component is not a good practice, so is there a better approach?
The issue in your current code is that, you are losing the id that should be deleted, so you need to store it in a ref or state.
Here is a solution that stores the id in state along with the boolean flag that shows/hides the Confirmation Box:
const [popup, setPopup] = useState({
show: false, // initial values set to false and null
id: null,
});
Modify the delete-handlers as:
// This will show the Cofirmation Box
const handleDelete = (id) => {
setPopup({
show: true,
id,
});
};
// This will perform the deletion and hide the Confirmation Box
const handleDeleteTrue = () => {
if (popup.show && popup.id) {
let filteredData = todos.filter((todo) => todo.id !== popup.id);
setToDos(filteredData);
setPopup({
show: false,
id: null,
});
}
};
// This will just hide the Confirmation Box when user clicks "No"/"Cancel"
const handleDeleteFalse = () => {
setPopup({
show: false,
id: null,
});
};
And, in the JSX, pass the handlers to Popup:
{popup.show && (
<Popup
handleDeleteTrue={handleDeleteTrue}
handleDeleteFalse={handleDeleteFalse}
/>
)}
I'm trying to render actual data in child component, but data does not render. What is wrong?
Parent component
const UserPanelContainer = ({ currentUser }) => {
const [initUsersData, setinitUsersData] = useState(currentUser);
useEffect(() => {
console.log('useEffect')
setinitUsersData(()=>getnewData())
}, [setinitUsersData, currentUser])
const getnewData = () =>{
console.log('getnewData')
setinitUsersData(currentUser)
}
return (
<UserPanel currentUser={initUsersData} hanleOnClickOut={hanleOnClickOut} >{console.log('usepanContainerRender')}</UserPanel>
);
};
export default UserPanelContainer;
child
const UserPanel = ({ currentUser, hanleOnClickOut }) => {
console.log(currentUser);
return (
<div className="dropdown">
{console.log('userPanelRender')}
<button
className="btn btn-secondary dropdown-toggle"
type="button"
id="dropdownMenuButton"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<img
className="avatar"
src={currentUser.photoURL}
alt="avatar"
/>
{currentUser.displayName}
</button>
<div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
<div className="dropdown-item">
Вошел как {currentUser.displayName}
</div>
<div className="dropdown-item" onClick={hanleOnClickOut}>
Выйти
</div>
</div>
</div>
);
};
export default UserPanel;
In console in child I can see correct actual data in props, but they are not rendered.
Actual data contains "currentUser" prop. But on Browser page i cant see data....
(if i delete currentUser from useEffect depencity i can see data from previus API call)
I see you are passing the setinitUsersData in the useEffect dependency array whereas you need to pass the actual state variable
try this,
useEffect(() => {
...
}, [initUsersData, currentUser])
instead of current,
useEffect(() => {
...
}, [setinitUsersData, currentUser])
I think by actual data you mean some api response.
Try this :-
useEffect(() => {
console.log('useEffect')
getnewData(currentUser)
}, [currentUser])
const getnewData = (currentUser) =>{
console.log('getnewData')
axios.get("/pathToData").then((res) => {
console.log(res);
setinitUsersData(res);
})
}
Replace parent component with the following code. You don't need to use useEffect as per the code you've posted. Since parent is already receiving currentUser and you have already updated state with that
const UserPanelContainer = ({ currentUser }) => {
const [initUsersData, setinitUsersData] = useState(currentUser);
return (<UserPanel
currentUser={initUsersData}
hanleOnClickOut={hanleOnClickOut}>{console.log('usepanContainerRender')}</UserPanel>
);
};
export default UserPanelContainer;