'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.
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 1:[1]
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>
Solution 2:[2]
Maybe you can make a regular React component and pass lang as props ?
const SetLang = ({ lang }) => {
const { i18n } = useTranslation();
useEffect(() => {
localStorage.setItem('preferredLanguage', lang);
i18n.changeLanguage(lang);
}, [])
return (
<Redirect to='/'/>
)
}
<Switch>
<Route exact path="/es" >
<SetLang lang="es"/>
</Route>
<Route exact path="/cat" >
<SetLang lang="cat"/>
</Route>
...
</Switch>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | rphlmr |
