I have created a table in which I would now like to be able to delete individual rows. The button is displayed and can be clicked, but the deleteItmen function is not called. Unfortunately I'm quite new to React.js and haven't found a solution yet.
const Tab = (props) => {
const [showContent, setShowContent] = useState([]);
const [loading, setLoading] = useState(false);
const token = getUser.getCurrentUser().token;
useEffect(() => {
LoadTable();
// eslint-disable-next-line
}, []);
const LoadTable = () => {
if (props.table === undefined)
getData("device", setShowContent, setLoading);
else getData(props.table, setShowContent, setLoading);
};
const getHeader = () => {
return <tr>{showContent[0].map((elem, index) => <th>{elem}</th>)}</tr>
}
const getBody = () => {
const bodylist = Object.values(showContent.slice(1));
const body = bodylist.map((elem, index) => ({ ...elem, button: (<button onclick={() => deleteItem(elem.id)}>Delete</button>) }))
return body.map((row, index) => <tr> {Object.values(row).map((elem, i) => <td> {elem}</td>)} </tr>)
}
const deleteItem = async (id) => {
await axios.delete('http://localhost:3000/api/' + id, { tablename: props.table }, {
headers: {
"x-access-token": token,
},
})
LoadTable();
}
return (
<div>
{!loading ?
<h1>Loading</h1> :
<table>
<thead>{getHeader()}</thead>
<tbody>{getBody()}</tbody>
</table>
}
</div>
);
Related
this is a component that retrieve data from API and generate a table. One button in a column for removing the row opens a Modal.
For avoiding in first component render to trigger api.delete request, an useRef set as false in second useEffect
Modal's Delete button return row info in deleteHandler function which successfully trigger api.delete request on confirmation, remove user in backend however table is not reloading.
Once row is removed expected result is triggering api.get request and display table now updated and without row removed.
In order to get that result I tried with const [reload, setReload] = useState(false); state which introduce another dependency to both userEffect
reload state effectively reload table data updated however it cause also that api.delete request trigger with const ida undefined. Below component script it can find useEffect with my tried.
Any hint or possible solution is appreciated
import React, { Fragment, useEffect, useState, useRef } from "react";
... other imports ...
export default function AllUsers() {
const api = useApi();
const [userData, setUserData] = useState();
const [showModal, setShowModal] = useState(false);
const navigate = useNavigate();
const [modalMessage, setModalMessage] = useState();
const [removeUserId, setRemoveUserId] = useState();
const [ida, setIda] = useState();
let effectRan = useRef(false);
const [reload, setReload] = useState(false);
useEffect(() => {
(async () => {
const response = await api.get("/admin/users");
if (response.ok) {
setUserData(response.body);
} else {
setUserData(null);
}
})();
}, [api]);
useEffect(() => {
if (effectRan.current) {
// console.log("effect run");
(async () => {
const response = await api.delete("/admin/users", {
ida: ida,
});
if (response.ok && response.status === 204) {
console.log(response);
} else {
console.log(response.body.errors);
}
})();
}
return () => (effectRan.current = true);
}, [api, ida]);
const handleEditClick = (e, rowIndex) => {
// console.log("user/" + rowIndex.username);
navigate("/user/" + rowIndex.username, [navigate]);
};
const handleRemoveClick = (e, rowIndex) => {
// console.log([rowIndex]);
setShowModal(true);
setModalMessage({
title: `Remove ${rowIndex.username}`,
message: `User's email to remove ${rowIndex.email}`,
});
setRemoveUserId(rowIndex.id);
};
const closeHandler = () => {
setShowModal(false);
};
const deleteHandler = () => {
// console.log(removeUserId);
setIda(removeUserId);
setShowModal(false);
};
// console.log(ida, idb);
return (
<Fragment>
{showModal && (
<BtModal
show={showModal}
title={modalMessage.title}
message={modalMessage.message}
handleClose={closeHandler}
onConfirm={deleteHandler}
/>
)}
<Body>
<h1>User Table</h1>
{userData === undefined ? (
<Spinner animation="border" />
) : (
<>
{userData === null ? (
<p>Could not retrieve users.</p>
) : userData.length === 0 ? (
<p>There are not users in system</p>
) : (
<UserListTable
newData={userData}
handleEditClick={handleEditClick}
handleRemoveClick={handleRemoveClick}
/>
)}
</>
)}
</Body>
</Fragment>
);
}
useEffect updated with reload state:
useEffect(() => {
(async () => {
const response = await api.get("/admin/users");
if (response.ok) {
setUserData(response.body);
effectRan.current = false;
} else {
setUserData(null);
}
})();
}, [api, reload]);
useEffect(() => {
if (effectRan.current) {
// console.log("effect run");
(async () => {
const response = await api.delete("/admin/users", {
ida: ida,
});
if (response.ok && response.status === 204) {
console.log(response);
setReload(!reload);
} else {
console.log(response.body.errors);
}
})();
}
return () => (effectRan.current = true);
}, [api, ida, reload]);
Here you modify your last edit
const deleteHandler = () => {
// console.log(removeUserId);
setIda(removeUserId);
setReload(!reload)
setShowModal(false);
};
useEffect(() => {
(async () => {
const response = await api.get("/admin/users");
if (response.ok) {
setUserData(response.body);
} else {
setUserData(null);
}
})();
}, [api]);
useEffect(() => {
effectRan.current = reload;
if (effectRan.current) {
// console.log("effect run");
(async () => {
const response = await api.delete("/admin/users", {
ida: ida,
});
if (response.ok && response.status === 204) {
console.log(response);
setReload(!reload);
} else {
console.log(response.body.errors);
}
})();
}
return () => {
effectRan.current = false;
};
}, [api, ida, reload]);
It looks like you're trying to delete an item in the table and confirm it via a prompt. I can see that you've used a lot of useEffects, which might be making things a bit complicated.
Don't worry though, I have a solution that should be much simpler. Let me show you what I mean!
const Page = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState([]);
const [deleting, setDeleting] = useState(false);
const [selectedItem, setSelectedItem] = useState(null);
const deleteHandler = async() => {
setDeleting(true);
await api.delete(selectedItem);
// Next you can either update the state itself
let tD = [...data];
const index = tD.findIndex((i) => i.id == selectedItem);
tD.splice(index, 1);
setSelectedItem(tD);
// or refetch data from the backend and update the state
const d = await api.getData();
setData(d);
setDeleting(false);
setSelectedItem(null);
};
useEffect(() => {
const fetchData = async() => {
setLoading(true);
const d = await api.getData();
setData(d);
setLoading(false);
}
fetchData();
}, [])
return loading ? <div>Loading...</div> : <>
{/*Other JSX elements*/}
{selectedItem && <div>
{/*prompt body*/}
<button onClick={() => {setSelectedItem(null)}}>Cancel</button>
<button disabled={deleting} onClick={() => {deleteHandler()}}>Delete</button>
{/*Button will be disabled when the DELETE request is sent*/}
</div>}
{data.map((row) => {
return <tr key={row.id}>
<td>...</td>
<td>...</td>
<td>
<button onClick={setSelectedItem(row.id)}>Delete</button>
</td>
</tr>
})}
</>
}
I want to calculate totalSum. Api gives 'amount' rows, but I need to add them together and cant yet find correct function. I am still learning with reactjs.
const url = "http://127.0.0.1:8000/api/expense";
const TableCardList = () => {
const [data, setData] = useState([]);
const getData = async () => {
const response = await fetch(url);
const data = await response.json();
setData(data);
console.timeLog(data);
};
useEffect (() => {
getData();
}, []);
let tableList = data.map((row) => {
return (
<TableCard
key={row.id}
id={row.id}
created_at={row.created_at}
title={row.title}
category={row.category}
amount={row.amount}
/>
);
});
return(
<div className="row">
<table class="table">
{tableList}
<td><b className="number"> {totalSum} Eur</b></td>
</table>
</div>
);
};
export default TableCardList;
Array.prototype.reduce can do the trick
const url = "http://127.0.0.1:8000/api/expense";
const TableCardList = () => {
const [data, setData] = useState([]);
const [totalSum, setTotalSum] = useState(0);
useEffect(() => {
const getData = async () => {
const response = await fetch(url);
const data = await response.json();
setData(data);
console.timeLog(data);
};
getData()
}, []);
useEffect(() => {
const total = data.reduce((acc, row) => acc + row.amount, 0);
setTotalSum(total)
}, [data]);
let tableList = data.map((row) => (
<TableCard
key={row.id}
id={row.id}
created_at={row.created_at}
title={row.title}
category={row.category}
amount={row.amount}
/>
));
return (
<div className="row">
<table class="table">
{tableList}
<td><b className="number"> {totalSum} Eur</b></td>
</table>
</div>
);
};
export default TableCardList;
A page list items, 10 by 10, with an infinite scroll.
Each item has a button "add to favorite", which when is pressed called the callback function handleClickFavorite, in order to not rerender items already rendered.
But, when handleClickFavorite is called, "data" are not fresh... If I had "data" dependency to handleClickFavorite = useCallback(async (item) => {...}, [user, data]);, "data" will be fresh, but each time I load more items, all items are rerended (I have a console.log into my Card PureComponent). So, how How can I do to use a fresh "data" in my handleClickFavorite without rerendered all my items please ?
const Home = () => {
const [user, setUser] = useState({ email: null, auth: false, favorites: [] });
const [data, setData] = useState([]);
const [isInfiniteDisabled, setInfiniteDisabled] = useState(false);
const config = useRef({
page: 0,
});
const loadData = (ev) => {
setInfiniteDisabled(true);
config.current.page += 1;
service.findAll(config.current.page).then(res => {
setData([
...data,
...res['items']
]);
});
}
useIonViewWillEnter(() => {
console.log('useIonViewWillEnter');
loadData();
});
const handleClickFavorite = useCallback(async (item) => {
if (user.auth) {
user.favorites.push(item.id);
setUser(user);
const datas = [...data];
for (let k in datas) {
if (datas[k].id === item.id) {
datas[k].rated = !datas[k].rated;
}
}
setData(datas);
} else {
// show login modal
}
}, [user]);
return (
<IonPage>
<IonContent fullscreen>
<IonList>
{data.map((item, index) => {
return <Card key={'card' + item.id} details={item} onClickFavorite={handleClickFavorite} />
})}
</IonList>
<IonInfiniteScroll
onIonInfinite={loadData}
threshold="100px"
disabled={isInfiniteDisabled}
>
<IonInfiniteScrollContent
loadingSpinner="bubbles"
loadingText="Loading more data..."
></IonInfiniteScrollContent>
</IonInfiniteScroll>
</IonContent>
</IonPage>
);
};
I have such a problem with pagination: it switches to another page only from the second click. When I click on page 2, it also remains on page 1, and only from the second time it switches to page 2. Also with the rest of the pages.
I did pagination component like this:
const Paginator = ({
total,
startPage = 1,
limit = 2,
totalPages = null,
onMovePage = null,
}) => {
const [hovered, setHovered] = useState(false);
const handleEnter = () => {
setHovered(true);
};
const handleLeave = () => {
setHovered(false);
};
const style = hovered ? { left: "-230px" } : {};
const [currentPage, setCurrentPage] = useState(startPage);
function range(start, stop, step) {
if(typeof stop=='undefined'){/*one param defined*/stop=start;start=0}
if(typeof step=='undefined'){step=1}
if((step>0&&start>=stop)||(step<0&&start<=stop)){return[]}
let result=[];
for(let i=start;step>0?i<stop:i>stop;i+=step){result.push(i)}
return result;
};
return (
<>
...
{range(1, totalPages+1).map(p => (
<PagItem key={p} handleClick={ () => {setCurrentPage(p); onMovePage && onMovePage({currentPage})} } title={p} name={p} />
))}
...
</>
}
And using it in softwares component:
const PER_PAGE = 2;
const Softwares = () => {
const [softwares, setSoftwares] = useState([]);
const [total, setTotal] = useState(null);
const [totalPages, setTotalPages] = useState(null);
const onFetchData = ({ currentPage }) => {
console.log('currentPage in onFetchData', currentPage)
fetch(`http://127.0.0.1:8000/api/software/?p=${currentPage}&per_page=${PER_PAGE}`)
.then(response => response.json())
.then(data => {
setSoftwares(data.results);
setTotal(data.count);
setTotalPages(data.total_pages);
})
}
useEffect(() => {
onFetchData({ currentPage: 1 })
}, []);
return (
<>
...
{softwares.map(s => (
<Article key={s.id} pathname={s.id} title={s.title} image={s.image} pubdate={s.pub_date} icon={s.category.parent.img} categoryID={s.category.id} categoryName={s.category.name} dCount={s.counter} content={s.content} />
))}
<Paginator totalPages={totalPages} total={total} onMovePage={onFetchData} limit={PER_PAGE} />
...
</>
);
};
So why is it happening?
Change the below
<PagItem key={p} handleClick={ () => {setCurrentPage(p); onMovePage && onMovePage({currentPage})} } title={p} name={p} />
to
<PagItem key={p} handleClick={ () => {setCurrentPage(p); onMovePage && onMovePage({currentPage:p})} } title={p} name={p} />
Because you're assuming your state currentPage is set by the time you call onMovePage which isn't true. Rely on the p to move to that page instead of state which will be set asynchronously.
While my code works from the functionalities, I have to click "refresh" after each click to see the changes. For example when I click "Add note" I have to refresh the page in order to see it. While it compiles successfully, the console shows three errors:
import { API, graphqlOperation } from "aws-amplify";
import { withAuthenticator } from "aws-amplify-react";
import React, { useEffect, useState } from "react";
import { createNote, deleteNote, updateNote } from "./graphql/mutations";
import { listNotes } from "./graphql/queries";
import {
onCreateNote,
onDeleteNote,
onUpdateNote
} from "./graphql/subscriptions";
const App = () => {
const [id, setId] = useState("");
const [note, setNote] = useState("");
const [notes, setNotes] = useState([]);
useEffect(() => {
getNotes();
const createNoteListener = API.graphql(
graphqlOperation(onCreateNote)
).subscribe({
next: noteData => {
const newNote = noteData.value.data.onCreateNote;
setNotes(prevNotes => {
const oldNotes = prevNotes.filter(note => note.id !== newNote.id);
const updatedNotes = [...oldNotes, newNote];
return updatedNotes;
});
setNote("");
}
});
const deleteNoteListener = API.graphql(
graphqlOperation(onDeleteNote)
).subscribe({
next: noteData => {
const deletedNote = noteData.value.data.onDeleteNote;
setNotes(prevNotes => {
const updatedNotes = prevNotes.filter(
note => note.id !== deletedNote.id
);
return updatedNotes;
});
}
});
const updateNoteListener = API.graphql(
graphqlOperation(onUpdateNote)
).subscribe({
next: noteData => {
const updatedNote = noteData.value.data.onUpdateNote;
setNotes(prevNotes => {
const index = prevNotes.findIndex(note => note.id === updatedNote.id);
const updatedNotes = [
...prevNotes.slice(0, index),
updatedNote,
...prevNotes.slice(index + 1)
];
return updatedNotes;
});
setNote("");
setId("");
}
});
return () => {
createNoteListener.unsubscribe();
deleteNoteListener.unsubscribe();
updateNoteListener.unsubscribe();
};
}, []);
const getNotes = async () => {
const result = await API.graphql(graphqlOperation(listNotes));
setNotes(result.data.listNotes.items);
};
const handleChangeNote = event => setNote(event.target.value);
const hasExistingNote = () => {
if (id) {
const isNote = notes.findIndex(note => note.id === id) > -1;
return isNote;
}
return false;
};
const handleAddNote = async event => {
event.preventDefault();
// Check if we have an exisiting note. If so, then update it.
if (hasExistingNote()) {
handleUpdateNote();
} else {
const input = { note };
await API.graphql(graphqlOperation(createNote, { input }));
}
};
const handleUpdateNote = async () => {
const input = { id, note };
await API.graphql(graphqlOperation(updateNote, { input }));
};
const handleDeleteNote = async noteId => {
const input = { id: noteId };
await API.graphql(graphqlOperation(deleteNote, { input }));
};
const handleSetNote = ({ note, id }) => {
setNote(note);
setId(id);
};
return (
<div className="flex flex-column items-center justify-center pa3 bg-washed-red">
<h1 className="code f2-l">Amplify Notetake</h1>
{/* Note Form */}
<form onSubmit={handleAddNote} className="mb3">
<input
type="text"
className="pa2 f4"
placeholder="Write your note"
onChange={handleChangeNote}
value={note}
/>
<button className="pa2 f4" type="submit">
{id ? "Update note" : "Add note"}
</button>
</form>
{/* Notes list */}
<div>
{notes.map(item => (
<div key={item.id} className="flex items-center">
<li onClick={() => handleSetNote(item)} className="list pa1 f3">
{item.note}
</li>
<button
onClick={() => handleDeleteNote(item.id)}
className="bg-transparent bn f4"
>
<span>×</span>
</button>
</div>
))}
</div>
</div>
);
};
export default withAuthenticator(App, { includeGreetings: true });
If you are using CLI version 2.0 and above, owner is a required argument. This is explained more in the link below:
https://aws-amplify.github.io/docs/cli-toolchain/graphql#authorizing-subscriptions
After I added Auth in the import
import { API, graphqlOperation, Auth } from 'aws-amplify';
captured the current user and passed it into the subscription it started working for me.
useEffect(() => {
getNotes();
const owner = Auth.user.getUsername();
const createNoteListener = API.graphql(
graphqlOperation(onCreateNote, { owner })
).subscribe({
next: noteData => {
const newNote = noteData.value.data.onCreateNote;
setNotes(prevNotes => {
const oldNotes = prevNotes.filter(note => note.id !== newNote.id);
const updatedNotes = [...oldNotes, newNote];
return updatedNotes;
});
setNote("");
}
});