Reputation: 111
Code below works fine except when call Add
function which adds new number to array,
array contains only last added item.
dont understand why it does not store IDs
App.ts
import MyComponent from "./main";
import { FavoriteContext, usePostsContextValue } from "./store";
function App() {
const postsContextValue = usePostsContextValue();
return (
<FavoriteContext.Provider value={postsContextValue}>
<MyComponent />
</Container>
</FavoriteContext.Provider>
);
}
export default App;
Component:
import { FavoriteContext } from "../store";
...
function MyComponent() {
const [data, setData] = useState<certificates[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [pageNumber, setPageNumber] = useState<number>(1);
const { IDs, Add, Remove } = useContext(FavoriteContext);
useEffect(() => {
console.log("IDs", IDs); //logs only 1 item, last selected Id
}, [IDs]);
const handleAddClick= async (id: number) => {
Add(id);
};
const columns: TableColumn<certificates>[] = useMemo(() => {
return [
{
name: "STATUS",
cell: (row) => <strong>{status(row.status)}</strong>,
},
{
cell: (row) => {
return (
<IconButton onClick={() => handleAddClick(row.id)}>
<BookmarkBorderIcon />
</IconButton>
);
},
button: true,
},
];
}, []);
return (
<DataTable
columns={columns}
data={data}
pagination
paginationServer
paginationTotalRows={totalRows}
onChangeRowsPerPage={handlePerRowsChange}
onChangePage={handlePageChange}
/>
);
}
export default MyComponent;
Context.ts:
import React, { useCallback, useMemo, useState } from "react";
import { favouriteType } from "../models";
export const defaultValue: favouriteType = {
IDs: [],
Add: (id: number) => {},
Remove: (id: number) => {},
};
export const FavoriteContext = React.createContext<favouriteType>(defaultValue);
function usePostsContextValue(): favouriteType {
const [IDs, setIDs] = useState<number[]>([]);
const Add = (id: number) => {
setIDs([...IDs, id]);
};
const Remove = (id: number) => {
const newPosts = [...IDs];
const removedPostIndex = newPosts.findIndex((f) => f === id);
if (removedPostIndex > -1) {
newPosts.splice(removedPostIndex, 1);
}
setIDs(newPosts);
};
return {
IDs,
Add,
Remove,
};
}
export { usePostsContextValue };
Upvotes: 0
Views: 33
Reputation: 166
Every time, a new ID is added, a new Add
function is created which would capture the value of the new ID.
const Add = (id: number) => {
setIDs([...IDs, id]); // <-- this capture the ID of the outer scope
};
This works as expected, however looking at the component below:
const columns: TableColumn<certificates>[] = useMemo(() => {
return [
{
name: "STATUS",
cell: (row) => <strong>{status(row.status)}</strong>,
},
{
cell: (row) => {
return (
<IconButton onClick={() => handleAddClick(row.id)}>
<BookmarkBorderIcon />
</IconButton>
);
},
button: true,
},
];
}, []);
Since this component is never rerendered, the Add
function is not updated, so it is always the first version where the captured IDs
is empty []
. Thus adding one ID is always:
const Add = (id: number) => {
setIDs([...[], id]); // the first vesion of Add captured IDs = [];
};
and always have only the last ID.
The solution is to include handleAddClick
(which captures the new Add
function in the useMemo dep array:
const columns: TableColumn<certificates>[] = useMemo(() => {
return [
{
name: "STATUS",
cell: (row) => <strong>{status(row.status)}</strong>,
},
{
cell: (row) => {
return (
<IconButton onClick={() => handleAddClick(row.id)}>
<BookmarkBorderIcon />
</IconButton>
);
},
button: true,
},
];
}, [handleAddClick]);
However, this destroys the purpose of useMemo
since Add
/handleAddClick
always recreated on every render, so the solution is to wrap both of them in useCallback
.
const Add = useCallback((id: number) => {
setIDs([...IDs, id]);
}, [IDs]); // notice I have to include IDs here so that the function can be recreated with the new IDs.
const handleAddClick= useCallback(async (id: number) => {
Add(id);
}, []);
Better yet, you can use the function syntax of setState
, which removes the need to include IDs
in the dep array, improving performance.
const Add = useCallback((id: number) => {
setIDs((prevIDs) => [...prevIDs, id]); // the function form will receive the previous state as the argument.
}, []);
Upvotes: 1