I have a basic question about SSR in general. I come from experience only in CSR and I find it quite struggling to do thing in SSR at the moment.
I have a case exemple that I would like someone to help me solve in order to understand a bit better.
In the current case I have an Nx repo with an app and some lib and I would like to setup a language changer.
apps/src/service => a service related to this app in order to set the locale of the user
'use server';
import { cookies } from 'next/headers';
import { defaultLocale } from 'src/config';
// In this example the locale is read from a cookie. You could alternatively
// also read it from a database, backend service, or any other source.
const COOKIE_NAME = 'NEXT_LOCALE';
export async function getUserLocale() {
return cookies().get(COOKIE_NAME)?.value || defaultLocale;
}
export async function setUserLocale(locale: string) {
cookies().set(COOKIE_NAME, locale);
}
apps/**/HeaderLanguage => a server component on my app that I would like it to show a language picker and change the language when done
import { useTranslations } from 'next-intl';
import { LanguagePicker } from '~remo-work/shared/ui';
import { locales } from 'src/config';
import { setUserLocale } from 'src/services';
export const HeaderLanguage = ({ current }: { current: string }) => {
const t = useTranslations('shared:ui.LanguagePicker');
const languages = locales.map((locale) => {
return {
label: t('language', { language: locale } }),
value: locale,
};
});
return (
<div>
<LanguagePicker
languages={languages}
current={current}
// this will not work in this current setup.
onLanguageChange={(value: string) => setUserLocale(value)}
/>
</div>
);
};
libs/shared/ui/LanguagePicker => a standalone component that is reusable across multiple app that has the sole purepose of making a menu to display languages, and dispatch on change an event
'use client';
import {
Button,
Menu,
MenuDropdown,
MenuItem,
MenuProps,
MenuTarget,
rem,
} from '@mantine/core';
import { IconWorld } from '@tabler/icons-react';
export const LanguagePicker = ({
languages,
current,
onLanguageChange,
...rest
}: {
languages: { label: string; value: string }[];
current: string;
onLanguageChange: (language: string) => void;
} & MenuProps) => {
return (
<Menu {...rest}>
<MenuTarget>
<Button
variant="transparent"
tt="uppercase"
leftSection={
<IconWorld style={{ width: rem(14), height: rem(14) }} />
}
>
{languages.find((lang) => lang.value === current)?.label}
</Button>
</MenuTarget>
<MenuDropdown>
{languages.map(({ value, label }) => (
<MenuItem key={value} onClick={() => onLanguageChange(value)}>
{label}
</MenuItem>
))}
</MenuDropdown>
</Menu>
);
};
Now, apps/src/service
is related to the app logic, so it should be usable in the app scope only, apps/**/HeaderLanguage
need to use translation and the cookies, so it should be usable in server only, finally, libs/shared/ui/LanguagePicker
do some client interaction so it should be client component.
The issue in the above setup is that the server can’t bind an event with the client, so I need the client component to be able to change the language, but my LanguagePicker is a shared component so it should remain dumb and not know “how to change the language”. Also it can’t use cookies since it’s server only. it feels like a circle that can’t be closed.
How can I connect the dot properly?