Changing language with React Hooks and Contex API

Viewed 795

I'm creating a page and it will be avaliable to use in 3 languages. I created 3 files that contain data neccesary to change displayed text. The concept is to check users browser language and display the language accordingly (it works ok) but also to give user option to change language of the page by buttons (it doesn't work). I used Context API to pass language and function to change language to other components - passing language worked but passing function didn't. The problem is that the page doesn't respond when I click on the buttons when it passed like this:

changeToPl: changeToPl,

this way was used in article : https://medium.com/swlh/react-context-api-hooks-and-functional-state-7c9005b43432

but if I do it like this:

changeToPl: changeToPl(),

I have too many renders. Below App component that contain context and language logic:

const LanguageContext = React.createContext({
    // language: '',
    // changeLanguage: () => {},
  }
);

const App = () => {

  let detectedLanguage = '';

  if (navigator.language.includes('pl')) {
    detectedLanguage = pl;
  } else if (navigator.language.includes('de')) {
    detectedLanguage = de;
  } else {
    detectedLanguage = en;
  }

  const [userLanguage, setUserLanguage] = useState(detectedLanguage);

  /*const chooseLanguage = (lang) => {
    // setUserLanguage(en);
    localStorage.setItem('lang', 'eeen');
    console.log(lang);
  }*/
  
  const changeToPl = () => {
    setUserLanguage(pl);
    console.log('pl');
  }

  const changeToEn = () => {
    setUserLanguage(en);
    console.log('en');
  }

  const changeToDe = () => {
    setUserLanguage(de);
    console.log('de');
  }

  // let chosenLanguage = userLanguage;

  const usedLanguage = userLanguage || detectedLanguage;


  return (
    <LanguageContext.Provider 
      value={{
        language: usedLanguage,
        changeToPl: changeToPl,
        changeToEn: changeToEn,
        changeToDe: changeToDe,
      }}
    >
      <GlobalStyle />
      <CookieBanner />
      <PopUp />
      <Navbar />
      {/* <Sidebar /> */}
      <Switch>
        <Route exact path='/' component={Home} /> 
        <Route exact path='/artworks/:slug' component={Artwork} />
        <Route exact path='/exhibitions' component={ExhibitionList} />
        <Route exact path='/biography' component={Biography} />
        <Route exact path='/contact' component={Contact} />
        <Route component={Error} />
      </Switch>
    </LanguageContext.Provider>
  );
}


export {App, LanguageContext};

and Sidebar component that contains buttons used to changing language:

export const Sidebar = ({position, hideSidebar}) => {

  const usedLanguage = useContext(LanguageContext);

  return (
    <>
      <Route exact path='/artworks/:slug'> {/* DISPLAYED ONLY IN ARTWORK COMPONENT */}
        <Link to='/'>
          <BackButton>
            {usedLanguage.language.menu.backToMainPage}
          </BackButton>
        </Link>
      </Route>
      <Sidenav style={{right: position}}>
        <Link to='/' onClick={hideSidebar}>
          <Button text={usedLanguage.language.menu.artworksMenuButton} color={variables.DIMGRAY}/>
        </Link>
        <Link to='/exhibitions' onClick={hideSidebar}>
          <Button text={usedLanguage.language.menu.exhibitionsMenuButton} color={variables.DIMGRAY}/>
        </Link>
        <Link to='/biography' onClick={hideSidebar}>
          <Button text={usedLanguage.language.menu.biographyMenuButton} color={variables.DIMGRAY}/>
        </Link>
        <Link to='/contact' onClick={hideSidebar}>
          <Button text={usedLanguage.language.menu.contactMenuButton} color={variables.DIMGRAY}/>
        </Link>
        <LanguageButtonsWrapper>
      <Button text='pl' color={variables.DIMGRAY} onClick={LanguageContext.changeToPl} />
      <Button text='en' color={variables.DIMGRAY} onClick={LanguageContext.changeToEn} />
      <Button text='de' color={variables.DIMGRAY} onClick={LanguageContext.changeToDe} />
    </LanguageButtonsWrapper>
        <SocialButtons>
          <SocialButtonIcon href='https://www.facebook.com/StructuralistArt/'>
            <AiFillFacebook />
          </SocialButtonIcon>
          <SocialButtonIcon href='https://www.instagram.com/structuralist_art/'>
            <AiFillInstagram />
          </SocialButtonIcon>
          <SocialButtonIcon href='https://www.behance.net/michalkrol263a63?fbclid=IwAR1LWaRyvVuh9UuM3MHByTYgqDOKG8Qo7h6mQq7tTM3ezJXU-HQ4Wtq09i8'>
            <AiFillBehanceSquare />
          </SocialButtonIcon>
        </SocialButtons>
      </Sidenav>
    </>
  )
}

I know that the function used to changing language could be more generic (3 in 1), context data could be in seperate file and that I have to use localStorage but for now I want to deal with described problem.

2 Answers

Ok I've figured it out. The problem was the structure of my App and btw. the thing I previously asked about :) Since I wanted to build reusable button, it looked like this:

export const Button = ({text, color, clickHandler}) => {

  return (
    <AppButton onClick={clickHandler} style={{color: color}}>
      {text}
    </AppButton>
  );
};

and because of that I needed to pass changeToPl/changeToEn/changeToDe functions like this:

<Button text='pl' color={variables.DIMGRAY} clickHandler={usedLanguage.changeToPl} />

not like this:

<Button text='pl' color={variables.DIMGRAY} onClick={usedLanguage.changeToPl} />

Seems your buttons should use the functions the context is providing, i.e. usedLanguage.changeToPl instead of LanguageContext.changeToPl:

const usedLanguage = useContext(LanguageContext);

...

<Button text='pl' color={variables.DIMGRAY} onClick={usedLanguage.changeToPl} />
<Button text='en' color={variables.DIMGRAY} onClick={usedLanguage.changeToEn} />
<Button text='de' color={variables.DIMGRAY} onClick={usedLanguage.changeToDe} />

You should also ensure the initial context value matches the shape that is actually used by components.

const LanguageContext = React.createContext({
  language: '',
  changeToPl: () => {},
  changeToEn: () => {},
  changeToDe: () => {}
});
Related