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');
});
};
Related
I need help with this code. It is a website that shows users on the screen and the button on the right should open the user information details in a modal. I can't get the information of the user I click on, to appear in the modal. Show me the last one.
web photo
Foto of the website
Foto of the modal
And this is my code
import { Modal } from "antd";
import 'antd/dist/antd.css';
import { useEffect, useState } from "react"
import { userApi } from "../api/userApi";
import { Usuario } from "../interfaces/fetchAllUserResponse";
export const List = () => {
const [users, setUsers] = useState<Usuario[]>([]);
const [currentPage, setCurrentPage] = useState(0);
const [isModalOpen, setIsModalOpen] = useState(false);
useEffect(() => {
// call API
userApi.get<Usuario[]>('/default/techJobMission')
.then( resp => {
setUsers( resp.data );
})
.catch( console.log )
}, []);
const filteredUsers = (): Usuario[] => {
return users.slice(currentPage, currentPage + 6);
}
const nextUser = () => {
setCurrentPage( currentPage + 5 )
}
const prevUser = () => {
if( currentPage > 0 ){
setCurrentPage( currentPage - 5 )
};
}
const renderItem = ({ _id, firstName, lastName, ticket, present }: Usuario) => {
const openModal = () => {
setIsModalOpen(true)
}
const handleOk = () => {
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
return (
<tr key={ _id }>
<td >
{( present ? <img className="ticket" src="icons/ticket-green.svg"/> : <img className="ticket" src="icons/ticket-red.svg"/>)}
</td>
<td className="user">
<p className="encabezado">{firstName } {lastName}</p>
{( present ? <p className="estado">Ha entrado</p> : <p className="estado">No ha entrado</p> )}
</td>
<td className="id-user">
<p className="encabezado2">ID</p>
<p className="estado">{_id}</p>
</td>
<td className="ticket-td">
<p className="encabezado2">Nº de ticket</p>
<p className="estado">{ticket + 1}</p>
</td>
<td>
<button onClick={openModal} className="btn-modal"> <img src="icons/menu-modal.svg"/> </button>
<Modal open={isModalOpen} onOk={handleOk} onCancel={handleCancel} >
<p>{firstName}</p>
</Modal>
</td>
</tr>
)
}
return (
<>
<table className="table">
<tbody>
{
filteredUsers().map( renderItem )
}
</tbody>
</table>
<br />
<button onClick={ prevUser } className="paginacion">
<img src="icons/anterior.svg"/> Anterior
</button>
<button onClick={ nextUser } className="paginacion">
Siguiente <img src="icons/siguiente.svg"/>
</button>
</>
)
}
It's opening all of the modals.
You have multiple modals and one boolean value indicating if "the modal" is open or not. So either they're all open or they're all not open.
Instead of tracking a single boolean value, track something like an identifier for which modal to display. For example:
const [modalID, setModalID] = useState();
And:
const openModal = (id) => {
setModalID(id)
}
const handleCancel = () => {
setModalID(undefined);
};
Then pass that id to the function from your record:
onClick={() => openModal(_id)}
And use it to determine if the modal is open:
open={modalID === _id}
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)
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'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;
The delete function of my app is working fine, however it requires the user to manually refresh the page after the user click the delete button in order to see the new list of elements in my database. I would like to automatically refresh after the click event. I am using react hooks for this projects. However, I found one solution if I remove useEffect's [] but in my backend it shows, its requesting crazily. I don't know, is it wise to remove useffect's [ ]?
Here is the component where it fetches data from backend and passes the props to another component
import React, { useState, useEffect } from "react";
import axios from "axios";
import Table from "../Table/Table";
import "./Display.css";
const Display = () => {
const [state, setState] = useState({ students: [], count: "" });
const [searchItem, setsearchItem] = useState({
item: ""
});
const Search = e => {
setsearchItem({ item: e.target.value });
};
useEffect(() => {
axios
.get("/students")
.then(response => {
setState({
students: response.data.students,
count: response.data.count
});
})
.catch(function(error) {
console.log(error);
});
}, []); //If I remove this square brackets, it works
const nameFilter = state.students.filter(list => {
return list.name.toLowerCase().includes(searchItem.item.toLowerCase());
});
return (
<div>
<h3 align="center">Student tables</h3>
<p align="center">Total students: {state.count}</p>
<div className="input-body">
<div className="row">
<div className="input-field col s6">
<input placeholder="search student" onChange={Search} />
</div>
</div>
</div>
<table className="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Date of birth</th>
<th>Address</th>
<th>Zipcode</th>
<th>City</th>
<th>Phone</th>
<th>Email</th>
<th colSpan="2">Action</th>
</tr>
</thead>
{nameFilter.map((object, index) => {
return (
<tbody key={index}>
<Table obj={object} /> //In here I am passing the props to the another component.
</tbody>
);
})}
</table>
</div>
);
};
export default Display;
This is second component which receives the props.
import React, { useState } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
const Table = props => {
const removeData = () => {
axios
.delete("/students/" + props.obj.id)
.then(console.log("Deleted"))
.catch(err => console.log(err));
};
return (
<React.Fragment>
<tr>
<td>{props.obj.name}</td>
<td>{props.obj.birthday}</td>
<td>{props.obj.address}</td>
<td>{props.obj.zipcode}</td>
<td>{props.obj.city}</td>
<td>{props.obj.phone}</td>
<td>{props.obj.email}</td>
<td>
<Link
to={"/edit/" + props.obj.id}
className="waves-effect waves-light btn"
>
Edit
</Link>
</td>
<td>
<button onClick={removeData} className="waves-effect red btn ">
Remove
</button>
</td>
</tr>
</React.Fragment>
);
};
export default Table;
The [] in the useEffect hook is a dependency array to trigger the effect to run. If you want to trigger the effect (without it going off mercilessly), you can create a new variable that triggers that effect to run.
import React, { useState, useEffect } from "react";
import axios from "axios";
import Table from "../Table/Table";
import "./Display.css";
const Display = () => {
const [state, setState] = useState({ students: [], count: "" });
const [requestData, setRequestData] = useState(new Date());
const [searchItem, setsearchItem] = useState({
item: ""
});
const Search = e => {
setsearchItem({ item: e.target.value });
};
useEffect(() => {
axios
.get("/students")
.then(response => {
setState({
students: response.data.students,
count: response.data.count
});
})
.catch(function(error) {
console.log(error);
});
}, [requestData]);
const nameFilter = state.students.filter(list => {
return list.name.toLowerCase().includes(searchItem.item.toLowerCase());
});
return (
<div>
<h3 align="center">Student tables</h3>
<p align="center">Total students: {state.count}</p>
<div className="input-body">
<div className="row">
<div className="input-field col s6">
<input placeholder="search student" onChange={Search} />
</div>
</div>
</div>
<table className="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Date of birth</th>
<th>Address</th>
<th>Zipcode</th>
<th>City</th>
<th>Phone</th>
<th>Email</th>
<th colSpan="2">Action</th>
</tr>
</thead>
{nameFilter.map((object, index) => {
return (
<tbody key={index}>
<Table obj={object} setRequestData={setRequestData} />
</tbody>
);
})}
</table>
</div>
);
};
export default Display;
Then you can trigger it from your Table component:
import React, { useState } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
const Table = props => {
const removeData = () => {
axios
.delete("/students/" + props.obj.id)
.then(() => {
props.setRequestData(new Date());
})
.catch(err => console.log(err));
};
return (
<React.Fragment>
<tr>
<td>{props.obj.name}</td>
<td>{props.obj.birthday}</td>
<td>{props.obj.address}</td>
<td>{props.obj.zipcode}</td>
<td>{props.obj.city}</td>
<td>{props.obj.phone}</td>
<td>{props.obj.email}</td>
<td>
<Link
to={"/edit/" + props.obj.id}
className="waves-effect waves-light btn"
>
Edit
</Link>
</td>
<td>
<button onClick={removeData} className="waves-effect red btn ">
Remove
</button>
</td>
</tr>
</React.Fragment>
);
};
export default Table;
Not sure if helps but you can always remove the item from the current array, so a refresh is not needed, for example you can pass as props a function that receives an id and then filter the students array to exclude the element that matches with that id and then update the state with the new array and count properties, something like this
In your parent:
const Display = () => {
const [state, setState] = useState({ students: [], count: "" });
const deleteItem = (id) => {
const newStudents = state.students.filter(student => student.id !== id)
const newCount = newStudents.length;
setState({ students: newStudents, count: newCount })
}
// Rest of the code
}
Now pass that function to your child component.
<Table obj={object} deleteItem={deleteItem} />
In the child component just modify your removeData method to add the deleteItem prop:
const Table = props => {
const removeData = () => {
axios
.delete("/students/" + props.obj.id)
.then(console.log("Deleted"))
.catch(err => console.log(err));
// Now if your request succeeds call the function to remove the item from the students state array
props.deleteItem(props.obj.id);
};
// Rest of the code
}
I know this does not answer your question, but when you're working with react or it is better to do this computations and filters on the app side, like in this case that even though the record was deleted from the db we also removed the record from the student state object and there's no need to refresh the page.
Remember, you're creating a single page application, so we want the nicest experience for the user without refreshing the page for every action the user makes.
Have a look at this
import React, { useState } from "react";
const Display = () => {
const [refresh, setRefresh] = useState(false)
const delete=() => {
// ................. //delete logic
reload ? setRefresh(false) : setRefresh(true) //toggle just to change state
}
useEffect(() => {
}, [reload]); //inject as dependency
}