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!
CodePudding user response:
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>
CodePudding user response:
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>