State
Unanswered
Blue Picardy Spaniel posted this in #help-forum
Blue Picardy SpanielOP
i have a nextjs application which is a discord bot dashboard. i have modules on the side which you can click. each module can be enabled and disabled and when enabled, you can edit settings in said module.
each module toggle has its own component, and when changed, a value in the database is changed. when i refresh my page, the content is changed to reflect the toggle change. however i want the state to actively update, not just when i reload my page. i already tried using use context, however it isnt working because some of my components are server components and i don't want to extract everything to the client.
any ideas?
each module toggle has its own component, and when changed, a value in the database is changed. when i refresh my page, the content is changed to reflect the toggle change. however i want the state to actively update, not just when i reload my page. i already tried using use context, however it isnt working because some of my components are server components and i don't want to extract everything to the client.
any ideas?
25 Replies
@Blue Picardy Spaniel i have a nextjs application which is a discord bot dashboard. i have modules on the side which you can click. each module can be enabled and disabled and when enabled, you can edit settings in said module.
each module toggle has its own component, and when changed, a value in the database is changed. when i refresh my page, the content is changed to reflect the toggle change. however i want the state to actively update, not just when i reload my page. i already tried using use context, however it isnt working because some of my components are server components and i don't want to extract everything to the client.
any ideas?
to archive that you need to have a very good understanding on how this works:
That said you need to wrap the stuff with the settings (context) und client functionallity inside client components, but keep the server functionality. Make sure you walking from the tree upwards and not downwards. You want the last components inside your tree to be client side if they need to be clientside. Not the first components, because else all nested server components will be translated to client components (as mentioned)
'use client'
<div>
{children} // can stay server component
</div>
<div>
<ServerComponent /> // will be translated to a client component
</div>That said you need to wrap the stuff with the settings (context) und client functionallity inside client components, but keep the server functionality. Make sure you walking from the tree upwards and not downwards. You want the last components inside your tree to be client side if they need to be clientside. Not the first components, because else all nested server components will be translated to client components (as mentioned)
Blue Picardy SpanielOP
how is {children} a server component if you're in a client component?
and with this method, would you say still use context?
@Blue Picardy Spaniel how is {children} a server component if you're in a client component?
that's the trick ^^
You can pass everything in there. Server components, client components and it will stay as server component/client component
You can pass everything in there. Server components, client components and it will stay as server component/client component
@Blue Picardy Spaniel and with this method, would you say still use context?
yes. You need to concentrate a lot when you converting it. Else there might be server component that gets translated to client components and you don't notice it while developing. So it can be pretty hard and you need to concentrate a lot
@B33fb0n3 yes. You need to concentrate a lot when you converting it. Else there might be server component that gets translated to client components and you don't notice it while developing. So it can be pretty hard and you need to concentrate a lot
Blue Picardy SpanielOP
but i thought server components couldn't useContext()?
@Blue Picardy Spaniel but i thought server components couldn't useContext()?
they can't, but client components can. So create a provider for your settings, fill your settings with serverside data. Then change your data (clientside, because it's inside your useContext Settings provider with the default SSR data) and the data will instantly change (because it's handled clientside)
@B33fb0n3 they can't, but client components can. So create a provider for your settings, fill your settings with serverside data. Then change your data (clientside, because it's inside your useContext Settings provider with the default SSR data) and the data will instantly change (because it's handled clientside)
Blue Picardy SpanielOP
so when displaying settings how should i get the data (in order to have it live updated)?
@Blue Picardy Spaniel so when displaying settings how should i get the data (in order to have it live updated)?
you update the data clientside. So you have a useState inside your SettingsProvider and that use state will be also passed to the Consumers so that they can update the specific settings.
If you don't want it like that way, you can also build an event based in app system or use [an exisiting one](https://docs.amplify.aws/gen1/javascript/build-a-backend/utilities/hub/)
If you don't want it like that way, you can also build an event based in app system or use [an exisiting one](https://docs.amplify.aws/gen1/javascript/build-a-backend/utilities/hub/)
@B33fb0n3 you update the data clientside. So you have a useState inside your SettingsProvider and that use state will be also passed to the Consumers so that they can update the specific settings.
If you don't want it like that way, you can also build an event based in app system or use [an exisiting one](https://docs.amplify.aws/gen1/javascript/build-a-backend/utilities/hub/)
Blue Picardy SpanielOP
i really want to get my head around using context to live update settings, do you think there's a tutorial or an example somewhere?
@Blue Picardy Spaniel i really want to get my head around using context to live update settings, do you think there's a tutorial or an example somewhere?
yes, there is a tutorial: https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#using-context-providers
Of course not specifically for your case, but you can build your stuff around that functionality.
This little guide shows what I mentioned in small here: https://nextjs-forum.com/post/1269212424863481949#message-1269213856480231445
You might need this:
Of course not specifically for your case, but you can build your stuff around that functionality.
This little guide shows what I mentioned in small here: https://nextjs-forum.com/post/1269212424863481949#message-1269213856480231445
You might need this:
type SettingsProviderProps = {
initialSettings: Partial<SettingsProviderSettingsTypes>,
channel: string
children: React.ReactNode
}
export const SettingsContext = createContext<Partial<SettingsProviderSettingsTypes>>({} as SettingsProviderSettingsTypes);
export default function SettingsProvider({initialSettings, channel, children}: SettingsProviderProps) {
const [currentSettings, setCurrentSettings] = useState(initialSettings)
// I done it here via an event based system, but ...
useHubListen(channel, async (data) => {
const settingId = data.payload.event;
setCurrentSettings((prev) => {
return {
...prev,
[settingId]: data.payload.data
}
})
});
// ... , but you can also pass the "setCurrentSettings" to your consumers like mentioned
return <SettingsContext.Provider value={currentSettings}>
{children}
</SettingsContext.Provider>
}@B33fb0n3 yes, there is a tutorial: https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#using-context-providers
Of course not specifically for your case, but you can build your stuff around that functionality.
This little guide shows what I mentioned in small here: https://discord.com/channels/752553802359505017/1269212424863481949/1269213856480231445
You might need this:
tsx
type SettingsProviderProps = {
initialSettings: Partial<SettingsProviderSettingsTypes>,
channel: string
children: React.ReactNode
}
export const SettingsContext = createContext<Partial<SettingsProviderSettingsTypes>>({} as SettingsProviderSettingsTypes);
export default function SettingsProvider({initialSettings, channel, children}: SettingsProviderProps) {
const [currentSettings, setCurrentSettings] = useState(initialSettings)
// I done it here via an event based system, but ...
useHubListen(channel, async (data) => {
const settingId = data.payload.event;
setCurrentSettings((prev) => {
return {
...prev,
[settingId]: data.payload.data
}
})
});
// ... , but you can also pass the "setCurrentSettings" to your consumers like mentioned
return <SettingsContext.Provider value={currentSettings}>
{children}
</SettingsContext.Provider>
}
Blue Picardy SpanielOP
"use client";
import type { ReactNode } from "react";
import React, { createContext, useState, useContext, useEffect } from "react";
import type { UserGuild } from "@/types/guilds";
import { getGuilds } from "@/lib/api/@me";
interface GuildContextType {
guilds: UserGuild[] | null;
updateGuildSetting: (guildId: string, settingKey: string, value: boolean) => void;
}
const GuildContext = createContext<GuildContextType | undefined>(undefined);
export const GuildProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [guilds, setGuilds] = useState<UserGuild[] | null>(null);
useEffect(() => {
(async function() {
const fetchedGuilds = await getGuilds();
setGuilds(fetchedGuilds);
})();
}, []);
const updateGuildSetting = (guildId: string, settingKey: string, value: boolean) => {
setGuilds(prevGuilds => {
if(!prevGuilds) return null;
return prevGuilds.map(guild =>
guild.id === guildId
? { ...guild, settings: { ...guild.settings, [settingKey]: value } }
: guild
);
});
};
return (
<GuildContext.Provider value={{ guilds, updateGuildSetting }}>
{children}
</GuildContext.Provider>
);
};
export const useGuilds = (): GuildContextType => {
const context = useContext(GuildContext);
if (context === undefined) {
throw new Error("useGuilds must be used within a GuildProvider");
}
return context;
};does this page look fine for my use guild context?
@Blue Picardy Spaniel ts
"use client";
import type { ReactNode } from "react";
import React, { createContext, useState, useContext, useEffect } from "react";
import type { UserGuild } from "@/types/guilds";
import { getGuilds } from "@/lib/api/@me";
interface GuildContextType {
guilds: UserGuild[] | null;
updateGuildSetting: (guildId: string, settingKey: string, value: boolean) => void;
}
const GuildContext = createContext<GuildContextType | undefined>(undefined);
export const GuildProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [guilds, setGuilds] = useState<UserGuild[] | null>(null);
useEffect(() => {
(async function() {
const fetchedGuilds = await getGuilds();
setGuilds(fetchedGuilds);
})();
}, []);
const updateGuildSetting = (guildId: string, settingKey: string, value: boolean) => {
setGuilds(prevGuilds => {
if(!prevGuilds) return null;
return prevGuilds.map(guild =>
guild.id === guildId
? { ...guild, settings: { ...guild.settings, [settingKey]: value } }
: guild
);
});
};
return (
<GuildContext.Provider value={{ guilds, updateGuildSetting }}>
{children}
</GuildContext.Provider>
);
};
export const useGuilds = (): GuildContextType => {
const context = useContext(GuildContext);
if (context === undefined) {
throw new Error("useGuilds must be used within a GuildProvider");
}
return context;
};
does this page look fine for my use guild context?
I am not that into your app but you shouldn't fetch the data clientside in your useEffect. Pass the data from the server to your client component and fetch like that serverside
@B33fb0n3 I am not that into your app but you shouldn't fetch the data clientside in your useEffect. Pass the data from the server to your client component and fetch like that serverside
Blue Picardy SpanielOP
ahh so pass the guilds into the GuildProvider
that makes a lot of sense
@Blue Picardy Spaniel ahh so pass the guilds into the GuildProvider
yea, like I done it via
as props
initialSettings: Partial<SettingsProviderSettingsTypes>,as props
Blue Picardy SpanielOP
okay and last thing,
import { type ReactNode } from "react";
import { Alert } from "@mantine/core";
import { Icon } from "@iconify/react";
import Switch from "@/components/ui/Switch";
import type { UserGuild } from "@/types/guilds";
import { moduleToAbbreviation } from "@/lib/utils";
export default async function Module({ guild, moduleName, description, noGrid, canDisableModule = true, children }: { guild: UserGuild, moduleName: string, description: string, noGrid?: boolean, canDisableModule?: boolean, children: ReactNode }) {
const moduleEnabled = guild.settings?.[moduleToAbbreviation(moduleName)];
return (
<>
{!moduleEnabled && moduleName !== "Overview" && <Alert
variant="light"
color="red"
radius="md"
title="Module Disabled"
icon={<Icon icon="mdi:alert-circle-outline"/>}
className="mb-6"
>
This module is disabled!
</Alert>}
<div className="flex items-center">
<h1 className="text-3xl font-semibold mb-2 mr-5">{moduleName}</h1>
{canDisableModule &&
<Switch
size="lg"
withThumbIcon
defaultValue={moduleEnabled}
guild={guild}
settingKey={moduleToAbbreviation(moduleName)}
/>
}
</div>
<p className="text-slate-300 border-b pb-3 mb-6 border-line-two">{description}</p>
<div className={!noGrid ? "grid grid-cols-2 gap-4" : ""}>
{children}
</div>
</>
);
}this is how im currently getting the guild settings
i need to switch it to useContext
but this is a server component
in your case the
GuildProvider is a client component and client components can be used inside server components. Like that you can fetch the data inside a server component and then pass the fetched data to your provider@B33fb0n3 in your case the GuildProvider is a client component and client components can be used inside server components. Like that you can fetch the data inside a server component and then pass the fetched data to your provider
Blue Picardy SpanielOP
im confused, don't i just need to do
const { guilds } = useGuilds();?Blue Picardy SpanielOP
nvm i just converted it to a client component