Search code examples
javascriptreactjstypescriptzustand

How to keep to select boxes in sync based on zustand store value in react?


One of the requirements for an web app i'm working on is to have multiple languages, to implement this i'm using i18n and zustand to store the new set language and have it accessible in any component/page.

I have two select boxes, one in the navbar and another in the footer of the page to set language. The problem is, when i set a new language in one select box, the page is translated, but i want the other select box to have it's default value as the new language set(picking it up from zustand store), so in both the navbar and the footer would have the same default value and be synced whenever one has changed.

For example the page starts with portuguese, but if i change it to english, the other select box still has the old value(portuguese), altho it's default value is set to the value in zustand store.

I've thought and tried couple solutions like storing the language in local storage and then get it or creating a function that runs always but none worked for me. Also wasn't alble to find anyonw with a similiar problem when researching it.

Bellow follows the code for the navbar component, the footer component and the zustand store.

Navbar:

import {
  BtnWrapper,
  LanguageSelector,
  Link,
  Logo,
  NavBar,
  NavListItem,
  NavlinkItemHighlighted,
  Navlist,
  RegisterAndLoginButton,
} from "./NavbarWithoutLogin.styles";
import { useTranslation } from "react-i18next";
import { useWebSiteLanguageStore } from "../../../store/WebSiteLanguageStore";

export const Nav = () => {
  const { t } = useTranslation()
  const websiteLanguageStore = useWebSiteLanguageStore()

  return (
    <>
      <NavBar>
        <Logo>logo</Logo>
        <Navlist>
          <NavlinkItemHighlighted>
            <span>{t("navbarLink1")}</span>
          </NavlinkItemHighlighted>
          <NavListItem>
            <Link>{t("navbarLink2")}</Link>
          </NavListItem>
          <NavListItem>
            <Link>{t("navbarLink3")}</Link>
          </NavListItem>
          <NavListItem>
            <Link>{t("navbarLink4")}</Link>
          </NavListItem>
          <NavListItem>
            <Link>{t("navbarLink5")}</Link>
          </NavListItem>
          <NavListItem>
            <RegisterAndLoginButton>
              {t("navbarLoginButton")}
            </RegisterAndLoginButton>
            |
            <RegisterAndLoginButton>
              {t("navbarRegisterButton")}
            </RegisterAndLoginButton>
          </NavListItem>
        </Navlist>
        <BtnWrapper>
          <LanguageSelector
            defaultValue={websiteLanguageStore.currentlySelectedLanguage}
            onChange={(e) =>
              websiteLanguageStore.changeCurrentlySelectedLanguage(
                e.target.value
              )
            }
          >
            <option value="pt">{t("navbarLanguageSelectorOption1")}</option>
            <option value="fr">{t("navbarLanguageSelectorOption2")}</option>
            <option value="en">{t("navbarLanguageSelectorOption3")}</option>
            <option value="de">{t("navbarLanguageSelectorOption4")}</option>
            <option value="tt">{t("navbarLanguageSelectorOption5")}</option>
            <option value="es">{t("navbarLanguageSelectorOption6")}</option>
          </LanguageSelector>
        </BtnWrapper>
      </NavBar>
    </>
  );
};

Footer:

import {
  LinkBox,
  LinkBoxUlLi,
  Footer,
  Copyright,
  CopyrightText,
  HelpCenterBox,
  LanguageSelector,
} from "./PageFooter.styles";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCopyright } from "@fortawesome/free-solid-svg-icons";
import {
  faFacebook,
  faLinkedin,
  faSquareInstagram,
  faYoutube,
} from "@fortawesome/free-brands-svg-icons";
import { useTranslation } from "react-i18next";
import { useWebSiteLanguageStore } from "../../../store/WebSiteLanguageStore";

export const PageFooter = () => {
  const { t } = useTranslation();
  const websiteLanguageStore = useWebSiteLanguageStore();

  return (
    <>
      <Footer>
        <div>
          <b>{t("footerFistSectionTitle")}</b>
          <LinkBox>
            <LinkBoxUlLi>
              <a>{t("footerFirstSectionOptions1")}</a>
            </LinkBoxUlLi>
            <LinkBoxUlLi>
              <a>{t("footerFirstSectionOptions12")}</a>
            </LinkBoxUlLi>
            <LinkBoxUlLi>
              <a>{t("footerFirstSectionOptions13")}</a>
            </LinkBoxUlLi>
          </LinkBox>
        </div>
        <div>
          <b>{t("footerSecondSectionTitle")}</b>
          <LinkBox>
            <LinkBoxUlLi>
              <a>{t("footerSecondSectionOption")}</a>
            </LinkBoxUlLi>
            <LinkBoxUlLi>
              <a>{t("footerSecondSectionOption2")}</a>
            </LinkBoxUlLi>
            <LinkBoxUlLi>
              <a>{t("footerSecondSectionOption3")}</a>
            </LinkBoxUlLi>
            <LinkBoxUlLi>
              <a>{t("footerSecondSectionOption4")}</a>
            </LinkBoxUlLi>
          </LinkBox>
        </div>
        <div>
          <b>{t("footerThirdSectionTitle")}</b>
          <LinkBox>
            <LinkBoxUlLi>
              <a>{t("footerThirdSectionOption")}</a>
            </LinkBoxUlLi>
            <LinkBoxUlLi>
              <a>{t("footerThirdSectionOption2")}</a>
            </LinkBoxUlLi>
          </LinkBox>
        </div>
        <div>
          <b>{t("footerThirdSectionTitle")}</b>
          <LinkBox>
            <LinkBoxUlLi>
              <HelpCenterBox>
                {t("footerFourthSectionHelpCenterBox")}
              </HelpCenterBox>
            </LinkBoxUlLi>
          </LinkBox>
          <span>{t("footerFourthSectionLanguageSelector")}: </span>
          <LanguageSelector
            defaultValue={websiteLanguageStore.currentlySelectedLanguage}
            onChange={(e) => {
              websiteLanguageStore.changeCurrentlySelectedLanguage(
                e.target.value
              );
            }}
          >
            <option value="pt">{t("footerLanguageSelectorOption1")}</option>
            <option value="fr">{t("footerLanguageSelectorOption2")}</option>
            <option value="en">{t("footerLanguageSelectorOption3")}</option>
            <option value="de">{t("footerLanguageSelectorOption4")}</option>
            <option value="tt">{t("footerLanguageSelectorOption5")}</option>
            <option value="es">{t("footerLanguageSelectorOption6")}</option>
          </LanguageSelector>
        </div>
        <div>
          <b>{t("footerFollowUsSectionTitle")}</b>
          <LinkBox>
            <LinkBoxUlLi>
              <a>
                <FontAwesomeIcon icon={faFacebook} size="xl" />
              </a>{" "}
              <a>
                <FontAwesomeIcon icon={faSquareInstagram} size="xl" />
              </a>{" "}
              <a>
                <FontAwesomeIcon icon={faYoutube} size="xl" />{" "}
                <a>
                  <FontAwesomeIcon icon={faLinkedin} size="xl" />
                </a>{" "}
              </a>{" "}
            </LinkBoxUlLi>
          </LinkBox>
        </div>
      </Footer>
      <Copyright>
        <CopyrightText>
          <FontAwesomeIcon icon={faCopyright} /> {t("footerCopyWrightText")}{" "}
        </CopyrightText>
      </Copyright>
    </>
  );
};

zustand store:

import { create } from "zustand";
import i18n from "../i18n";

type WebSiteLanguageSettings = {
  currentlySelectedLanguage: string
  changeCurrentlySelectedLanguage: (language: string) => void
}

export const useWebSiteLanguageStore = create<WebSiteLanguageSettings>(
  (set) => {
    return {
      currentlySelectedLanguage: i18n.language,
      changeCurrentlySelectedLanguage(language) {
        i18n.changeLanguage(language)
        this.currentlySelectedLanguage = language
      },
    };
  }
);

I'm using styled-components, if the style files are needed i can post it here.


Solution

  • I'm not sure of how good or efficient is this, but i found a solution for this problem. Using this code i managed to make the two select boxes synced whenever a language change is set to the website.

    Also took couple tips from everyone answering and commenting, so thanks all. If any one as tips to improve this and prevent bugs or problems in the future please be welcome.

    I was thinking if i really needed zustand for this, from a point of if i could implement this without using it but still be good, i'm a beginner in react and also state managment solutions like zustand seems to be appropiate to more complex state managment problems.

    I created an helper function setLanguage, which takes the new select value when onChange, and sets the new language with i18n.changeLanguage and stores the language in localStorage. In the i18n definition function i put it to pick the language from localStorage or english as default and fallback too.

    Heres the code:

    import i18n from "../i18n";
    
    export const setLanguage = (language: string) => {
        i18n.changeLanguage(language);
        localStorage.setItem("language", language)
        window.location.reload();
    };
    
    
    
    import {
      BtnWrapper,
      Link,
      Logo,
      NavBar,
      NavListItem,
      NavlinkItemHighlighted,
      Navlist,
      RegisterAndLoginButton,
    } from "./NavbarWithoutLogin.styles";
    import { useTranslation } from "react-i18next";
    import { setLanguage } from "../../../helpers/setLanguage";
    import { LanguageSelectorSelect } from "../LanguageSelector/LanguageSelector";
    
    export const Nav = () => {
      const { t } = useTranslation();
    
      return (
        <>
          <NavBar>
            <Logo>Ilha</Logo>
            <Navlist>
              <NavlinkItemHighlighted>
                <span>{t("navbar.navbarLink1")}</span>
              </NavlinkItemHighlighted>
              <NavListItem>
                <Link>{t("navbar.navbarLink2")}</Link>
              </NavListItem>
              <NavListItem>
                <Link>{t("navbar.navbarLink3")}</Link>
              </NavListItem>
              <NavListItem>
                <Link>{t("navbar.navbarLink4")}</Link>
              </NavListItem>
              <NavListItem>
                <Link>{t("navbar.navbarLink5")}</Link>
              </NavListItem>
              <NavListItem>
                <RegisterAndLoginButton>
                  {t("navbar.navbarLoginButton")}
                </RegisterAndLoginButton>
                |
                <RegisterAndLoginButton>
                  {t("navbar.navbarRegisterButton")}
                </RegisterAndLoginButton>
              </NavListItem>
            </Navlist>
            <BtnWrapper>
              <LanguageSelectorSelect
                value={String(localStorage.getItem("language"))}
                onChange={(e) => {
                  setLanguage(e.target.value);
                }}
              >
                <option value="pt">{t("navbar.navbarLanguageSelectorOption1")}</option>
                <option value="fr">{t("navbar.navbarLanguageSelectorOption2")}</option>
                <option value="en">{t("navbar.navbarLanguageSelectorOption3")}</option>
                <option value="de">{t("navbar.navbarLanguageSelectorOption4")}</option>
                <option value="tt">{t("navbar.navbarLanguageSelectorOption5")}</option>
                <option value="es">{t("navbar.navbarLanguageSelectorOption6")}</option>
              </LanguageSelectorSelect>
            </BtnWrapper>
          </NavBar>
        </>
      );
    };
    
    
    import {
      LinkBox,
      LinkBoxUlLi,
      Footer,
      Copyright,
      CopyrightText,
      HelpCenterBox,
    } from "./PageFooter.styles";
    import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
    import { faCopyright } from "@fortawesome/free-solid-svg-icons";
    import {
      faFacebook,
      faLinkedin,
      faSquareInstagram,
      faYoutube,
    } from "@fortawesome/free-brands-svg-icons";
    import { useTranslation } from "react-i18next";
    import { setLanguage } from "../../../helpers/setLanguage";
    import { LanguageSelectorSelect } from "../LanguageSelector/LanguageSelector";
    
    export const PageFooter = () => {
      const { t } = useTranslation();
    
      return (
        <>
          <Footer>
            <div>
              <b>{t("footerSection.footerFistSectionTitle")}</b>
              <LinkBox>
                <LinkBoxUlLi>
                  <a>{t("footerSection.footerFirstSectionOptions1")}</a>
                </LinkBoxUlLi>
                <LinkBoxUlLi>
                  <a>{t("footerSection.footerFirstSectionOptions12")}</a>
                </LinkBoxUlLi>
                <LinkBoxUlLi>
                  <a>{t("footerSection.footerFirstSectionOptions13")}</a>
                </LinkBoxUlLi>
              </LinkBox>
            </div>
            <div>
              <b>{t("footerSection.footerSecondSectionTitle")}</b>
              <LinkBox>
                <LinkBoxUlLi>
                  <a>{t("footerSection.footerSecondSectionOption")}</a>
                </LinkBoxUlLi>
                <LinkBoxUlLi>
                  <a>{t("footerSection.footerSecondSectionOption2")}</a>
                </LinkBoxUlLi>
                <LinkBoxUlLi>
                  <a>{t("footerSection.footerSecondSectionOption3")}</a>
                </LinkBoxUlLi>
                <LinkBoxUlLi>
                  <a>{t("footerSection.footerSecondSectionOption4")}</a>
                </LinkBoxUlLi>
              </LinkBox>
            </div>
            <div>
              <b>{t("footerSection.footerThirdSectionTitle")}</b>
              <LinkBox>
                <LinkBoxUlLi>
                  <a>{t("footerSection.footerThirdSectionOption")}</a>
                </LinkBoxUlLi>
                <LinkBoxUlLi>
                  <a>{t("footerSection.footerThirdSectionOption2")}</a>
                </LinkBoxUlLi>
              </LinkBox>
            </div>
            <div>
              <b>{t("footerSection.footerThirdSectionTitle")}</b>
              <LinkBox>
                <LinkBoxUlLi>
                  <HelpCenterBox>
                    {t("footerSection.footerFourthSectionHelpCenterBox")}
                  </HelpCenterBox>
                </LinkBoxUlLi>
              </LinkBox>
              <span>
                {t("footerSection.footerFourthSectionLanguageSelector")}:{" "}
              </span>
              <LanguageSelectorSelect
                value={String(localStorage.getItem("language"))}
                onChange={(e) => {
                  setLanguage(e.target.value);
                }}
              >
                <option value="pt">
                  {t("footerSection.footerLanguageSelectorOption1")}
                </option>
                <option value="fr">
                  {t("footerSection.footerLanguageSelectorOption2")}
                </option>
                <option value="en">
                  {t("footerSection.footerLanguageSelectorOption3")}
                </option>
                <option value="de">
                  {t("footerSection.footerLanguageSelectorOption4")}
                </option>
                <option value="tt">
                  {t("footerSection.footerLanguageSelectorOption5")}
                </option>
                <option value="es">
                  {t("footerSection.footerLanguageSelectorOption6")}
                </option>
              </LanguageSelectorSelect>
            </div>
            <div>
              <b>{t("footerSection.footerFollowUsSectionTitle")}</b>
              <LinkBox>
                <LinkBoxUlLi>
                  <a>
                    <FontAwesomeIcon icon={faFacebook} size="xl" />
                  </a>{" "}
                  <a>
                    <FontAwesomeIcon icon={faSquareInstagram} size="xl" />
                  </a>{" "}
                  <a>
                    <FontAwesomeIcon icon={faYoutube} size="xl" />{" "}
                    <a>
                      <FontAwesomeIcon icon={faLinkedin} size="xl" />
                    </a>{" "}
                  </a>{" "}
                </LinkBoxUlLi>
              </LinkBox>
            </div>
          </Footer>
          <Copyright>
            <CopyrightText>
              <FontAwesomeIcon icon={faCopyright} />{" "}
              {t("footerSection.footerCopyWrightText")}{" "}
            </CopyrightText>
          </Copyright>
        </>
      );
    };