Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Does abstracting out a functional component containing a hook call violate the rules of hooks?

I’m establishing two paths to switch language in my site in my react router. The components associated with those paths need just to perform a useEffect to set the language (though i18n) and then redirect to /. They look like this:

const SetEs = () => {
  const { i18n } = useTranslation();
  useEffect(() => {
    localStorage.setItem('preferredLanguage', 'es');
    i18n.changeLanguage('es');
  }, [])

  return (
    <Redirect to='/'/>
  )
}

const SetCat = () => {
  const { i18n } = useTranslation();
  useEffect(() => {
    localStorage.setItem('preferredLanguage', 'cat');
    i18n.changeLanguage('cat');
  }, [])

  return (
    <Redirect to='/'/>
  )
}

Then in the routes Switch…

<Switch>
  <Route exact path="/es" >
    <SetEs/>
  </Route>
  <Route exact path="/cat" >
    <SetCat/>
  </Route>
  ...
</Switch>

So far so good.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

Now, looking back at the component’s code I see clear that would be very kind to all my future "me"s and future coworkers to abstract a little that code redundancy. So I wrap it up in a function which takes the lang as parameter… hopefully it will simplify things out in the long run:

const SetLang = (lang: string) => () => {
  const { i18n } = useTranslation();
  useEffect(() => {
    localStorage.setItem('preferredLanguage', lang);
    i18n.changeLanguage(lang);
  }, [])

  return (
    <Redirect to='/'/>
  )
}
const SetEs = SetLang('es');
const SetCat = SetLang('cat');

But now I can’t get pass this error: React Hook "useTranslation" cannot be called inside a callback

(I think) I know the rules of hooks. I know they shouldn’t be called inside nested functions but I thought, up until now, that this referred only to functions nested within a component. Then there is the Don’t call Hooks from regular JavaScript functions but again, this is a regular function which returns a component which in turn calls the hook.

What am I missing? How do I get to be a good colleague nicely abstracting things out in a situation like this? What’s the correct way of factoring components?

Thanks!

>Solution :

Create a custom hook – useLang that accepts lang as an argument:

const useLang = (lang: string) => {
  const { i18n } = useTranslation()
  
  useEffect(() => {
    localStorage.setItem('preferredLanguage', lang)
    i18n.changeLanguage(lang);
  }, [lang])
}

Create a SetLang component that accepts lang as a prop, and calls useLang:

interface Props {
  lang: string;
}

const SetLang = ({ lang }: Props) => {
  useLang(lang)

  return (
    <Redirect to='/'/>
  )
}

Use it in your Route passing the lang prop:

<Switch>
  <Route exact path="/es" >
    <SetLang lang="es" />
  </Route>
  <Route exact path="/cat" >
    <SetLang lang="cat" />
  </Route>
  ...
</Switch>

You can also create wrapper components:

const SetEs = () => <SetLang lang="es" />;
const SetCat = () => <SetLang lang="cat" />;

and use them as you originally intended:

<Switch>
  <Route exact path="/es" >
    <SetEs />
  </Route>
  <Route exact path="/cat" >
    <SetCat />
  </Route>
  ...
</Switch>
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading