So I have been trying to figure out how to rerender the items map with new items when I click the "Mark Read" button. I tried putting the items in a useState and updating the state on button click, but the issue with that is when I set the state it's always one render behind because set state is async. How would I go about implementing this?
const Main: React.FC<MainProps> = (props) => {
// Get All Feeds
const feeds = trpc.feed.getAllFeeds.useQuery();
// Get Items
let unsorteditems;
if (props.FeedType == "one") {
unsorteditems = trpc.feed.getItemsFromFeed.useQuery({
feedId: props.feedId,
});
} else if (props.FeedType == "readalready") {
unsorteditems = trpc.feed.getReadItems.useQuery();
} else {
unsorteditems = trpc.feed.getFeedItemsFromAllFeeds.useQuery();
}
// Sort Items than add title, logo, and markedAsRead
const sorteditems = unsorteditems.data?.sort(
(objA, objB) => Number(objB.createdAt) - Number(objA.createdAt)
// Replace in other order to sort in reverse
);
let items = sorteditems?.map((item) => {
const feed = feeds.data?.find((feed) => feed.id == item.feedId);
return {
...item,
feedTitle: feed?.title,
feedLogoUrl: feed?.LogoUrl,
markedAsRead: false,
};
});
const feedName = items?.[0]?.feedTitle;
// Mark Read function
const markreadmutation = trpc.feed.markReadUsingUpsert.useMutation();
const onClickMarkRead = async (itemId: string) => {
markreadmutation.mutate({ itemId: itemId });
};
// I tried this but it didn't work, and I can't use state because its async and dosent update in time
function updateItems(index: number) {
const newItems = items?.map((item, i) => {
if (i == index) {
return {
...item,
markedAsRead: true,
};
} else {
return item;
}
});
items = newItems;
}
return (
<>
{/* I removed alot of code here to make it easier to understand so css may look weird */}
{/* <!-- Main --> */}
<div className="basis-5/12 border-l border-slate-300/40">
<h1 className="sticky top-0 z-50 border-b border-slate-300/40 bg-opacity-60 bg-clip-padding py-2 pl-2 text-xl font-bold backdrop-blur-xl backdrop-filter">
{props.FeedType == "one"
? feedName
: props.FeedType == "all"
? "All Feeds"
: props.FeedType == "readalready"
? "Recently Read"
: "Woops"}
</h1>
<div>
{items?.length == 0 && <p>No Items</p>}
{items?.map((item, i) => (
<div
className={`border-slate-300/4 relative flex h-28 flex-col border-b ${
item.markedAsRead ? "bg-gray-200" : "bg-white"
}`}
key={item.id}
>
{/* ^^^ If item is marked read it will be grayed out ^^^ */}
<button
onClick={() => {
// THIS BUTTON
// When I click this is should rerender the map with the item marked as read
onClickMarkRead(item.id);
updateItems(i);
}}
>
[Mark Read]
</button>
</div>
))}
</div>
</div>
</>
);
};
CodePudding user response:
You better use state. You can't access asynchronous code on first render of React component. And in order to use the latest value of the items, you should store your items in state, then create useCallback function for your async calls and items manipulation (sort etc) and finally call this function in useEffect. It will look something like this:
const [items, setItems] = useState([]);
const getItems = useCallback( async() => {
const fetchedItems = await yourFetchFunction();
setItems(fetchedItems);
}, []);
useEffect(() => {
getItems();
}, [getItems]);
CodePudding user response:
After clicking the button, you should call the updateItems only after awaiting the mutation. In the current code provided you call it synchronously. You can add it inside onClickMarkRead. Pass the index in it and try like this (in updateItems set items in state after updating them):
const onClickMarkRead = async (itemId: string, index: number) => {
await markreadmutation.mutate({ itemId: itemId });
updateItems(index);
};
If it still doesn't work, you can provide the whole component with the latest code changes via jsFiddle or CodePen or GitHub, so I can see and eventually play around with it.