State doesn't seem to cause a re-render. Next.js 14 (app router, client component)
Answered
SpicyJungle posted this in #help-forum
i'm having an issue with nextjs 14 app router here. might be an obvious issue but I can't find it.
The
The
guild state here doesn't seem to cause a re-render of the component, at least when I pass the guild prop down it doesn't change any of the data i use with it. dashboard/page.tsx <h2 className="text-2xl text-white font-bold">Your Servers</h2>
{guilds &&
guilds.map((guild) => {
const isCommon = commonGuilds.find(
(g: dbGuild) => g.guildId === guild.id
);
return (
<HoverCard key={guild.id}>
<HoverCardTrigger
className="w-full bg-black/40 p-2 text-white rounded-md hover:scale-105 transition-all"
onClick={() => {
if (!isCommon) return;
setGuild({ ...isCommon, ...guild }) // <--- I update state here
}}
>
{guild.name}
</HoverCardTrigger>
...
// this is in the same component as above:
<div className="main h-full w-full bg-black/40 rounded-md">
{guild ? <Dash guild={guild} /> : <h1 className="w-full text-center mt-20 text-xl text-white">Select a server to begin.</h1>}
// ^ this doesn't update (so I'm guessing it doesn't rerender)
</div>Answered by joulev
Maybe add a key to force update? <Dash guild={guild} key={guild.id} />
14 Replies
Bump
@SpicyJungle i'm having an issue with nextjs 14 app router here. might be an obvious issue but I can't find it.
The `guild` state here doesn't seem to cause a re-render of the component, at least when I pass the `guild` prop down it doesn't change any of the data i use with it.
`dashboard/page.tsx`
tsx
<h2 className="text-2xl text-white font-bold">Your Servers</h2>
{guilds &&
guilds.map((guild) => {
const isCommon = commonGuilds.find(
(g: dbGuild) => g.guildId === guild.id
);
return (
<HoverCard key={guild.id}>
<HoverCardTrigger
className="w-full bg-black/40 p-2 text-white rounded-md hover:scale-105 transition-all"
onClick={() => {
if (!isCommon) return;
setGuild({ ...isCommon, ...guild }) // <--- I update state here
}}
>
{guild.name}
</HoverCardTrigger>
...
// this is in the same component as above:
<div className="main h-full w-full bg-black/40 rounded-md">
{guild ? <Dash guild={guild} /> : <h1 className="w-full text-center mt-20 text-xl text-white">Select a server to begin.</h1>}
// ^ this doesn't update (so I'm guessing it doesn't rerender)
</div>
try add a console.log here
onClick={() => {
if (!isCommon) return;
const newGuild = { ...isCommon, ...guild }
console.log(newGuild)
setGuild(newGuild)
}}Thinking about it, I display the guild name somewhere else, and it updates as intended. So it does rerender and i'm messing up somewhere else
Sometimes react is too aggressive in caching the component
found the place of error, not sure how to solve. So in i'm adding a way to blacklist certain articles. This is the code for the states of the component + the function that sends a request to blacklist it.
On the two console.log statements, i see the
const BlacklistedArticles = ({ guild }: { guild: ExtendedGuild }) => {
const [article, setArticle] = useState("");
const [blacklisted, setBlacklisted] = useState<string[]>(
guild.blacklistedArticles
);
console.log("CURRENT BLACKLISTED", blacklisted)
console.log("GUILD BLACKLIST", guild.blacklistedArticles)
const sendReq = async () => {
if (!article) return;
setArticle("");
const prevBlacklist = blacklisted;
setBlacklisted([...blacklisted, article]);
const res = await axios
.post(
`/api/discord/blacklist?guild=${guild.id}&type=article&article=${article}`
)
.catch((e: AxiosError) => {
setBlacklisted(prevBlacklist);
toast.error(
"Failed to blacklist article: " + e.response?.data ?? "Unkown error"
);
});
if (!res) return;
guild.blacklistedArticles.push(article);
toast.success("Successfully blacklisted article.");
};
...On the two console.log statements, i see the
blacklisted state doesn't clear when changing guild@joulev Maybe add a key to force update? <Dash guild={guild} key={guild.id} />
omfg fixed everything
thank you so much 😠ðŸ™
@SpicyJungle found the place of error, not sure how to solve. So in i'm adding a way to blacklist certain articles. This is the code for the states of the component + the function that sends a request to blacklist it.
ts
const BlacklistedArticles = ({ guild }: { guild: ExtendedGuild }) => {
const [article, setArticle] = useState("");
const [blacklisted, setBlacklisted] = useState<string[]>(
guild.blacklistedArticles
);
console.log("CURRENT BLACKLISTED", blacklisted)
console.log("GUILD BLACKLIST", guild.blacklistedArticles)
const sendReq = async () => {
if (!article) return;
setArticle("");
const prevBlacklist = blacklisted;
setBlacklisted([...blacklisted, article]);
const res = await axios
.post(
`/api/discord/blacklist?guild=${guild.id}&type=article&article=${article}`
)
.catch((e: AxiosError) => {
setBlacklisted(prevBlacklist);
toast.error(
"Failed to blacklist article: " + e.response?.data ?? "Unkown error"
);
});
if (!res) return;
guild.blacklistedArticles.push(article);
toast.success("Successfully blacklisted article.");
};
...
On the two console.log statements, i see the `blacklisted` state doesn't clear when changing guild
Oh then this is expected. States do not change when the initial value of the state changes. The key solution is the way to resolve this
TIL
though i guess that does make sense
Yeah you would probably want to use only a single source of truth and consume + update the guild value directly rather than use a separate state for the guild.blacklistedArticles